数据结构c语言篇 《二》链表概述,增删改查等多功能实现及相关面试题(上)
Posted 程序猿是小贺
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构c语言篇 《二》链表概述,增删改查等多功能实现及相关面试题(上)相关的知识,希望对你有一定的参考价值。
概述
链表概念
链表,别名链式存储结构或单链表,用于存储逻辑关系为 “一对一” 的数据。与顺序表不同,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置是随机的。
链表的结构
链表中每个数据的存储都由以下两部分组成:
数据域 | 指针域 |
---|
1.数据元素本身,其所在的区域称为数据域;
2.指向直接后继元素的指针,所在的区域称为指针域;
链表实际存储的是一个一个的结点,真正的数据元素包含在这些结点中,如下图。
头节点和头指针
头节点:其实就是一个不存任何数据的空节点,通常作为链表的第一个节点。
头指针:一个普通的指针,它的特点是永远指向链表第一个节点的位置。
首元节点:由于头节点(也就是空节点)的缘故,链表中称第一个存有数据的节点为首元节点。
一个完整链表结构如图
注意:链表中有头节点时,头指针指向头节点;反之,若链表中没有头节点,则头指针指向首元节点。
链表的实现
0515SList.h
// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);
//单链表在pos位置之前插入x
void SListInsertBefore(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
//链表的销毁
void SListDestory(SListNode** pplist);
0515SList.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <Windows.h>
#include<assert.h>
#include"0515SList.h"
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
SListNode *node = (SListNode *)malloc(sizeof(SListNode));
//此处不能使用assert断言,因为node==NULL合法
if (node == NULL);
{
assert(0);
return NULL;
}
node->data = x;
node->next = NULL;
return node;
}
// 单链表打印
void SListPrint(SListNode* plist)
{
SListNode *cur = plist;
while (cur)
{
printf("%d-->", cur->data);
cur = cur->next;
}
printf("NULL\\n");
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist);
//空链表
if (*pplist == NULL)
{
*pplist = BuySListNode(x);
}
else// 非空
{
//1.先找到链表中的最后一个结点
//2.插入元素
SListNode *cur = *pplist;
while (cur->next)
{
//不能使用cur++,因为链表物理地址不连续,则cur++会与cur->next有差异
cur=cur->next;
}
cur->next = BuySListNode(x);
}
}
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
assert(pplist);
if (NULL == *pplist)//空链表
{
return;
}
else if (NULL == (*pplist)->next)// 链表中只有一个结点
{
free(*pplist);
*pplist = NULL;
}
else
{
// 链表中有多个结点
//1.找到最后一个结点并保存前一个,然后删掉最后一个,最后前一个的next指向NULL
SListNode *cur = *pplist;
SListNode *prev = NULL;
while (cur->next)
{
prev = cur;
cur = cur->next;
}
prev->next = NULL;
free(cur);
}
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode *newnode = BuySListNode(x);
newnode->next = *pplist;
*pplist = newnode;
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
SListNode *delNode = NULL;
assert(pplist);
if (NULL == *pplist)//空链表
{
return;
}
else
{
delNode = *pplist;
*pplist = delNode->next;
free(delNode);
}
}
//求链表长度
int Slistsize(SListNode* plist)
{
int count = 0;
SListNode *cur = plist;
while (cur != NULL)
{
cur = cur->next;
count++;
}
return count;
}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
assert(plist);
SListNode *cur = plist;
while (cur)
{
if (x == cur->data)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
SListNode *newnode = NULL;
if (pos == NULL)
return;
newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//在之前插入
void SListInsertBefore(SListNode* pos, SLTDateType x)
{
SListNode *newnode = NULL;
if (pos == NULL)
return;
newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
SListNode*tmp = pos;
pos = newnode;
newnode = tmp;
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
SListNode *delNode = NULL;
if (pos == NULL||pos->next ==NULL)
return;
else
{
delNode = pos->next;
pos->next = delNode->next;
free(delNode);
}
}
//链表的销毁
void SListDestory(SListNode **pplist)//直接使用头删法
{
SListNode *delNode = NULL;
if (NULL == *pplist)//空链表
{
return;
}
while (NULL != *pplist)//循环删除
{
delNode = *pplist;
*pplist = delNode->next;
free(delNode);
}
}
test.c(略)
还有一个.c的实现文件我在这里就不给大家写了,要一一实现的话可能篇幅就有点大了,有机会的话大家自己把代码拷过去自己实现一下,让我偷一小会懒,谢谢~
相关面试题
由于今天工作量有点多,后面的一些题目为了节省时间我就不给大家画图展示了,相互理解一下谢谢大家了,然后大家歇一下这里正菜来了
1.移除链表元素
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
思路:
1.从头开始遍历链表,判断开头是否相同,如果有先删除开头部分。
2.依次向后遍历,进行删除结点的操作。当最后为空时跳出循环,遍历结束。
注意:在删除过程中记得保留该结点的前一个结点
核心代码方法一
struct ListNode* removeElements(struct ListNode* head, int val){
struct ListNode*cur=head;
struct ListNode*prev=NULL;
while(cur)
{
if(cur->val==val)
{
if(prev==NULL) //要移除的元素是第一个
{
head=cur->next;
free(cur);
cur=head;
}
else// 要移除的元素是除第一个以外的任一个
{
prev->next=cur->next;
free(cur);
cur=prev->next;
}
}
else
{
prev=cur;
cur=cur->next;
}
}
return head;
}
方法二 递归
struct ListNode* removeElements(struct ListNode* head, int val){
if(head==NULL)
{
return NULL;
}
head->next=removeElements(head->next,val);
return head->val==val?head->next:head;
}
2.反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
方法一
1.三指针法
在遍历列表时,将当前节点的 next 指针改为指向前一个元素。由于节点没有引用其上一个节点,因此必须事先存储其前一个元素。在更改引用之前,还需要另一个指针来存储下一个节点。最后return时候需要注意,cur已经指向NULL,prev才是新的头指针。
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* next= NULL;
struct ListNode* cur = head;
struct ListNode* prev = NULL;
while(NULL != cur){
next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
return prev;
}
头插法:将原链表结点逐个拆下来,然后头插到新链表中
struct ListNode* reverseList(struct ListNode* head){
struct ListNode*cur=head;
struct ListNode*newhead=NULL;
if(head==NULL)
return NULL;
while(cur)
{
head=cur->next;//head指向cur的next,保存下一个的地址
cur->next=newhead;//让cur的next指向newhead,
newhead=cur;//把newhead放到cur的位置
cur=head;//让cur往后走
}
return newhead;
}
3.链表的中间结点
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
思路:
这里我们只要使用快慢指针即可
定义两个指针,快指针每次走两步,慢指针每次走一步,同时从头结点开始往后走,若到最后快指针为空或者快指针的next为空循环停止,程序结束,此时要寻找的中间结点就是慢指针所在的结点位置。
核心代码
struct ListNode* middleNode(struct ListNode* head){
if(head==NULL||head->next==NULL)
{return head;}
struct ListNode *fast=head;
struct ListNode *slow=head;
while(fast&&fast->next)//fast不为空保证第一步成功,fast->next不为空保证第二步走成功
{
fast=fast->next->next;//每次往前走两步
slow=slow->next;//每次往前走一步
}
return slow;
}
当然如果题目要求返回第一个中间结点的话,则只需要定义一个指针用来保存慢指针,然后判断快指针是否为空,若为空则返回该指针。
4.获取该链表中倒数第k个结点。
输入一个链表,输出该链表中倒数第k个结点。
要求:只能遍历一遍
思路:
定义两个指针,一个先走k步然后两个指针再同时走,直到先走的指针为空然后结束,此时第二个指针所在的位置就是倒数第k个结点。
核心代码
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
{
struct ListNode* first=pListHead;
struct ListNode* second=pListHead;
//让first先往后走k步
while(k--)
{
if(first == NULL)
return NULL;
first = first->next;
}
//同时往后走,直到first为空退出
while(first)
{
first = first->next;
second = second->next;
}
return second;
}
5.链表相交
思路:
1.获取两个链表的节点个数。
2.求两个链表的结点个数差。
3.让长的那个链表先走差值步。
4.然后两个指针同时走,相遇的结点既是交点。
核心代码
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode *node1=headA;
struct ListNode *node2=headB;
int count1=0;
int count2=0;
if(headA==NULL||headB==NULL)//判断两链表是否为空,是的话直接返回NULL
return NULL;
while(node1)//遍历链表1,统计节点数
{
node1=node1->next;
count1++;
}
while(node2)//遍历链表2,统计节点数
{
node2=node2->next;
count2++;
}
int k=count1-count2;
struct ListNode *nodeA=headA;
struct ListNode *nodeB=headB;
if(k>0)//链表一节点数多于链表二,先走
{
while(k--)
{
nodeA=nodeA->next;
}
}
else if(k<0)//链表二节点数多于链表一,先走
{
int n=0-k;
while(n--)
{
nodeB=nodeB->next;
}
}
else// 两个链表节点数相同,不做处理
{
//不进行任何操作
}
while(nodeA)//此时链表1和链表二剩余节点数相同,循环条件哪个都可以,这里也可以使用链表二操作。
{
if(nodeA==nodeB)
{
break;
}
nodeA=nodeA->next;
nodeB=nodeB->next;
}
return nodeA;
}
以上就是今天的内容,关于那些面试题这里只给出了某一种解答方法,并不代表答案只有一种,由于篇幅原因我在这里就不多赘述了,作者才疏学浅,要是哪里写的有问题欢迎大佬们及时帮忙纠正,共同学习共同进步谢谢!~
未完,待续…
以上是关于数据结构c语言篇 《二》链表概述,增删改查等多功能实现及相关面试题(上)的主要内容,如果未能解决你的问题,请参考以下文章