当前位置:   article > 正文

【数据结构】单链表基本操作:查找、插入、删除、创建_链表查找

链表查找

链表的存储结构

 链表由结点组成,结点由数据域和指针域组成。其中,数据域存放的就是数据元素,指针域存放下一个结点的地址。数据元素可以只有一个,也可以有多个不同类型的数据元素,甚至是数组。下图和代码来自《C Primer Plus》,该链表每个节结点同时含char类型和int类型。

  1. #define TSIZE 45
  2. struct film{
  3. char title[TSIZE];
  4. int rating;
  5. struct film *next;//指针域
  6. };
  7. struct film head;//头指针

​​​​​​在上述表示中,头指针存储第一个结构的地址。头指针指向链表中的第一项。在上述的表示方式中,结构体的名字是struct film,每次使用该类型的指针,都要写全这个名字。

struct film *p=(struct film*)malloc(sizeof(struct film));//调用malloc,为新结构和新指针分配空间

事实上, 在以后的数据结构学习中,我们常用的是下面这种形式:

  1. tyoedef int ElemType;
  2. typedef struct LNode{
  3. ElemType data;
  4. struct LNode *next;
  5. }LNode,*LinkList;
  6. LNode L;
  7. LinkList p=(LinkList)malloc(sizeof(LNode));

在这里,L是单链表的名字,p是一个同类型指针,但还未定义它指向谁。

虽然LNode和*LinkList是等价的,但还是常用上述表示方式。在某些编译器里,不这样表示可能会报错。

在后续学习中,头结点,头指针,第一个结点等概念也略有变化。以往,我们设L为LinkList型的变量,则L为单链表的头指针,它指向表中的第一个结点。若L为空,则所表示的线性表为空表,长度为0 。此后,在单链表的第一个结点前设一个结点,称之为头结点头结点的指针域存储第一个结点的存储位置(即指向第一个结点的指针)。单链表的头指针指向头结点。若头结点的指针域为空,则单链表为空表。若单链表名为L,L就是头指针head,在不同参考资料中可能用L,也可能用head。下图是博主用ppt画的,上为空表,下为非空表。

单链表基本操作

遍历输出

