基于C语言的单链表实现
Posted 胖仙人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于C语言的单链表实现相关的知识,希望对你有一定的参考价值。
->Gitee源代码点击这里<-
单链表是由不连续的空间组合而成的。每个空间称为单链表的一个结点。
一个结点中除了存放数据之外,还要存放下一个结点的地址,如果没有下一个结点,则存放空指针。表示其后面没有结点了
从逻辑上来看,就好像A和B链接了起来。
我们通过结构体来定义链表的结点信息
typedef int LDataType;
typedef struct LinkedList
LDataType data; //数据值
struct LinkedList* next;//下一个结点的地址
LinkedList;
单链表的好处是按需开辟空间,有数据要插入时,就开辟一个结点,删除数据时,就释放一个结点。
所以在处理数据之前,我们需要先写出一个开辟结点的接口
由于我们不知道一个
//开辟新结点
LinkedList* BuyNewNode(LDataType data);
LinkedList* BuyNewNode(LDataType data)
LinkedList* node = (LinkedList*)malloc(sizeof(LinkedList));
if (node == NULL)
printf("Buy new node failed!\\n");
exit(-1);
node->data = data;
node->next = NULL; //由于在创建新节点时,我们不知道下一个结点的情况,所以默认存放空指针
return node;
想要实现数据处理,我们还需要借助一个指针plist。指针类型和结构体类型相同。通过这个指针我们可以判断链表的情况。
当plist为空时,即plist不指向任何一个结点,则链表为空
当plist不为空时,即plist指向某个结点,则链表不为空。
plist初始化的值默认为NULL;
struct LinkedList* phead == NULL;
数据处理接口的设计:
一、头部插入数据
我们创建了plist,plist即代表链表。通过plist的状态来判断链表的状态。
plist的初始值为NULL:
此时链表为空。
在链表的头部插入第一个结点A,此时plist的值为A的地址值,plist指向第一个结点:
此时在链表头部再插入一个结点B,此时plist内存放的地址值就要变成结点B的地址:
并且此时需要注意,新插入的结点B中的指针部分存放的不再是空指针了,而是存放结点A的地址,以将两个结点关联起来
在这个过程中我们发现:
链表在数据处理时,plist的值是要发生变化的;所以接口在接收参数时,接受的应该是plist的地址(即传址调用)
void LinkedListPushHead(LinkedList** pplist, LDataType data);
void LinkedListPushHead(LinkedList** pplist, LDataType data)
LinkedList* node = BuyNewNode(data);
node->next = *pplist;
*pplist = node;
二、尾部插入数据
尾部插入数据分需要分两种情况讨论:
①链表内没有数据时,plist直接指向插入的新结点
②链表内有数据时,我们需要先找到插入之前链表的尾(指向的下一个结点为空的即为尾),让原来的尾指向插入的结点。
图中A结点即为插入新结点之前的尾,尾插入结点B之后,B成为新的尾,A指向B;
![image.png](https://img-blog.csdnimg.cn/img_convert/fd782107917a8807883cdebae64f8ff4.png#clientId=udc03f381-13bd-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=159&id=ue7802d1f&margin=[object Object]&name=image.png&originHeight=212&originWidth=1009&originalType=binary&ratio=1&rotation=0&showTitle=false&size=15178&status=done&style=shadow&taskId=ua0b0e98c-6d02-4ec9-9802-12d31918253&title=&width=757)
void LinkedListPushBack(LinkedList** pplist, LDataType data);
void LinkedListPushBack(LinkedList** pplist, LDataType data)
LinkedList* node = BuyNewNode(data);
LinkedList* tail = *pplist;
if (tail == NULL)
*pplist = node;
else
while (tail->next != NULL) //通过遍历找到原来的尾
tail = tail->next;
tail->next = node;
为了方便测试,还需要写一个打印链表的接口
void LinkedListPrint(LinkedList** pplist);
void LinkedListPrint(LinkedList** pplist)
LinkedList* cur = *pplist;
while (cur != NULL)
printf("%d->", cur->data);
cur = cur->next;
printf("NULL\\n");
三、头删数据
头删示意图如下,删除头结点A,则A结点的空间需要被释放,plist不在指向A,而是指向A指向的下一个结点(即B结点)
void LinkedListPopHead(LinkedList** pplist);
void LinkedListPopHead(LinkedList** pplist)
assert(*pplist != NULL);
LinkedList* head = *pplist;
*pplist = head->next;
free(head);
head = NULL;
四、尾删数据
尾删结点,首先需要找到尾,并释放尾结点,让尾结点之前的结点指向空
尾删示意图如下:结点A原本指向结点B,B指向NULL,尾删B之后,B空间被释放,结点A指向NULL
由于链表只能按着顺序往下走,所以当我们只创建一个tail指针找尾时,我们没办法访问尾指针之前的指针,此时则需要第二个指针prev来帮助记录tail指针前一个指针的位置,prev的初始值设为NULL;
特殊情况:当链表只有一个结点时,使用prev指针会出现空指针引用的情况,因此该情况需要单独拿出来考虑
void LinkedListPopBack(LinkedList** pplist);
void LinkedListPopBack(LinkedList** pplist)
assert(*pplist != NULL);
LinkedList* tail = *pplist;
LinkedList* prev = NULL;
if (tail->next == NULL) //只有一个结点的情况
free(tail);
tail = NULL;
*pplist = NULL;
else
while (tail->next != NULL) //有多个节点时,通过遍历找尾
prev = tail; //tail要往后走,prev则记录tail之前的位置
tail = tail->next; //tail往后遍历
prev->next = NULL;
free(tail);
tail = NULL;
五、查找
查找数据,查找到则返回结点的地址,否则返回NULL
LinkedList* LinkedListSearch(LinkedList** pplist, LDataType data);
LinkedList* LinkedListSearch(LinkedList** pplist, LDataType data)
LinkedList* cur = *pplist;
while (cur != NULL)
if (cur->data == data)
return cur;
else
cur = cur->next;
return NULL;
六、在指定结点之后插入新结点
由于链表遍历的不可逆,使得访问某个结点之前的结点很不方便,因为接口设计时,设计为在指定结点之后插入新结点
此外,链表不能像顺序表一样利用下标随机访问,所以该接口的使用还需要配合查找接口。需要先查找指定的结点,获得该结点的地址,再将该地址作为pos传给插入接口
void LinkedListPushAfter(LinkedList* pos, LDataType data);
void LinkedListPushAfter(LinkedList* pos, LDataType data)
assert(pos);
LinkedList* node = BuyNewNode(data);
node->next = pos->next;
pos->next = node;
七、删除指定结点之后的一个结点
接口设计成删除指定位置结点之后的结点,原因同上。使用时同样也需要配合查找接口的调用
需要注意的是,pos不能为尾结点,需要加断言判断
void LinkedListPopAfter(LinkedList* pos, LDataType data);
void LinkedListPopAfter(LinkedList* pos, LDataType data)
assert(pos);
assert(pos->next != NULL);
LinkedList* next = pos->next;
pos->next = next->next;
free(next);
next = NULL;
八、修改结点值
该接口也需要配合查找接口的使用
void LinkedListModify(LinkedList* pos, LDataType data);
void LinkedListModify(LinkedList* pos, LDataType data)
assert(pos);
pos->data = data;
九、链表的销毁
void LinkedListDestroy(LinkedList** pplist);
void LinkedListDestroy(LinkedList** pplist)
LinkedList* cur = *pplist;
while (cur != NULL)
LinkedList* next = cur->next;
free(cur);
cur = next;
*pplist = NULL;
以上是关于基于C语言的单链表实现的主要内容,如果未能解决你的问题,请参考以下文章