链表
1.链表的概念及结构
概念:连表示一种屋里存储结构上的非连续,非吮吸的储存结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
物理上:1.链式结构在逻辑上是连续的,但在物理上不一定连续。
2.现实中的节点一般都是从对上申请出来的。
3.从对上申请空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。
2.链表的分类
1.单向或者双向
2.带头或者不带头
3.循环或者非循环
共计8种
最常用的两种链表:无头单向非循环链表 和 带头双向循环链表
1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际更多是作为其他数据结构的子结构,如:哈希桶,图的邻链表。另外在笔试中出现很多。
2.带头双向循环链表:结构最复杂,一般用于单独存储数据。实际中使用的链表结构,都涉及带头双向循环链表。另外,这个结构看似复杂,实际使用代码实现会带来很多优势,实现反而更简单。
3.链表的实现
无头单向非循环链表
建立结构体变量
- typedef int SLDataType;//方便更改数据类型
- typedef struct SList
- {
- SLDataType data;
- struct SList* next;//节点,存下一个链表的地址。
- }SList;
链表不需要单独封装一个函数初始化
1.链表打印
基本思路:1)断言指针
2)为空直接打印NULL
3)创建临时变量,判断条件 cur!=NULL
- void SListPrint(SList* phead)
- {
- if (phead == NULL)
- printf("NULL\n");
- else
- {
- SList* cur = phead;//一般情况下不要动头节点。
- while (cur != NULL)//为空结束循环
- {
- printf("%d->", cur->data);
- cur = cur->next;
- }
- printf("NULL\n");
- }
- }
2.创建一个新的节点
后续插入数据都需要创建新新节点,独立封装一个函数是最优解。
基本思路:1)malloc节点,判断节点是否成功创建,否就直接终止程序。
2)结构体成员初始化。
- SList* BuySListNode(SLDataType x)
- {
- //创建一个节点
- SList* tmp = (SList*)malloc(sizeof(SList));
- if (tmp == NULL)
- {
- printf("malloc fail\n");
- exit(-1);//直接退出程序
- }
- else
- {
- SList* newnode = tmp;
- //初始化
- newnode->data = x;
- newnode->next = NULL;
- return newnode;
- }
- }
2.尾插数据
基本思路:1)创建一个新节点
2)传递指针要传递二级指针,创建变量创建的是结构体指针,修改数据要在地址上修改。
3)判断链表是否为空,为空直接赋值,不为空找到为节点,链接上即可。
配图:
代码实现:
- void SListPushBack(SList** pphead, SLDataType x)
- {
- //不需要断言。
- //创建一个节点。
- SList* newnode = BuySListNode(x);
- //如果为空,直接赋值
- if (*pphead == NULL)
- {
- *pphead = newnode;
- }
- else
- {
- //遍历链表,找到尾节点
- SList* tail = *pphead;
- while (tail->next != NULL)//下一个节点为空,上一个节点就是尾节点。
- {
- tail = tail->next;//找下一个节点
- }
- tail->next = newnode;
- //新节点初始化时指针是指NULL,可以不用二次链接
- }
- }
3.头插数据
基本思路:1)创建新节点
2)判断空与非空,是否需要额外讨论。
画图:
1)非空
2)空
两者无区别
代码:
- void SListPushFront(SList** pphead, SLDataType x)
- {
- SList* newnode = BuySListNode(x);
- //plist非空
- newnode->next = *pphead;
- *pphead = newnode;
- //为空时不需要额外讨论,画图解释。
- }
4.尾删数据
基本思路:1)断言
2)链表为空,直接返回
3)找到尾节点,free掉
i.只有2个节点,尾删就是置空头节点。
ii.多个节点,采用双指针的方法prev,tail
画图:
1)链表为空 return;
2)链表有2个节点
3)链表有多个节点
prev->next = tail->next(NULL);
free(tail);
代码:
- void SListPopBack(SList** pphead)
- {
- assert(pphead);
- //空节点
- if (*pphead == NULL)
- {
- return;
- }
- else if ((*pphead)->next == NULL)
- {
- free(*pphead);
- *pphead = NULL;
- }
- //多个节点
- else
- {
- SList* tail = (*pphead)->next;
- SList* prev = *pphead;
- while (tail->next != NULL)
- {
- tail = tail->next;
- prev = prev->next;
- }
- free(tail);
- prev->next = NULL;
- }
- }
5.头删数据
基本思路:1)修改传递二级指针
2)如果是空节点直接返回
3)优良二或多个节点,创建一个next指针,保存头节点的
下一个节点,free掉头节点,将头节点换成next
画图:
当只有一个节点的时候,next= NULL,改变与多个节点的情况相同,不需要额外讨论。
代码:
- void SListPopFront(SList** pphead)
- {
- assert(pphead);
- //空节点
- if (*pphead == NULL)
- {
- return;
- }
- else
- {
- //两个或多个节点
- SList* next = (*pphead)->next;
- free(*pphead);
- *pphead = next;
- }
- }
6.查找x
基本思路:1)断言,防止传递空指针
2)遍历查找x,找到返回节点,找不到返回NULL
实现代码:
- SList* SListFind(SList* phead, SLDataType x)
- {
- assert(phead);
- SList* cur = phead;
- while (phead != NULL)
- {
- if (cur->data == x)
- {
- return cur;
- }
- else
- cur = cur->next;
- }
- return NULL;
- }
7.在pos后插入x
基本思路:1)断言pos,以防传递空指针
2)创建新的节点newnode
3)进行插入的两种思路
思路一:创建临时变量tmp 保存pos->next节点,随便链接即可。
思路二:直接链接,但是要注意从后往前链接,防止出现连接未完成,pos找不到原先的pos->next。
- void SistInsertAfter(SList* pos, SLDataType x)
- {
- assert(pos);
- 方法一:
- //SList* tmp = pos->next;//保存节点,以防止pos指向原先的节点失效,引发bug
- //SList* newnode = BuySListNode(x);//插入就要创建新的节点
- //pos->next = newnode;//pos链接新的节点
- //newnode->next = tmp;//实现插入
-
- //方法二:
- SList* newnode = BuySListNode(x);
- newnode->next = pos->next;
- pos->next = newnode;
-
- }
8.在pos前插入x
基本思路:1)断言pos;
2)创建新的节点
3)分情况讨论
i若是pos指向的是头节点,那就调用头插函数接口。
ii一般情况,创建一个cur指针变量,找到pos前的节点,进行插入,与7相似的处理手段。
实现代码:
- void SListInsertBefore(SList** pphead,SList* pos, SLDataType x)
- {
- assert(pos);
- SList* newnode = BuySListNode(x);
- if (pos == *pphead)
- {
- SListPushFront(pphead,x);
- }
- else
- {
- SList* cur = *pphead;
- while (cur->next != pos)
- {
- cur = cur->next;
- }
- cur->next = newnode;
- newnode->next = pos;
- }
- }
9.删除pos后的节点
基本思路:1)断言pos
2)创建临时节点next,记录pos->next,即要free的节点
3)进行链接pos 和 next->next
4)坑点,若pos后面为空,那么就会出现next->next访问出错,就需要处理下。
使用if语句进行处理。
画图:
代码实现:
- void SListPopAfter(SList* pos)
- {
- assert(pos);
- if (pos->next != NULL)//pos后为NULL就不执行代码
- {
- SList* next = pos->next;
- pos->next = next->next;
- free(next);
- next = NULL;
- }
- /*else
- printf("pos已经是尾节点了\n");*///单纯测试代码
- }
10.删除pos前的节点
基本思路:1)断言
2)分情况讨论:
i.pos如果是头节点,直接返回
ii.如果是两个节点,那就直接调用头删函数解决
iii.如果是多个节点,创建cur节点,cur->next->next为pos时,就是要找的cur
free节点。
3)由于使用一般情况的节点数必须超过3个,所以<3的就需要额外讨论。
画图:
i
ii
iii
代码实现:
- void SListPopBefore(SList* pos,SList** pphead)
- {
- assert(pos);
- //一个
- if (pos == *pphead)
- {
- return;
- }
- else if ((*pphead)->next == pos)
- {
- SListPopFront(pphead);
- }
- else
- {//多个
- SList* cur = *pphead;
- while (cur->next->next != pos)
- {
- cur = cur->next;
- }
-
- SList* prev = cur->next;
- cur->next = pos;
- free(prev);
- prev = NULL;
- }
- }
11.删除pos位置的值
基本思路:1)断言pos和pphead
2)分类讨论
i如果pos指向的是头节点,那就调用头删函数
ii创建临时变量cur遍历链表,找到pos前的节点,记为cur,链接cur和pos->next
free掉pos
画图:
代码实现:
- void SListPopPos(SList** pphead, SList* pos)
- {
- assert(pphead&&pos);
- if (pos == *pphead)
- {
- SListPopFront(pphead);
- }
- else
- {
- SList* cur = *pphead;
- while (cur->next != pos)//找到pos前的节点
- {
- cur = cur->next;
- }
- cur->next = pos->next;//链接
- free(pos);//释放pos
- pos = NULL;
- }
- }
12.删除链表
基本思路:1)遍历链表,一个节点一个节点的删
2)free前创建临时变量next记录cur->next,循环条件就是cur!=NULL
3)头节点最后置空
- void SListDestroy(SList** pphead)
- {
- SList* cur = *pphead;
- while (cur)//一个节点一个节点的删
- {
- SList* next = cur->next;
- free(cur);//释放
- cur = next;//循环调整
- }
- *pphead = NULL;
- }