上面讲了顺序表后,顺序表的缺陷是什么呢?
1.动态增容有性能消耗
2.需要头部插入数据,需要挪动数据
为什么需要单链表?
鉴于以上两点缺陷,我们引入链表。
链表的概念及结构概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表的逻辑结构如下:
链表结点进行了分层,上层用于存储数据 ; 下层用指针指向下一个结点,以达到连接目的
顺序表VS单链表:
1.顺序表是利用realloc
一次性动态调整出一大块空间,所以这一大块空间中的每个单元,地址都是连续的。
2.单链表每一次都是用的malloc
开辟的一个空间,那么每个空间的地址一定是不一样且不连续的。
废话不多说,用代码实现一下,还是创建三个头文件:
定义单链表结构:
#include<stdio.h> #include<stdlib.h> #include<assert.h> typedef int SLTDataType; //方便以后修改数据类型 struct SListNode { SLTDatType data; struct SListNode* next; }SLNode; //把结构体名改短一点
思考下为何使用typedef?上次讲顺序表就说过,就不再重复了!
单链表的空间开辟:
SLNode* BuySLTNode(SLTDatType val) { SLNode* newnode = (SLNode*)malloc(sizeof(SLNode)); if(newnode == NULL) { perror("错误原因:"); exit(-1); } newnode->data = val; newnode->next = NULL; return newnode; }
单链表的尾插:
完成尾插前先回忆下单链表的结构:
phead
(头指针)指向头结点(第一个结点),之后的每个结点的下层指针next
指向下一个结点,其中尾结点的next
为空.
实现思路:
1.遍历找到最后一个结点(即其next为空)
2.malloc动态开辟一个空间存储数据,然后把新开辟的空间的next
置为空.
3.使用尾结点的next
连接新开辟的空间
下面程序注意一下(错误):
void SListPushBack(SLNode* phead,SLTDataType val) { //第一步:找尾结点, 即cur->next 等于 NULL SLNode* cur = phead; while(cur->next != NULL) //cur用于迭代 { cur = cur->next; } //第二步:开辟新空间 SLNode* newnode = BuySLTNode(val); //第三步:连接 cur->next = newnode; }
上面程序有问题:原因plist
的类型为SLTNode *
,而我们形参类型也是SLTNode *
,这属于值传递,值传递相当于形参是实参的一份临时拷贝,形参的改变并不会影响实参的值。想要修改实参的值就需要进行传址操作,在这里传plist
的地址.形参用二级指针。
还有不要忘了断言。如果phead为空,会引发异常
修改程序后:
void SListPushBack(SLNode** pphead, SLTDataType val) { assert(pphead); //pphead不可以为空指针. if (*pphead == NULL) { *pphead = BuySLTNode(val); } else { //第一步:找尾结点, 即cur->next 等于 NULL SLNode* cur = *pphead; while (cur->next != NULL) //cur用于迭代 { cur = cur->next; } //第二步:开辟新空间 SLNode* newnode = BuySLTNode(val); //第三步:连接 cur->next = newnode; } }
单链表的头插:
这一点要注意:函数想要改变phead的值,所以我们的形参需要二级指针。
创建新节点:
新节点链接原来的头节点:
让phead指针指向新节点:
void SListPushFront(SLNode** pphead,SLTDataType val) { assert(pphead); //第一步,创建新节点 SLNode* newnode = BuySLTNode(val); //第二步,新结点链接原来的头结点 newnode->next = *pphead; //第三步,phead指针指向新节点 *pphead = newnode; }
单链表的尾删:
1.考虑正常情况,如下图。
2.考虑结点数量只有0或1时。
从头开始遍历,找到倒数第二个节点:
然后free掉最后一个节点:
最后将倒数第二个节点的next置NULL:
void SListPopBack(SLNode** pphead) { assert(pphead); //结点数0 assert(*pphead); //结点数1 if((*pphead)->next == NULL)//只有一个结点时 { free(*pphead); *pphead = NULL; return; } //多节点,第一步,找倒数第二个结点 SLNode* cur = *pphead; while (cur->next->next != NULL) { cur = cur->next; } //第二步,free掉最后一个结点 free(cur->next); //第三步,将现结点释放掉,置NULL cur->next = NULL; }
单链表的头删:
1.先把第二个结点的地址记下来。
2.释放第一个结点。
3.phead链接到原来的第二个结点
void SListPopFront(SLNode** pphead) { assert(pphead); //0结点 assert(*pphead); //1结点 if ((*pphead)->next == NULL) { free(*pphead); *pphead = NULL; return; } //多结点,第一步,保留第二个结点地址 SLNode* next = (*pphead)->next; //第二步,释放第一个结点 free(*pphead); //第三步,连接第二个 *pphead = next; }
单链表的长度查找:
这个函数没有修改phead,所以选择值传递,不改变实参。
int SListSize(SLNode* phead) { SLTNode* cur = phead; int size = 0; while(cur->next != NULL) { size++; cur = cur->next; }; return size; }
单链表判空操作:
bool SListEmpty(SLNode* phead) { return phead == NULL; }
单链表的值查找操作:
如果可以找到,就返回那个结点,如果找不到,返回空指针。
SLNode* SListFind(SLNode* phead, SLTDataType val) { SLTNode* cur = phead; while(cur) { if(cur->data==val) { return cur; } cur = cur->next; } return NULL; }
单链表的数据打印:
void SListPrint(SLNode* phead) { SLTNode* cur = phead; while(cur->next != NULL) { printf("%d->",cur->data); } printf("NULL\n"); }
单链表的删除操作:
1.找到目标结点之前位置
2.提前保存目标结点后位置
3.销毁目标结点
4.链接原目标结点之前的位置与原目标结点之后的位置
void SListErase(SLNode** pphead,SLNode* pos) { assert(pphead); //0结点情况 assert(*pphead); //1结点时.相当于头删,直接调用头删. if((*pphead)->next == NULL) { SListPopFront(pphead); } else { SLTNode* cur = *pphead; while(cur->next != pos) { cur = cur->next; } SLNode* next = pos->next; free(pos); cur->next = next; } }
单链表的插入操作:
1.找到目标结点之前结点
2.创建新结点
3.新结点链接目标结点
4.原目标结点之前的结点链接新结点
void SListInsert(SLNode** pphead,SLNode* pos,SLTDataType val) { assert(pphead); assert(pos); //当第一个结点便是目标结点,其实就是头查 if (*pphead== pos) { SListPushFront(pphead,elem); } //当多个结点时 else { SLNode* cur = *pphead; while (cur->next != pos) { cur = cur->next; } SLNode* next = BuySLTNode(val);//创建准备插入的结点 next->next = pos; cur->next = next; } }
单链表的销毁操作:
void SListDestory(SLNode** pphead) { assert(pphead); SLNode* cur = *pphead; while (cur) { SLNode* next = cur->next; free(cur); cur = next; } *pphead = NULL; }
数据结构的单链表内容到此设计结束了,感谢您的阅读!!!
如果内容对你有帮助的话,记得给我三连(点赞、收藏、关注)——做个手有余香的人。感谢大家的支持!!!