当前位置:   article > 正文

C语言实现链表的基本操作(超详细注释)

C语言实现链表的基本操作(超详细注释)

第一次学数据结构的时候,c语言基础实在太差,在尝试用c语言来实现的时候一直碰壁,数据结构书里的代码是伪代码,使用的时候各种内存访问冲突,各种锟斤拷烫烫烫,各种变量级别不同。于是我又重新学了一遍c语言,并且又看了一本详细阐述c语言指针有关的书,终于明白了,以前为什么出那么多错。之前一直出错的本质就是没有理解指针。学完了以后,现在已经可以成功使用C语言实现数据结构中的算法了。分享一下我的学习成果,希望和我一样的人也能少走弯路。

我想在函数内完成我想要的操作,之前一直用的是一级指针,可是每次创建完的链表都会内存访问冲突。后来经过深入的学习,我发现了这个错误的本质和c语言中经典的用swap函数交换两个变量的值到主函数中没有交换的错误是一样的。然后我就采用了二级指针,解决了这个问题。

代码中LinkList *Head等价于LNode **Head,
LinkList head等价于LNode *head。
为了便于区分我将二级指针中的头节点的首字母进行了大写。
 

下面是头文件“链表.h”,主要放了链表结构的声明以及相关函数的声明。

  1. //蔚明
  2. //链表.h
  3. /*我想在函数内完成我想要的操作,之前一直用的
  4. 是一级指针,可是每次创建完的链表都会内存访问
  5. 冲突。后来经过深入的学习,我发现了这个错误的
  6. 本质和c语言中经典的用swap函数交换两个变量的值
  7. 到主函数中没有交换的错误是一样的。然后我就采用
  8. 了二级指针,解决了这个问题,代码中多次用到二级指
  9. 针。
  10. 代码中LinkList *Head等价于LNode **Head,
  11. LinkList head等价于LNode *head。
  12. 为了便于区分我将二级指针中的头节点的首字母进行了大写。*/
  13. #ifndef 链表_H
  14. #define 链表_H
  15. #pragma warning(disable:6031)//在visual studio中使用字符串函数,输入函数时
  16. #pragma warning(disable:4996)//会报错(C4996,C6031),使用这两行代码以后可以正常使用。
  17. //也可以"字符串函数名_s()"的方式使用函数,但需要更多参数。
  18. #include "stdio.h"
  19. #include "stdlib.h"
  20. #define OK 0
  21. #define FAIL -1
  22. #define ENTER printf("\n");
  23. typedef int Elemtype;//可将"int"更换为任意类型变量。
  24. typedef struct LNode//定义一个链表的结构类型。
  25. {
  26. Elemtype data; //结点的数据域。
  27. struct LNode* next; //结点的指针域。
  28. }LNode, * LinkList;
  29. int Init_List(LNode** L);//使用这个函数来初始化一个节点。运行成功返回OK。
  30. int Assign_List(LNode** L);//为结点数据域赋值,成功返回OK,此函数可按需改变。
  31. void Get_List(LNode* p, Elemtype* e);//取结点p的数据域。此函数可按需改变
  32. void Print_List(Elemtype e);//打印数据域内容。
  33. int Creat_List(LinkList* Head);//使用这个函数来创造一个链表,运行成功返回OK.
  34. void Read_List(LinkList head);//遍历链表
  35. LNode* Search_List(LinkList head, Elemtype e);//在链表中查找第一个含有元素e的节点,并返回这个结点的地址。
  36. int Insert_List(LinkList* Head, Elemtype e);//在第一个含有元素e的结点后面插入一个新的结点,成功返回OK。
  37. int Delete_List(LinkList* Head, Elemtype e);//删除第一个含有元素e的节点。
  38. void Destroy_List(LinkList* Head);//销毁整个链表。
  39. #endif

在编写这个代码时,注释中有此函数可按需更改。意思是更改这个函数。可以对不同的数据域进行操作。以便于快速的修改整个程序代码。创造不同的链表的目的。