在正式开始之前,我们不妨先熟悉一下单链表的遍历本身。

  1. void DisplayLink(LinkList L)
  2. {
  3. LinkList p;
  4. p=L;//链表名L,L也是头指针的名字,指向头结点。头结点的指针域指向第一个结点(即p->next)
  5. while(p!=NULL)//注意一下代码的简洁易懂。p!=NULL比!(p=NULL)常用
  6. {
  7. printf(%d\n",&p->data);
  8. p=p->next;//不能用p++,因为链表不是连续存储的
  9. }
  10. }

注意,遍历本身是不需要分配空间的,不需要调用malloc函数。LinkList类型的指针p本身就是用来指向LinkList类型结构体的。p指向需要插入链表中的新结点时,需要给p分配空间。

按位序查找

顺序表按位序查找比较简单,直接引用L.elem[i]即可。但链表查找必须从头开始

  1. Status ListLocate(LinkList L,int i,ElemType &e)
  2. {
  3. int j;
  4. LinkList p;//同理遍历,不要给p分配动态存储空间
  5. p=L->next;//p存储第一个结点的地址
  6. j=1;//(j=0)
  7. while(p&&j<i)//(j<i-1)
  8. {
  9. p=p->next;//循环结束时,p存储第i个结点的地址
  10. j++;
  11. }
  12. if(!p||j>i) return ERROR;//i>表长||空表
  13. e=p->data;
  14. printf("第%d位是元素%d\n", i, e);
  15. return OK;
  16. }

在本节学习中,Status型函数比较常用,因为可以对操作失败做出返回值。你也可以用bool代替。

按值查找

  1. Status QueryNode(LinkList L,ElemType x)
  2. {
  3. LinkList p;int i=0;
  4. p=L->next;
  5. while(p!=NULL)
  6. {
  7. if(p->data=x)
  8. {
  9. i++;
  10. printf("元素%d在链表第%d位\n",x,i);
  11. return OK;
  12. }
  13. p=p->next;
  14. }
  15. return ERROR;
  16. }

删除

链表的删除和插入原理相同,重点都在于不要丢失结点地址

  1. Status DeletList(LinkList &L,int i,ElemType &e)//删除第i个结点
  2. {
  3. LinkList p,q;//q指向第i个结点。循环结束时,p指向第i-1个结点
  4. p=L;//p存储头结点的地址
  5. int j=0;
  6. while(p->next&&j<i-1)
  7. {
  8. p=p->next;
  9. i++;
  10. }
  11. if(!p->next||j>i-1) return ERROR;
  12. //*****************************
  13. q=p->next;
  14. p->next=q->next;//第i-1个结点的指针域指向第i+1个结点
  15. //*****************************
  16. e=q->data;//用e存放q的数据域
  17. free(q);//释放第i个结点
  18. return OK;
  19. }

插入

  1. Status ListInsert(LinkList &L,int i,ELemType e)//在第i个结点前插入新结点
  2. {
  3. LinkList p=L;
  4. LinkList q;
  5. int j=0;
  6. while(p&&j<i-1)
  7. {
  8. p=p->next;
  9. j++;//循环结束时,p指向第i-1个结点
  10. }
  11. if(!p||j>i) return ERROR;
  12. else
  13. {
  14. q=(LinkList)malloc(sizeof(LNode));
  15. q->next=p->next;
  16. p->next=q;
  17. p->data=e;
  18. }
  19. return OK;
  20. }

注意在第i位后插入元素的while循环条件不是while(p->next&&j<i-1),而是while(p&&j<i-1),因为尾结点之后也可以插入新结点,操作和其他结点是一样的。如一个长度为12的链表,可在其第13位前插入。

创建

头插法

每个新结点都是头结点的新直接后继。

由图知,头插法的输入顺序为倒序

关键步骤:1.初始化一个空表 2.for循环逐个插入结点

  1. //关键步骤
  2. p->next=L->next;
  3. L->next=p;
  1. void CreatList(LinkList &L,int n)//L为链表名,n为表长
  2. {
  3. L=(LinkList)malloc(sizeof(LNode));//定义指向L的头指针
  4. L->next=NULL;//初始化L为空表
  5. for(int i=n;i>0;--i)
  6. {
  7. LinkList p=(LinkList)malloc(sizeof(LNode));//随用随分配
  8. scanf("%d",&p->data);
  9. p->next=L->next;
  10. L->next=p;
  11. }
  12. }

for(i=n;i>0;i--)和for(i=n;i>0;--i)都表示递减循环,即从n开始,每次递减1,直到i小于等于0为止。

区别在于循环体中对i的更新位置不同。在for(i=n;i>0;i--)中,i表示先使用i的当前值进行循环体操作,然后再将i减1;而在for(i=n;i>0;--i)中,--i表示先将i减1,然后再使用新值进行循环体操作。

尾插法

每个新结点都是尾结点的新直接前驱

它的思路是遍历链表,将新的结点插入到链表的尾部,即在原有链表的最后一个结点之后插入新的结点。这种方法可以保持原链表的相对顺序不变。

具体步骤如下:

  1. 创建一个新的结点q,将待插入的值赋给q的data域。
  2. 如果链表为空,即头结点为NULL,将q赋给头结点的next域,即头结点指向q。
  3. 否则,遍历链表,找到链表的最后一个结点。
  4. 将q插入到最后一个结点的后面,即将最后一个结点的next指针指向q。
  5. 更新链表的最后一个结点为q,即将q赋给最后一个结点。

这样,每次插入新的结点时,都会保持链表的相对顺序不变,并且新的结点都会插入到链表的尾部。

  1. void CreateList(LinkList& L, int n) {//尾插法
  2. // 正序输入 n 个数据元素,建立带头结点的单链表
  3. LinkList p, q; int i;
  4. L = (LinkList)malloc(sizeof(LNode));
  5. q = L; // 先建立一个带头结点和尾指针的单链表
  6. for (i = 0; i < n; ++i) {
  7. p = (LinkList)malloc(sizeof(LNode));
  8. scanf_s("%d", &p->data); // 输入元素值
  9. q->next = p;
  10. q = p;
  11. } //
  12. q->next = NULL; //修改尾指针
  13. }

一道题目

题干

不妨用下面这道题串联上述操作,顺便练习C语言菜单的建立。

主函数中实现下列操作(蓝色部分用函数实现)

  1. 头插法创建n个随机元素的单链表
  2. 输出单链表
  3. 输入要查找的位序i,调用函数,按位序查找单链表,输出位序为i的元素值
  4. 输入要插入的元素f和要插入的位置,调用插入函数,输出更新后的链表
  5. 输入要删除的元素位置,调用按位序删除函数,先输出删除的元素值,再输出更新后的链表

菜单

C语言菜单是一种通过控制台界面为用户提供选择功能的方式。通常使用switch语句和循环来实现菜单的选择和功能执行的过程。通过用户输入的选择执行对应的功能函数。在每个功能函数内部,你可以编写你想要执行的具体功能代码。若当用户输入某个特定值时,返回上一级菜单,则实现了多级菜单。

本题用到的菜单比较简单,博主采用了最基本的if...else if...语句。

源代码

源代码如下。编译器vs20

一点细节:博主认为输出查找结果的printf语句放在主函数里比放在查找函数里方便。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. constexpr auto ERROR = 0;
  4. constexpr auto OK = 1;
  5. typedef int ElemType;
  6. typedef int Status;
  7. typedef struct node {
  8. ElemType data;
  9. struct node* next;
  10. }LNode, * LinkList;
  11. void DisplayLink(LinkList L)//输出链表
  12. {
  13. LinkList p;
  14. p = L->next;//链表名L,L也是头指针的名字,指向头结点。头结点的指针域指向第一个结点(即p->next)
  15. while (p!=NULL)
  16. {
  17. printf("%d\n", p->data);
  18. p = p->next;//不能用p++,因为链表不是连续存储的
  19. }
  20. }
  21. Status ListLocate(LinkList L, int i,ElemType& e)//按位序查找
  22. {
  23. LinkList p;
  24. p = L->next;
  25. int j = 1;
  26. while (p!=NULL&&j<i)
  27. {
  28. p = p->next;
  29. j++;
  30. }
  31. if (!p||j>i) return ERROR;
  32. e = p->data;
  33. return OK;
  34. }
  35. Status DeletList(LinkList& L, int i, ElemType& e)//删除第i个结点
  36. {
  37. LinkList p, q; int j;
  38. p = L; j = 0;
  39. while (p->next&&j<i-1)//循环结束时,p指向第i-1个结点
  40. {
  41. p = p->next;
  42. j++;
  43. }
  44. if (!(p->next )|| j > i - 1) return ERROR;
  45. q = p->next;
  46. p->next = q->next;
  47. e = q->data;
  48. free(q);
  49. return OK;
  50. }
  51. Status ListInsert(LinkList &L, ElemType e, int i)//在第i个结点前插入
  52. {
  53. LinkList p, q;
  54. int j = 0;
  55. p = L;
  56. while (p&&j < i - 1)
  57. {
  58. p = p->next;
  59. j++;
  60. }
  61. if (!p || j > i - 1) return ERROR;
  62. q = (LinkList)malloc(sizeof(LNode));
  63. q->data = e;
  64. q->next = p->next;
  65. p->next = q;
  66. return OK;
  67. }
  68. void ListCreate(LinkList& L, int n)//头插法:输入顺序为倒序
  69. {
  70. LinkList p;
  71. L = (LinkList)malloc(sizeof(LNode));
  72. L->next= NULL;
  73. printf("输入元素:\n");
  74. for(int i=0;i<n;i++)
  75. {
  76. p = (LinkList)malloc(sizeof(LNode));
  77. scanf_s("%d",&p->data);
  78. p->next = L->next;
  79. L->next = p;
  80. }
  81. }
  82. int select(void)//菜单选择函数
  83. {
  84. int s;
  85. printf("查找请按1\n");
  86. printf("删除请按2\n");
  87. printf("插入请按3\n");
  88. scanf_s("%d", &s);
  89. return s;
  90. }
  91. int main(void)
  92. {
  93. LinkList A;
  94. int a; printf("请输入链表长度:\n"); scanf_s("%d", &a);
  95. ListCreate(A, a);
  96. printf("您已创建链表如下:\n");
  97. DisplayLink(A);
  98. printf("查找请按1\n删除请按2\n插入请按3\n退出请按0\n");
  99. int x;//由用户输入,控制小while循环
  100. int b;//要查找、删除第几位
  101. int c;//返回第几位的值
  102. x = 6;
  103. while (x)//x的初始值为除1、2、3之外的非0数
  104. {
  105. scanf_s("%d", &x);
  106. if (x == 1)
  107. {
  108. printf("要查找第几位\n"); scanf_s("%d", &b);
  109. ListLocate(A, b, c);
  110. printf("第%d位是数字%d\n", b, c);
  111. printf("查找请按1\n删除请按2\n插入请按3\n退出请按0\n");
  112. }
  113. else if (x == 2)
  114. {
  115. printf("要删除第几位\n"); scanf_s("%d", &b);
  116. DeletList(A, b, c);
  117. printf("您已删除第%d位的值%d\n", b, c);//如果删除失败,输出的还是原表
  118. DisplayLink(A);
  119. printf("查找请按1\n删除请按2\n插入请按3\n退出请按0\n");
  120. }
  121. else if (x == 3)
  122. {
  123. printf("在第几个结点前插入\n");
  124. scanf_s("%d", &b);
  125. printf("插入元素:\n");
  126. scanf_s("%d", &c);
  127. ListInsert(A, c,b);
  128. printf("您已在%d位前插入%d\n", b, c);//如果插入失败,输出的还是原表
  129. DisplayLink(A);
  130. printf("查找请按1\n删除请按2\n插入请按3\n退出请按0\n");
  131. }
  132. else printf("重新输入\n");
  133. }
  134. printf("退出循环,操作结束\n");
  135. return 0;
  136. }

运行结果

其一

其二

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/444187
推荐阅读
相关标签
  

闽ICP备14008679号