一、链表是什么
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
在逻辑上链表模型就像火车的车厢一样一节一节相连
而实际在内存空间中,链表的每个节点所占内存块可能并不连续
注意:
1.从上图看出,链式结构在逻辑上连续,在物理上不一定连续
2.现实中的结点一般是在堆上申请的
3.从堆上申请的空间,是按照一定策略分配空间的,两次申请的空间可能连续,也可能不连续
假设在32位系统上,结点中值域为int类型 ,则一般一个结点的大小为8个字节,则也可能有下列链表:
二、链表的分类
链表总共靠三种方向区别:
1.单向和双向
2.带头和不带头(头代表哨兵位)
3.循环和不循环
所以链表总共的组合有2x2x2=8种
本文主要学习其中的一种单向链表(单链表),为单向不带头不循环链表
单链表是链表的一种基础数据结构,由首指针和若干个结点以及NULL组成,结点包含结点值和结点指针存放下个节点的地址,首指针指向首结点,再由首结点指向下个结点,依次相接,最后一个节点的指针存放NULL。
注:
之后的代码分析,所参考的原链表都是该图,phead作为首指针实参
该种链表的特点是每个结点中包含自己的结点值和下一个结点的地址(尾结点储存的地址是空),其中也可能是空链表,由phead指向NULL。
三、程序思想
头文件一览
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<assert.h> #include<stdlib.h> 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位置之后的值 void SListEraseAfter(SListNode* pos); // 单链表的销毁 void SListDestroy(SListNode* plist); //单链表在pos位置修改值 void SListChange(SListNode* pos, SLTDateType x);
3.1 链表结点的创建
typedef int SLTDateType; typedef struct SListNode { SLTDateType data;//结点值 struct SListNode* next;//结点指针 }SListNode;
3.2 动态申请一个结点
有了链表结点的创建,我们就需要需要申请结点
// 动态申请一个结点 SListNode* BuySListNode(SLTDateType x) { SListNode* newnode = (SListNode*)malloc(sizeof(SListNode)); if (newnode==NULL) { perror("malloc fail"); exit(-1); } else { newnode->data = x; newnode->next = NULL; return newnode; } }
解析:
动态申请了一块空间,大小为sizeof(SListNode)(即一个结点大小),但是malloc返回的指针是void* ,所以我们要将返回值强制类型转换为SListNode*,再赋给我们创建的结点指针newnode,如果newnode为空,则说明申请空间失败了,这时程序会终止,并输入错误信息,如果进入else,则将我们所给结点值x赋给newnode->data,且使newnode->next指向NULL。
增添功能:
3.3 单链表实现头插
我们在单链表实现头插,其实是在phead的后面,首结点的前面插入结点,物理意义上来讲就是使phead指向插入的结点,而使新结点的结点指针指向一开始的首结点
// 单链表的头插 void SListPushFront(SListNode** pplist, SLTDateType x) { SListNode* newnode = BuySListNode(x); newnode->next = *pplist; *pplist = newnode; }
解析:
这里我们不需要考虑是否是空链表,只要先将phead指向的地址赋给newnode的结点指针即可,然后再使phead指向newnode,但是这里有一个大坑,如果我们传的是一级指针,我们完成函数后会发现链表并没有添加结点,这是因为一级指针改变的结点结构体,而我们这里需要改变的是首指针的地址,改变的是指针,所以需要二级指针的帮助,形参这里传的是phead的地址SListNode** pplist,因此当我们要执行改变首指针操作时,都要传二级指针。
3.4 单链表实现尾插
尾插的物理意义就是原尾结点的结点指针指向新结点,新结点的结点指针指向NULL,如果为空链表,参考头插一样的情况。
// 单链表尾插 void SListPushBack(SListNode** pplist, SLTDateType x) { SListNode* newnode = BuySListNode(x); if (*pplist==NULL) { *pplist = newnode; } else { SListNode* tail = *pplist; while (tail->next) { tail = tail->next; } tail->next = newnode; }
解析:
尾插和头插有一些区别,如果为空链表,执行的操作和头插差不多,但如果不是空链表,就稍微麻烦点,需要我们遍历找到到尾结点, 在执行插入操作
3.5 单链表在pos位置之前插入结点
//单链表在pos之前插入结点 void SListInsert(SListNode** pphead, SListNode* pos, SLTDateType x) { assert(pos); SListNode* newnode = BuySListNode(x); if (pos == *pphead) { SListPushFront(pphead, x); } else { SListNode* cur = *pphead; while (cur->next != pos) { cur = cur->next; if (cur) return; } newnode->next = pos; cur->next = newnode; } }
解析:
这里要断言,pos不应该为空。中间插分两种情况,链表有一个结点或一个以上结点,所以当有一个结点时,就和头插一样了,因为我们已经实现了头插,所以我们直接调用即可。当有两个结点时,我们需要遍历链表,找到pos位置的前驱结点,将前驱结点的结点指针指向newnode,将newnode的结点指针指向pos位置的结点,完成链接,但如果当cur遍历到空时,说明链表遍历完了还没找到pos位置,说明给的pos位置不合理(不存在我们的链表当中),不作操作。
3.6 单链表在pos位置之后插入结点
// 单链表在pos位置之后插入结点 void SListInsertAfter(SListNode* pos, SLTDateType x) { assert(pos); SListNode* newnode = BuySListNode(x); SListNode* cur = pos->next; pos->next = newnode; newnode->next = cur; }
解析:
断言pos,创建插入的结点,将pos位置的结点指针赋给cur,再将tail赋给pos的结点指针 ,最后将cur赋给tail的结点指针。
删除功能
3.7 头删
// 单链表头删 void SListPopFront(SListNode** pplist) { assert(*pplist); SListNode* tail = *pplist; *pplist = tail->next; free(tail); }
解析:
首指针不能为空,所以断言,不为空的话,将首指针拷贝给tail ,再将首结点的结点指针赋给*pplist,然后free掉tail,这里可以省略掉将tail置空的环节,因为tail是一个局部变量,出了函数作用域就销毁了,所以就不用担心变成野指针。
3.8 尾删
// 单链表的尾删 void SListPopBack(SListNode** pplist) { assert(*pplist); if ((*pplist)->next==NULL) { free(*pplist); *pplist = NULL; } else { SListNode* cur = *pplist; while (cur->next->next) { cur= cur->next; } free(cur->next); cur->next = NULL; } }
解析:
断言*pplist,然后分两种情况,如果只有一个结点,直接销毁,然后将首指针置空,如果有一个以上结点,则遍历到尾结点的前一个结点赋给cur,然后free(cur->next),再把cur->next置空。
3.9 单链表删除pos位置后的结点
// 单链表删除pos位置之后的结点 void SListEraseAfter(SListNode* pos) { assert(pos); assert(pos->next); SListNode* cur = pos->next->next; free(pos->next); pos->next = cur; }
解析:
pos不能为空,pos的结点指针也不能为空,断言。将pos的结点指针指向的结点的结点指针赋给cur,然后销毁pos的结点指针指向的结点,再将pos和cur链接。
3.10 单链表删除pos位置的结点
//单链表销毁pos位置的结点 void SListErase(SListNode** pphead, SListNode* pos) { assert(pphead); assert(*pphead); assert(pos); if (*pphead==pos) { SListPopFront(*pphead); } else { SListNode* cur = *pphead; while (cur->next != pos) { cur = cur->next; } cur->next = pos->next; free(pos); } }
解析:
*pphead,pphead,pos不为空,断言。也分两种情况,如果是所销毁的结点是首结点,直接执行头删,如果不是首结点,就遍历到pos位置的前驱结点,赋给cur,再将cur的结点指针指向pos的后继结点,最后销毁pos结点。
查找功能
3.11 查找pos位置的结点
// 单链表查找 SListNode* SListFind(SListNode* plist, SLTDateType x) { SListNode* tail = plist; while (tail) { if (tail->data ==x) return tail; tail = tail->next; } return NULL; }
解析:
我们通过判断结点的结点值是否和我们要找的值相等,来判断是否为我们所寻找的结点,所以遍历一遍链表,当找到第一个结点值为x时,返回该结点的地址,没找到则返回NULL。
修改功能
3.12 修改pos位置的结点
//单链表在pos位置修改值 void SListChange(SListNode* pos,SLTDateType x) { assert(pos); pos->data = x; }
3.13 销毁单链表
// 单链表的销毁 void SListDestroy(SListNode* plist) { if (plist==NULL) return; if (plist->next==NULL) { free(plist); return; } else if (plist->next->next==NULL) { free(plist->next); free(plist); return; } SListNode* cur = plist; SListNode* tail = cur->next->next; while (cur->next->next) { plist = cur->next; cur->next = tail; free(plist); tail = tail->next; } plist = NULL; free(cur->next); free(cur); }
四、执行增删查改
#include"SLT.h" //检查单链表的增添功能 void Test1() { SListNode* plist = NULL; //头插 printf("头插检测\n"); SListPushFront(&plist, 1); SListPushFront(&plist, 2); SListPushFront(&plist, 3); SListPrint(plist); printf("\n\n"); //尾插 printf("尾插检测\n"); SListPushBack(&plist, 4); SListPushBack(&plist, 5); SListPushBack(&plist, 6); SListPrint(plist); printf("\n\n"); //中间插 printf("中间插\n"); SListNode* pos = SListFind(plist, 3); SListInsertAfter(pos, 10); SListInsert(&plist, pos, 100); SListPrint(plist); SListDestroy(plist); printf("\n\n"); } //检查单链表的删除功能 void Test2() { printf("检查删除\n"); SListNode* plist = NULL; SListPushFront(&plist, 1); SListPushFront(&plist, 2); SListPushFront(&plist, 3); SListPushFront(&plist, 5); SListPushFront(&plist, 10); SListPushFront(&plist, 100); SListPrint(plist); printf("\n\n"); //头删 printf("头删\n"); SListPopFront(&plist); SListPrint(plist); printf("\n\n"); //尾删 printf("尾删\n"); SListPopBack(&plist); SListPrint(plist); printf("\n\n"); //中间删 printf("中间删\n"); SListNode* pos = SListFind(plist, 3); SListEraseAfter(pos); SListPrint(plist); printf("\n\n"); SListErase(&plist, pos); SListPrint(plist); SListDestroy(plist); printf("\n\n"); } //检查单链表的查找修改功能 void Test3() { //检查查找修改 printf("查找修改\n"); SListNode* plist = NULL; SListPushFront(&plist, 89); SListPushFront(&plist, 2); SListPushFront(&plist, 3); SListPushFront(&plist, 5); SListPushFront(&plist, 0); SListPushFront(&plist, 99); SListPrint(plist); printf("\n\n"); SListNode* pos = SListFind(plist, 5); SListChange(pos, 5 * 100); SListPrint(plist); printf("\n\n"); SListDestroy(plist); } int main() { Test1(); Test2(); Test3(); return 0; }
五、项目代码汇总
5.1 SLT.h
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<assert.h> #include<stdlib.h> 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 // 分析思考为什么不在pos位置之前插入? void SListInsertAfter(SListNode* pos, SLTDateType x); // 单链表删除pos位置之后的值 // 分析思考为什么不删除pos位置? void SListEraseAfter(SListNode* pos); // 单链表的销毁 void SListDestroy(SListNode* plist); //单链表在pos位置修改值 void SListChange(SListNode* pos, SLTDateType x);
5.2 SLT.c
#include"SLT.h" // 动态申请一个节点 SListNode* BuySListNode(SLTDateType x) { SListNode* newnode = (SListNode*)malloc(sizeof(SListNode)); if (newnode==NULL) { perror("malloc fail"); exit(-1); } else { newnode->data = x; newnode->next = NULL; return newnode; } } // 单链表打印 void SListPrint(SListNode* plist) { while (plist) { printf("%d->", plist->data); plist = plist->next; } printf("NULL\n"); } // 单链表尾插 void SListPushBack(SListNode** pplist, SLTDateType x) { SListNode* newnode = BuySListNode(x); if (*pplist==NULL) { *pplist = newnode; } else { SListNode* tail = *pplist; while (tail->next) { tail = tail->next; } tail->next = newnode; } } // 单链表的头插 void SListPushFront(SListNode** pplist, SLTDateType x) { SListNode* newnode = BuySListNode(x); newnode->next = *pplist; *pplist = newnode; } // 单链表的尾删 void SListPopBack(SListNode** pplist) { assert(*pplist); if ((*pplist)->next==NULL) { free(*pplist); *pplist = NULL; } else { SListNode* cur = *pplist; while (cur->next->next) { cur= cur->next; } free(cur->next); cur->next = NULL; } } // 单链表头删 void SListPopFront(SListNode** pplist) { assert(*pplist); SListNode* tail = *pplist; *pplist = tail->next; free(tail); } // 单链表查找 SListNode* SListFind(SListNode* plist, SLTDateType x) { SListNode* tail = plist; while (tail) { if (tail->data ==x) return tail; tail = tail->next; } return NULL; } // 单链表在pos位置之后插入x void SListInsertAfter(SListNode* pos, SLTDateType x) { assert(pos); SListNode* tail = BuySListNode(x); SListNode* cur = pos->next; pos->next = tail; tail->next = cur; } // 单链表删除pos位置之后的节点 void SListEraseAfter(SListNode* pos) { assert(pos); assert(pos->next); SListNode* cur = pos->next->next; free(pos->next); pos->next = cur; } // // 单链表的销毁 void SListDestroy(SListNode* plist) { if (plist==NULL) return; if (plist->next==NULL) { free(plist); return; } else if (plist->next->next==NULL) { free(plist->next); free(plist); return; } SListNode* cur = plist; SListNode* tail = cur->next->next; while (cur->next->next) { plist = cur->next; cur->next = tail; free(plist); tail = tail->next; } plist = NULL; free(cur->next); free(cur); } //单链表在pos之前插入x void SListInsert(SListNode** pphead, SListNode* pos, SLTDateType x) { assert(pos); SListNode* newnode = BuySListNode(x); if (pos == *pphead) { SListPushFront(pphead, x); } else { SListNode* cur = *pphead; while (cur->next != pos) { cur = cur->next; if (cur) return; } newnode->next = pos; cur->next = newnode; } } //单链表销毁pos位置的节点 void SListErase(SListNode** pphead, SListNode* pos) { assert(pphead); assert(*pphead); assert(pos); if (*pphead==pos) { *pphead = pos->next; free(pos); } else { SListNode* cur = *pphead; while (cur->next != pos) { cur = cur->next; } cur->next = pos->next; free(pos); } } //单链表在pos位置修改值 void SListChange(SListNode* pos,SLTDateType x) { assert(pos); pos->data = x; }
5.3 Test.c
#include"SLT.h" //检查单链表的增添功能 void Test1() { SListNode* plist = NULL; //头插 printf("头插检测\n"); SListPushFront(&plist, 1); SListPushFront(&plist, 2); SListPushFront(&plist, 3); SListPrint(plist); printf("\n\n"); //尾插 printf("尾插检测\n"); SListPushBack(&plist, 4); SListPushBack(&plist, 5); SListPushBack(&plist, 6); SListPrint(plist); printf("\n\n"); //中间插 printf("中间插\n"); SListNode* pos = SListFind(plist, 3); SListInsertAfter(pos, 10); SListInsert(&plist, pos, 100); SListPrint(plist); SListDestroy(plist); printf("\n\n"); } //检查单链表的删除功能 void Test2() { printf("检查删除\n"); SListNode* plist = NULL; SListPushFront(&plist, 1); SListPushFront(&plist, 2); SListPushFront(&plist, 3); SListPushFront(&plist, 5); SListPushFront(&plist, 10); SListPushFront(&plist, 100); SListPrint(plist); printf("\n\n"); //头删 printf("头删\n"); SListPopFront(&plist); SListPrint(plist); printf("\n\n"); //尾删 printf("尾删\n"); SListPopBack(&plist); SListPrint(plist); printf("\n\n"); //中间删 printf("中间删\n"); SListNode* pos = SListFind(plist, 3); SListEraseAfter(pos); SListPrint(plist); printf("\n\n"); SListErase(&plist, pos); SListPrint(plist); SListDestroy(plist); printf("\n\n"); } //检查单链表的查找修改功能 void Test3() { //检查查找修改 printf("查找修改\n"); SListNode* plist = NULL; SListPushFront(&plist, 89); SListPushFront(&plist, 2); SListPushFront(&plist, 3); SListPushFront(&plist, 5); SListPushFront(&plist, 0); SListPushFront(&plist, 99); SListPrint(plist); printf("\n\n"); SListNode* pos = SListFind(plist, 5); SListChange(pos, 5 * 100); SListPrint(plist); printf("\n\n"); SListDestroy(plist); } int main() { Test1(); Test2(); Test3(); return 0; }