下面是源文件“链表.c”,主要放了链表相关函数的定义。

  1. //蔚明
  2. //链表.c
  3. /*在编写这个代码时,注释中有此函数可按需更改。
  4. 意思是更改这个函数可以对不同的数据域进行操作。
  5. 以便于快速的修改整个程序代码。创造不同的链表的目的。*/
  6. #include "链表.h"
  7. int Init_List(LNode** L)//使用这个函数来初始化一个节点。运行成功返回OK。
  8. {
  9. if ((*L = (LNode*)malloc(sizeof(LNode))) == NULL) //为结点分配内存
  10. {
  11. puts("内存分配失败!!!");
  12. exit(1);
  13. }
  14. else return OK;
  15. }
  16. int Assign_List(LNode** L)//为结点数据域赋值,成功返回OK,此函数可按需改变
  17. {
  18. Elemtype data;
  19. puts("请为结点赋值");
  20. scanf("%d", &data);
  21. (*L)->data = data;
  22. return OK;
  23. }
  24. void Get_List(LNode* p, Elemtype* e)//取结点p的数据域。
  25. {
  26. *e = p->data;
  27. }
  28. void Print_List(Elemtype e)//打印数据域内容。此函数可按需改变
  29. {
  30. printf("%d ", e);
  31. }
  32. int Creat_List(LinkList* Head) //使用这个函数来创造一个链表,运行成功返回OK.
  33. {
  34. puts("正在创建一个链表。");
  35. //创建一个只有头结点的空链表。
  36. if (Init_List(Head) != OK) exit(1);
  37. (*Head)->next = NULL;
  38. LNode* r = *Head; //尾指针初始化并指向头结点。
  39. char over_symbol = '#'; //结束创建的标志。
  40. while (1==1)
  41. {
  42. //生成新的结点p。
  43. LNode* p;
  44. if (Init_List(&p) != OK) exit(1); //初始化结点p。
  45. Assign_List(&p); //为新结点数据域赋值。
  46. p->next = NULL;
  47. r->next = p; //把新结点插到尾结点之后。
  48. r = p; //尾指针指向新结点。
  49. //如果没有getchar()吃掉回车,会直接跳过下面的scanf()函数。
  50. puts("结束请输入:over,否则请按<Enter>");
  51. getchar();//吃掉回车。
  52. scanf("ove%c", &over_symbol); //当输入"over"时跳出循环。
  53. getchar();//吃掉回车。
  54. if (over_symbol == 'r') break;//判断是否满足结束条件。
  55. }
  56. puts("创建成功");
  57. return OK;
  58. }
  59. void Read_List(LinkList head)//遍历链表
  60. {
  61. LNode* read = head->next; //定义一个读取数据域的指针。
  62. Elemtype e; //定义一个保存数据域的变量
  63. //寻链向下进行操作。
  64. while (read != NULL)
  65. {
  66. Get_List(read, &e); //把结点数据域内容读到变量e
  67. Print_List(e); //打印数据域内容
  68. read = read->next;
  69. }
  70. ENTER;
  71. }
  72. LNode* Search_List(LinkList head, Elemtype e) //在链表中查找第一个含有元素e的节点,并返回这个结点的地址。没找到返回NULL。
  73. {
  74. LNode* read = head->next; //定义一个读取数据域的指针。
  75. while (read != NULL) //在到达尾结点之前一直循环。
  76. {
  77. if (read->data == e) return read; //如果找到就返回,否则寻链向下。
  78. else read = read->next;
  79. }
  80. return NULL; //到了为结点还是没找到,返回NULL。
  81. }
  82. int Insert_List(LinkList* Head, Elemtype e) //在第一个含有元素e的结点后面插入一个新的结点,成功返回OK.
  83. {
  84. puts("\n正在插入一个结点");
  85. LNode* read = (*Head)->next; //定义一个读取数据域的指针。
  86. LNode* pre_read = *Head; //定义一个指向读取数据域的指针的前驱的指针。
  87. LNode* temp = NULL;
  88. //查找需要操作的结点。
  89. while (read != NULL) //在到达尾结点之前一直循环。
  90. {
  91. if (read->data == e) break; //如果找到就返回,否则寻链向下。
  92. else
  93. {
  94. read = read->next;
  95. pre_read = pre_read->next;
  96. }
  97. }
  98. if (read == NULL) return FAIL; //到了为结点还是没找到,返回。
  99. temp = read; //使用temp临时保存要 操作的结点
  100. LNode* p = NULL; //生成新的结点p。
  101. if (Init_List(&p) != OK) exit(1); //初始化结点p。
  102. Assign_List(&p); //为新结点数据域赋值。
  103. p->next = temp; //将新结点与原结点后继相连。
  104. pre_read->next = p;//把原结点的前驱的后继与新结点相连。
  105. puts("插入成功");
  106. return OK;
  107. }
  108. int Delete_List(LinkList* Head, Elemtype e) //删除第一个含有元素e的节点,成功返回OK。
  109. {
  110. puts("\n正在删除一个结点");
  111. LNode* read = (*Head)->next; //定义一个读取数据域的指针。
  112. LNode* pre_read = *Head; //定义一个指向读取数据域的指针的前驱的指针。
  113. LNode* temp = NULL;
  114. //查找需要操作的结点。
  115. while (read != NULL) //在到达尾结点之前一直循环。
  116. {
  117. if (read->data == e) break; //如果找到就返回,否则寻链向下。
  118. else
  119. {
  120. read = read->next;
  121. pre_read = pre_read->next;
  122. }
  123. }
  124. if (read == NULL) return FAIL; //到了为结点还是没找到,返回。
  125. temp = read->next; //使用temp临时保存要 操作的结点的后继。
  126. free(read); //释放结点的空间。
  127. pre_read->next = temp; //把此结点前驱和后继相连。
  128. puts("删除成功");
  129. return OK;
  130. }
  131. void Destroy_List(LinkList* Head) //销毁整个链表。
  132. {
  133. puts("\n正在销毁一个链表");
  134. LNode* p = *Head; //定义一个访问链表结点的指针。
  135. LNode* temp = p->next; //使用temp临时保存要操作的结点的后继。
  136. //寻链向下,依次释放内存。
  137. do {
  138. free(p); //释放结点的空间。
  139. p = temp; //结点指向它的后继。
  140. temp = p->next;
  141. }
  142. while (temp != NULL);
  143. puts("销毁成功");
  144. return;
  145. }

