当前位置:   article > 正文

【数据结构】第三节:单链表

【数据结构】第三节:单链表

前言 

本篇要求掌握的C语言基础知识:指针、结构体

目录

前言 

单链表

概念

对比链表和顺序表

创建链表

实现单链表

准备工作

 打印链表

 创建节点并初始化

尾插

二级指针的调用

尾插代码 

头插

尾删

头删

查找(返回节点) 

 在指定位置(pos)之前插入数据

在指定位置(pos)之后插入数据

删除pos节点

删除pos之后的节点 

销毁链表


单链表

概念

        链表是⼀种物理存储结构上⾮连续⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

对比链表和顺序表

顺序表

        1) 占用一大片连续内存空间

        2) 不需要额外空间存储逻辑关系,总空间需求最少

        4) 可顺序访问,支持随机访问

        5) 在C语言中,通过数组实现

        6) 数据元素的插入和删除操作通过移动元素完成

链表

        1) 不要求占用连续内存空间

        2) 不仅要存储数据,还要存储数据之间的关系,故总空间需求较大

        3) 通过指针反映逻辑关系

        4) 逻辑连续,物理可不连续

        5) 只可顺序访问,不支持随机访问

        6) 存在标记:头指针

        7) 数据元素的插入和删除操作通过修改指针完成:定位插入点/删除点的直接前驱/后

        从上文可以得知与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/节点” ,节点的组成主要有两个部分:当前节点要保存的数据和保存下⼀个节点的地址(指针变量)。

创建链表

  1. //创建节点
  2. typedef int SLTDataType;
  3. typedef struct SLNode
  4. {
  5. SLTDataType data;//数据域
  6. struct SLNode* next;//指针域
  7. }SLTNode;
  8. //创建节点
  9. SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
  10. node1->data = 1;
  11. SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
  12. node2->data = 2;
  13. SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
  14. node3->data = 3;
  15. SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
  16. node4->data = 4;
  17. //链接节点
  18. node1->next = node2;
  19. node2->next = node3;
  20. node3->next = node4;
  21. node4->next = NULL;//尾指针置空

        其中数据域用于存放数据,指针域用于存放下一个结点的地址。上面的链表是手动创建节点,只是为了展示链表的形成,后续创建和链接单链表可以通过函数实现。

实现单链表

准备工作

在工程中一共包含三个文件

  • 定义文件SLNode.h:定义函数和结构体,头文件:stdio.h、stdlib.h、assert.h
  • 实现文件SLNode.c:实现函数具体功能,头文件:SLNode.h
  • 测试文件test.c:测试每一部分代码的正确性,头文件:SLNode.h

        在开始之前我们需要定义一个指向为空的结构体类型的节点(SLNode*)plist,作为链表的头节点。

SLNode* plist = NULL;

 打印链表

  1. //打印
  2. void SLTprint(SLTNode* phead)
  3. {
  4. SLNode* pcur = phead;
  5. while (pcur != NULL)
  6. {
  7. printf("%d ", pcur->data);
  8. pcur = pcur->next;
  9. }
  10. printf("\n");
  11. }

 创建节点并初始化

  1. //创建节点并初始化
  2. SLNode* SLTbuyNode(SLTDataType x)
  3. {
  4. SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));//创建新节点
  5. if (newnode == NULL)
  6. {
  7. perror("malloc fail!");
  8. exit(1);//表示非正常退出
  9. }
  10. newnode->data = x;
  11. newnode->next = NULL;
  12. return newnode;
  13. }

尾插

二级指针的调用

        从这一部分开始就涉及到了二级指针传参的问题,在对单链表进行尾插时,如果此时头节点plist指向为空(即该单链表为空),就需要在函数内部改变头指针的指向,指向新插入的节点。

        这里举一个简单的例子,假如我要实现一个交换两个整形数据的函数,应该如何实现?

  1. void Exchange(int a,int b)
  2. {
  3. int tmp=a;
  4. a=b;
  5. tmp=b;
  6. }

        如果仅仅将两个整形作为参数是无法成功的,因为在主函数中调用Exchange时在栈帧中又开辟了一块地址不同于主函数的函数栈帧,以上"传值调用"仅仅将形参里的内容进行交换,在函数执行结束时所占据的空间会被释放,同时形参也会因为被销毁而无法对实参产生影响。

        如果想要"形参影响实参",就要把"传值调用"改为"传址调用",即将变量的地址作为参数传给函数,对应的函数参数应为指针类型。

  1. void Exchange(int* a,int* b)
  2. {
  3. int* tmp=*a;
  4. *a=*b;
  5. *tmp=*b;
  6. }

        这样就实现了交换两个数据的操作。

        同理,想要在函数内部改变一级头指针plist的指向,应该把plist的地址传入,用二级指针接收,也就是"传址调用",如果只传递一级指针(即链表的头指针),无法直接修改它所指向的地址,因为在函数内部对指针的修改不会影响到函数外部,最终只是将形参指针的指向改变而无法对实参造成影响。为了实现对链表头指针的修改,需要传递指向指针的指针,这样在函数内部就可以修改指针所指向的地址,从而改变链表的头指针。

 来一张图解释二级指针

总结:只要头指针发生改变就需要用到二级指针

尾插代码 

  1. void SLTpushBack(SLTNode** pphead, SLTDataType x)
  2. {
  3. assert(pphead);
  4. SLTNode* newnode = SLTbuyNode(x);
  5. if (*pphead == NULL)//链表为空
  6. {
  7. *pphead = newnode;
  8. }
  9. else
  10. {
  11. SLNode* ptail = *pphead;
  12. while (ptail->next != NULL)//遍历链表找到尾节点
  13. {
  14. ptail = ptail->next;
  15. }
  16. ptail->next = newnode;
  17. }
  18. }

头插

       与尾插同理,头指针的指向发生改变,需要借助二级指针

  1. void SLTpushFront(SLTNode** pphead, SLTDataType x)
  2. {
  3. assert(pphead);
  4. SLTNode* newnode = SLTbuyNode(x);
  5. newnode->next = *pphead;//*pphead是指向第一个节点的指针
  6. *pphead = newnode;
  7. }

尾删

  1. void SLTpopBack(SLTNode** pphead)
  2. {
  3. assert(pphead && *pphead);//*pphead为空说明整个链表为空
  4. if ((*pphead)->next == NULL)//链表中只有一个节点
  5. {
  6. free(*pphead);
  7. *pphead = NULL;
  8. }
  9. else
  10. {
  11. SLTNode* ptail = *pphead;
  12. SLTNode* prev = *pphead;
  13. while (ptail->next != NULL)
  14. {
  15. prev = ptail;//prev指向的是尾节点的前一个节点
  16. ptail = ptail->next;
  17. }
  18. free(ptail);
  19. prev->next = NULL;//prev成为新的尾节点
  20. ptail = NULL;
  21. }
  22. }

头删

  1. void SLTpopFront(SLTNode** pphead)
  2. {
  3. assert(pphead && *pphead);
  4. if ((*pphead) == NULL)
  5. {
  6. free(*pphead);
  7. *pphead = NULL;
  8. }
  9. else
  10. {
  11. SLTNode* p = *pphead;//此时p指向的是头节点
  12. *pphead = (*pphead)->next;
  13. free(p);
  14. p = NULL;
  15. }
  16. }

查找(返回节点) 

  1. SLNode* SLTfind(SLTNode* phead, SLTDataType x)
  2. {
  3. assert(phead);
  4. SLNode* pcur = phead;
  5. while (pcur != NULL)
  6. {
  7. if (pcur->data == x)
  8. {
  9. return pcur;
  10. }
  11. pcur = pcur->next;
  12. }
  13. return NULL;//没有找到返回NULL
  14. }

 在指定位置(pos)之前插入数据

  1. void SLTinsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
  2. {
  3. assert(pphead && *pphead && pos);
  4. SLTNode* pcur = *pphead;
  5. SLNode* newnode = SLTbuyNode(x);
  6. if (pos == *pphead)
  7. {
  8. SLTpushFront(pphead, x);
  9. }
  10. else
  11. {
  12. while (pcur->next != pos)//遍历到pos节点的前驱节点
  13. {
  14. pcur = pcur->next;
  15. }
  16. newnode->next = pos;
  17. pcur->next = newnode;
  18. }
  19. }

在指定位置(pos)之后插入数据

  1. void SLTinsertAfter(SLTNode* pos, SLTDataType x)
  2. {
  3. assert(pos);
  4. SLNode* newnode = SLTbuyNode(x);
  5. if (pos->next == NULL)//如果pos是尾节点
  6. {
  7. pos->next = newnode;
  8. newnode->next = NULL;
  9. }
  10. else
  11. {
  12. SLNode* pafter = pos->next;//pcur是pos的后继节点
  13. newnode->next = pafter;
  14. pos->next = newnode;
  15. }
  16. }

         在这里不调用二级指针的原因是头指针无需改变,需要改变的时pos节点内部next指针的指向,而对于next指针来说,pos指向的时next所在的节点,所以pos可以直接访问这个黑点,从而改变next的指向,换句话pos相对于next来说就是二级指针

删除pos节点

  1. void SLTerase(SLTNode** pphead, SLTNode* pos)
  2. {
  3. assert(*pphead && pos && pphead);
  4. if (pos->next == NULL)//如果pos是尾节点
  5. {
  6. SLTNode* prev = *pphead;
  7. while (prev->next != pos)
  8. {
  9. prev = prev->next;
  10. }
  11. prev->next = NULL;
  12. free(pos);
  13. pos = NULL;
  14. }
  15. else if (*pphead == pos)//如果pos是头节点
  16. {
  17. SLTNode* next = (*pphead)->next;
  18. free(*pphead);
  19. (*pphead) = next;
  20. }
  21. else
  22. {
  23. SLTNode* prev = *pphead;
  24. while (prev->next != pos)
  25. {
  26. prev = prev->next;
  27. }
  28. prev->next = pos->next;
  29. free(pos);
  30. pos = NULL;
  31. }
  32. }

删除pos之后的节点 

  1. void SLTeraseAfter(SLTNode* pos)
  2. {
  3. assert(pos->next && pos);
  4. SLTNode* next = pos->next;
  5. pos->next = pos->next->next;
  6. free(next);
  7. next = NULL;
  8. }

销毁链表

  1. void SLTdestroy(SLTNode** pphead)
  2. {
  3. assert(*pphead && pphead);
  4. SLTNode* pcur = *pphead;
  5. while (pcur != NULL)
  6. {
  7. SLTNode* next = pcur->next;
  8. free(pcur);
  9. pcur = next;
  10. }
  11. *pphead = NULL;
  12. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/516339
推荐阅读
相关标签
  

闽ICP备14008679号