下面是源文件“main.c”,主函数在这个文件中,主要是测试链表有关函数的功能是否正常。

  1. //蔚明
  2. //main.c
  3. #include"链表.h"
  4. void main()
  5. {
  6. LinkList head = NULL;
  7. Elemtype e = 0;
  8. if (Creat_List(&head) != OK)//创建一个头结点为head的链表。
  9. {
  10. puts("创建链表失败!!!");
  11. exit(1);
  12. }
  13. Read_List(head);//读取链表的数据域并打印。
  14. puts("请输入那个结点前需要插入");
  15. scanf("%d", &e);
  16. if(Insert_List(&head, e) != OK)//在链表中指定的结点前插入元素e
  17. {
  18. puts("插入失败!!!");
  19. exit(1);
  20. }
  21. puts("插入后的链表");
  22. Read_List(head);//读取链表的数据域并打印。
  23. puts("请输入那个结点需要删除");
  24. scanf("%d", &e);
  25. if (Delete_List(&head, e) != OK)
  26. {
  27. puts("删除失败!!!");
  28. exit(1);
  29. }
  30. puts("删除后的链表");
  31. Read_List(head);//读取链表的数据域并打印。
  32. Destroy_List(&head);//释放为这个链表申请的内存。
  33. return;
  34. }

运行结果如下:

我把Elemtype修改成了一个有名字,年龄的结构体变量,然后略微修改了对变量赋值的函数以及查找该变量的条件判断语句,部分修改如下

  1. //Elemtype由int变为如下
  2. typedef struct person
  3. {
  4. char name[10];
  5. int age;
  6. }Elemtype;
  7. //变量的赋值和呈现进行修改
  8. int Assign_List(LNode** L)//为结点数据域赋值,成功返回OK,此函数可按需改变
  9. {
  10. Elemtype data={"",0};
  11. puts("请输入名字");
  12. scanf("%s", data.name);
  13. getchar();
  14. puts("请输入年龄");
  15. scanf("%d", &data.age);
  16. (*L)->data = data;
  17. return OK;
  18. }
  19. void Print_List(Elemtype e)//打印数据域内容。此函数可按需改变
  20. {
  21. printf("姓名:%s,年龄:%d\n", e.name,e.age);
  22. }
  23. //略微修改插入和删除函数中的判断语句
  24. if (read->data == e) break; //改为if (strcmp(read->data.name, e.name)==0) return read;
  25. //略微修改主函数中的赋值语句
  26. scanf("%d",&e);//改为 scanf("%s", e.name);e.age = 0;

得到的运行结果如下

我学C语言大概半年,编写代码的手段可能比较稚嫩,如果写的比较垃圾,欢迎前辈指正。

创作不易,求点赞。

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

闽ICP备14008679号