数据结构与算法02--链表基础

Posted lxjshuju

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法02--链表基础相关的知识,希望对你有一定的参考价值。

1双向线性链表

1.1 问题

双向线性链表是採用链式存储的方式存储的线性表。链式存储结构是由一系列结点(链表中每个元素称为结点)组成,每个结点包含两个部分:一个是存储数据元素的数据域,还有一个是存储当前结点的前驱结点和后继结点地址的指针域,结点是在有数据时动态生成的。是一种物理存储单元上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

1.2 方案

双向线性链表的基本操作包含:

1) 初始化操作,在初始化操作中将双向线性链表的头指针指空。

2) 求表长,求表长操作是计算出双向线性链表中结点的个数。

3) 取双向线性链表中指定位置的结点,即给定双向线性链表中的第几个结点,求其值。

4) 查找结点。在双向线性链表中查找值为x的结点,并返回该结点在双向线性链表中的位置。

若双向线性链表中有多个结点的值和x同样。则返回首次找到的结点位;若双向线性链表中没有结点的值为x,则返回一个NULL表示查找失败。

5) 插入结点,在双向线性链表的第i个结点前插入一个值为x的新结点。

6) 删除结点,删除双向线性链表的第i个结点。

1.3 步骤

实现此案例须要依照例如以下步骤进行。

步骤一:定义双向线性链表

在C语言中:

1)定义一个变量来表示双向线性链表中某个结点的数据。

2)定义两个指针来表示该结点的前驱结点和后继结点的地址。

3)这双方面的信息共同描写叙述一个双向线性链表的结点,可将它们封装在一起。

代码例如以下所看到的:

  1. typedef int DataType;
  2. struct Node{
  3. DataType data;
  4. struct Node *pre, *next;
  5. };

上述代码中,下面代码:

  1. typedef int DataType;

是将数据类型int起了一个别名叫做DataType。并在后面的程序中仅仅使用DataType。而不使用int。

这样做的优点是当双向线性链表中的数据类型发生变化时。仅仅须要改动此句中的int为要改变的数据类型,就可以将程序中全部数据变量的数据类型变成指定的类型。

上述代码中。下面代码:

  1. struct Node *pre, *next;

分别定义了当前结点的前驱指针和后继指针。由于当前结点的前驱结点和后继结点都是双向线性链表的结点数据类型,所曾经驱指针和后继指针也是struct Node *类型。

步骤二:初始化操作

在主程序中。定义双向线性链表的头指针。

在初始化函数中将头指针初始化为NULL。表示为空表。

代码例如以下所看到的:

  1. void init(struct Node** head)
  2. {
  3. *head = NULL;
  4. }
  5. int main()
  6. {
  7. //头指针
  8. struct Node *headList;
  9. init(&headList);
  10. }

步骤三:求表长

求表长即求双向线性链表中结点的个数,结点的个数是通过遍历链表并统计结点个数得到的。

代码例如以下所看到的:

  1. int getSize(struct Node* head)
  2. {
  3. struct Node* p = head;
  4. int count = 0;
  5. while(p)
  6. {
  7. count ++;
  8. p = p->next;
  9. }
  10. return count;
  11. }

步骤四:取链表中指定位置的数据元素

首先,推断要查找的是否为头结点。

然后,遍历链表,确定要查找的结点位置。

代码例如以下:

  1. struct Node* getptr(struct Node* head, int pos) {
  2. struct Node *p = head;
  3. if (p == 0 || pos == 0) {
  4. return head;
  5. }
  6. for(int i = 0; p && i < pos; i++) {
  7. p = p->next;
  8. }
  9. return p;
  10. }

步骤五:插入结点

首先。推断要插入的结点位置是否正确。

然后,创建结点。

最后,插入结点,插入时分两种情况处理:一种是插入到第一个结点前面,还有一种是插入到链表中间。

  1. //指定位置 插入元素
  2. bool insert(struct Node** head, int position, DataType d) {
  3. if (position < 0 || position > getSize(*head)) {
  4. return false;
  5. }
  6. //创建 节点
  7. struct Node *node = (struct Node*)malloc(sizeof(struct Node));
  8. node->data = d;
  9. node->pre = NULL;
  10. node->next = NULL;
  11. //插入到第一个节点的前面
  12. if (position == 0) {
  13. node->next = *head;
  14. if (*head != NULL)
  15. (*head)->pre = node;
  16. *head = node;
  17. return true;
  18. }
  19. //插入到链表的中间
  20. struct Node *p = getptr(*head, position - 1);
  21. struct Node* r = p->next;
  22. node->next = r;
  23. r->pre = node;
  24. p->next = node;
  25. node->pre = p;
  26. return true;
  27. }

上述代码中,下面代码:

  1. if (*head != NULL)
  2. (*head)->pre = node;

是推断头指针是否为空,假设不为空。则头指针指向的结点的前驱指针指向新增加的结点。

上述代码中。下面代码:

  1. node->next = r;
  2. r->pre = node;
  3. p->next = node;
  4. node->pre = p;

node为要插入的新结点,插入到p和r指向的结点之间,所以node的后继结点指针指向r,r的前驱结点指针指向node,node的前驱指针指向p,p的后继指针指向node。

步骤六:删除节点

首先,推断要删除的结点位置是否正确。

然后。删除结点,删除时分两种情况处理:一种是删除第一个结点,还有一种是删除链表的中间结点。

最后,释放被删除结点所占有的存储空间。

代码例如以下:

  1. //删除指定位置元素
  2. bool erases(struct Node** head, int pos) {
  3. if (pos < 0 || pos >= getSize(*head))
  4. return false;
  5. //删除第一个结点
  6. struct Node *p = *head;
  7. if (pos == 0) {
  8. *head = (*head)->next;
  9. if (*head != NULL)
  10. (*head)->pre = NULL;
  11. free(p);
  12. p = NULL;
  13. return true;
  14. }
  15. //删除链表的中间结点
  16. p = getptr(*head, pos - 1);
  17. struct Node *q = p->next;
  18. p->next = q->next;
  19. q->next->pre = p;
  20. free(q);
  21. q = NULL;
  22. return true;
  23. }

上述代码中,下面代码:

  1. if (*head != NULL)
  2. (*head)->pre = NULL;

是删除链表的头结点后。假设头指针不为空,即链表中还有结点。则让新的头结点的前驱指针为空。

上述代码中,下面代码:

  1. p->next = q->next;
  2. q->next->pre = p;

p指向要删除结点的前一结点,q指向要删除的结点,此时仅仅须要让p由指向q,改为指向q的后继结点;让q的后继结点的前驱指针由指向q,改为指向p,就可以将要删除的结点q从链表中断开,从而达到删除的目的。

步骤七:改动结点中数据

首先,推断要改动的结点是否在链表内。

然后,找到要改动的结点的位置。

最后。改动结点的数据。

  1. //改动指定位置 元素
  2. bool set(struct Node* head, int pos, DataType d) {
  3. if (pos < 0 || pos >= getSize(head)) {
  4. return false;
  5. }
  6. struct Node *p = getptr(head, pos);
  7. p->data = d;
  8. return true;
  9. }

步骤八:删除链表

首先,遍历整个链表。

然后,逐个释放链表中每一个结点。

  1. void clears(struct Node* head) {
  2. while (head) {
  3. struct Node *p = head->next;
  4. free(head);
  5. head = p;
  6. }
  7. }

3.4 完整代码

本案例的完整代码例如以下所看到的:

  1. #include <stdio.h>
  2. #include <stdbool.h>
  3. #include <stdlib.h>
  4. typedef int DataType;
  5. struct Node{
  6. DataType data;
  7. struct Node *pre, *next;
  8. };
  9. void init(struct Node** head)
  10. {
  11. *head = NULL;
  12. }
  13. int getSize(struct Node* head)
  14. {
  15. struct Node* p = head;
  16. int count = 0;
  17. while(p)
  18. {
  19. count ++;
  20. p = p->next;
  21. }
  22. return count;
  23. }
  24. //找到指定位置 元素地址
  25. struct Node* getptr(struct Node* head, int pos) {
  26. struct Node *p = head;
  27. if (p == 0 || pos == 0) {
  28. return head;
  29. }
  30. for(int i = 0; p && i < pos; i++) {
  31. p = p->next;
  32. }
  33. return p;
  34. }
  35. //指定位置 插入元素
  36. bool insert(struct Node** head, int position, DataType d) {
  37. if (position < 0 || position > getSize(*head)) {
  38. return false;
  39. }
  40. //创建 节点
  41. struct Node *node = (struct Node*)malloc(sizeof(struct Node));
  42. node->data = d;
  43. node->pre = NULL;
  44. node->next = NULL;
  45. //插入到第一个节点的前面
  46. if (position == 0) {
  47. node->next = *head;
  48. if (*head != NULL)
  49. (*head)->pre = node;
  50. *head = node;
  51. return true;
  52. }
  53. //插入到链表的中间
  54. struct Node *p = getptr(*head, position - 1);
  55. struct Node* r = p->next;
  56. node->next = r;
  57. r->pre = node;
  58. p->next = node;
  59. node->pre = p;
  60. return true;
  61. }
  62. //删除指定位置元素
  63. bool erases(struct Node** head, int pos) {
  64. if (pos < 0 || pos >= getSize(*head))
  65. return false;
  66. //删除第一个结点
  67. struct Node *p = *head;
  68. if (pos == 0) {
  69. *head = (*head)->next;
  70. if (*head != NULL)
  71. (*head)->pre = NULL;
  72. free(p);
  73. p = NULL;
  74. return true;
  75. }
  76. //删除链表的中间结点
  77. p = getptr(*head, pos - 1);
  78. struct Node *q = p->next;
  79. p->next = q->next;
  80. q->next->pre = p;
  81. free(q);
  82. q = NULL;
  83. return true;
  84. }
  85. //改动指定位置 元素
  86. bool set(struct Node* head, int pos, DataType d) {
  87. if (pos < 0 || pos >= getSize(head)) {
  88. return false;
  89. }
  90. struct Node *p = getptr(head, pos);
  91. p->data = d;
  92. return true;
  93. }
  94. //清理 链表
  95. void clears(struct Node* head) {
  96. while (head) {
  97. struct Node *p = head->next;
  98. free(head);
  99. head = p;
  100. }
  101. }
  102. //打印
  103. void print(struct Node* head) {
  104. struct Node *p = head;
  105. while (p) {
  106. printf("%d ", p->data);
  107. p = p->next;
  108. }
  109. printf("\n");
  110. }
  111. int main()
  112. {
  113. //头指针
  114. struct Node *headList;
  115. init(&headList);
  116. insert(&headList, 0, 10);
  117. insert(&headList, 0, 20);
  118. insert(&headList, 0, 30);
  119. insert(&headList, 2, 40);
  120. insert(&headList, 2, 50);
  121. insert(&headList, 0, 60);
  122. insert(&headList, 0, 80);
  123. print(headList);
  124. erases(&headList, 1);
  125. print(headList);
  126. set(headList, 0, 100);
  127. set(headList, 0, 110);
  128. print(headList);
  129. return 0;
  130. }

4 单向线性链表

4.1 问题

单向线性链表是採用链式存储的方式存储的线性表。

链式存储结构是由一系列结点(链表中每个元素称为结点)组成。每个结点包含两个部分:一个是存储数据元素的数据域,还有一个是存储下一个结点地址的指针域,结点是在有数据时动态生成的,是一种物理存储单元上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

4.2 方案

链表的基本操作包含:

1) 初始化操作。在初始化操作中将单向线性链表的头指针指空。

2) 求表长,求表长操作是计算出单向线性链表中结点的个数。

3) 取单向线性链表中指定位置的结点。即给定链表中的第几个结点,求其值。

4) 查找结点。在单向线性链表中查找值为x的结点,并返回该结点在单向线性链表中的位置。若单向线性链表中有多个结点的值和x同样,则返回首次找到的结点位。若单向线性链表中没有结点的值为x,则返回一个NULL表示查找失败。

5) 插入结点。在单向线性链表的第i个结点前插入一个值为x的新结点。

6) 删除结点。删除单向线性链表的第i个结点。

4.3 步骤

实现此案例须要依照例如以下步骤进行。

步骤一:定义单向线性链表

在C语言中:

1)定义一个变量来表示单向线性链表中某个结点的数据。

2)定义一个指针来表示该结点的后继结点的地址。

3)这双方面的信息共同描写叙述一个单向线性链表的结点。可将它们封装在一起。

代码例如以下所看到的:

  1. typedef int DataType;
  2. struct Node{
  3. DataType data;
  4. struct Node *next;
  5. };

上述代码中,下面代码:

  1. typedef int DataType;

是将数据类型int起了一个别名叫做DataType,并在后面的程序中仅仅使用DataType。而不使用int。这样做的优点是当链表中的数据类型发生变化时。仅仅须要改动此句中的int为要改变的数据类型。就可以将程序中全部数据变量的数据类型变成指定的类型。

步骤二:初始化操作

在主程序中,定义链表的头指针。

在初始化函数中将头指针初始化为NULL。表示为空表。

代码例如以下所看到的:

  1. void init(struct Node** head)
  2. {
  3. *head = NULL;
  4. }
  5. int main()
  6. {
  7. //头指针
  8. struct Node *headList;
  9. init(&headList);
  10. }

步骤三:求表长

求表长即求链表中结点的个数。结点的个数是通过遍历链表并统计结点个数得到的。

代码例如以下所看到的:

  1. int getSize(struct Node* head)
  2. {
  3. struct Node* p = head;
  4. int count = 0;
  5. while(p)
  6. {
  7. count ++;
  8. p = p->next;
  9. }
  10. return count;
  11. }

步骤四:取链表中指定位置的数据元素

首先,推断要查找的是否为头结点。

然后。遍历链表,确定要查找的结点位置。

代码例如以下:

  1. struct Node* getptr(struct Node* head, int pos) {
  2. struct Node *p = head;
  3. if (p == 0 || pos == 0) {
  4. return head;
  5. }
  6. for(int i = 0; p && i < pos; i++) {
  7. p = p->next;
  8. }
  9. return p;
  10. }

步骤五:插入结点

首先。推断要插入的结点位置是否正确。

然后,创建结点。

最后,插入结点。插入时分两种情况处理:一种是插入到第一个结点前面,还有一种是插入到链表中间。

  1. bool insert(struct Node** head, int position, DataType d) {
  2. if (position < 0 || position > getSize(*head)) {
  3. return false;
  4. }
  5. //创建 节点
  6. struct Node *node = (struct Node*)malloc(sizeof(struct Node));
  7. node->data = d;
  8. node->next = NULL;
  9. //插入到第一个节点的前面
  10. if (position == 0) {
  11. node->next = *head;
  12. *head = node;
  13. return true;
  14. }
  15. //插入到链表的中间
  16. struct Node *p = getptr(*head, position - 1);
  17. struct Node* r = p->next;
  18. node->next = r;
  19. p->next = node;
  20. return true;
  21. }

步骤六:删除节点

首先。推断要删除的结点位置是否正确。

然后,删除结点,删除时分两种情况处理:一种是删除第一个结点。还有一种是删除链表的中间结点。

最后,释放被删除结点所占有的存储空间。

代码例如以下:

  1. bool erases(struct Node** head, int pos) {
  2. if (pos < 0 || pos >= getSize(*head))
  3. return false;
  4. struct Node *p = *head;
  5. if (pos == 0) {
  6. *head = (*head)->next;
  7. free(p);
  8. p = NULL;
  9. return true;
  10. }
  11. p = getptr(*head, pos - 1);
  12. struct Node *q = p->next;
  13. p->next = q->next;
  14. free(q);
  15. q = NULL;
  16. return true;
  17. }

步骤七:改动结点中数据

首先,推断要改动的结点是否在链表内。

然后,找到要改动的结点的位置。

最后。改动结点的数据。

  1. bool set(struct Node* head, int pos, DataType d) {
  2. if (pos < 0 || pos >= getSize(head)) {
  3. return false;
  4. }
  5. struct Node *p = getptr(head, pos);
  6. p->data = d;
  7. return true;
  8. }

步骤八:删除链表

首先,遍历整个链表。

然后。逐个释放链表中每一个结点。

  1. void clears(struct Node* head) {
  2. while (head) {
  3. struct Node *p = head->next;
  4. free(head);
  5. head = p;
  6. }
  7. }

4.4 完整代码

本案例的完整代码例如以下所看到的:

  1. #include <stdio.h>
  2. #include <stdbool.h>
  3. #include <stdlib.h>
  4. typedef int DataType;
  5. struct Node{
  6. DataType data;
  7. struct Node *next;
  8. };
  9. void init(struct Node** head)
  10. {
  11. *head = NULL;
  12. }
  13. int getSize(struct Node* head)
  14. {
  15. struct Node* p = head;
  16. int count = 0;
  17. while(p)
  18. {
  19. count ++;
  20. p = p->next;
  21. }
  22. return count;
  23. }
  24. //找到指定位置 元素地址
  25. struct Node* getptr(struct Node* head, int pos) {
  26. struct Node *p = head;
  27. if (p == 0 || pos == 0) {
  28. return head;
  29. }
  30. for(int i = 0; p && i < pos; i++) {
  31. p = p->next;
  32. }
  33. return p;
  34. }
  35. //指定位置 插入元素
  36. bool insert(struct Node** head, int position, DataType d) {
  37. if (position < 0 || position > getSize(*head)) {
  38. return false;
  39. }
  40. //创建 节点
  41. struct Node *node = (struct Node*)malloc(sizeof(struct Node));
  42. node->data = d;
  43. node->next = NULL;
  44. //插入到第一个节点的前面
  45. if (position == 0) {
  46. node->next = *head;
  47. *head = node;
  48. return true;
  49. }
  50. //插入到链表的中间
  51. struct Node *p = getptr(*head, position - 1);
  52. struct Node* r = p->next;
  53. node->next = r;
  54. p->next = node;
  55. return true;
  56. }
  57. //删除指定位置元素
  58. bool erases(struct Node** head, int pos) {
  59. if (pos < 0 || pos >= getSize(*head))
  60. return false;
  61. struct Node *p = *head;
  62. if (pos == 0) {
  63. *head = (*head)->next;
  64. free(p);
  65. p = NULL;
  66. return true;
  67. }
  68. p = getptr(*head, pos - 1);
  69. struct Node *q = p->next;
  70. p->next = q->next;
  71. free(q);
  72. q = NULL;
  73. return true;
  74. }
  75. //改动指定位置 元素
  76. bool set(struct Node* head, int pos, DataType d) {
  77. if (pos < 0 || pos >= getSize(head)) {
  78. return false;
  79. }
  80. struct Node *p = getptr(head, pos);
  81. p->data = d;
  82. return true;
  83. }
  84. //清理 链表
  85. void clears(struct Node* head) {
  86. while (head) {
  87. struct Node *p = head->next;
  88. free(head);
  89. head = p;
  90. }
  91. }
  92. //打印
  93. void print(struct Node* head) {
  94. struct Node *p = head;
  95. while (p) {
  96. printf("%d ", p->data);
  97. p = p->next;
  98. }
  99. printf("\n");
  100. }
  101. int main()
  102. {
  103. //头指针
  104. struct Node *headList;
  105. init(&headList);
  106. insert(&headList, 0, 10);
  107. insert(&headList, 0, 20);
  108. insert(&headList, 0, 30);
  109. insert(&headList, 2, 40);
  110. insert(&headList, 2, 50);
  111. insert(&headList, 0, 60);
  112. insert(&headList, 0, 80);
  113. print(headList);
  114. erases(&headList, 0);
  115. print(headList);
  116. set(headList, 0, 100);
  117. set(headList, 0, 110);
  118. print(headList);
  119. return 0;
  120. }

















以上是关于数据结构与算法02--链表基础的主要内容,如果未能解决你的问题,请参考以下文章

一篇解单链表(0基础看)(C语言)《数据结构与算法》

02_数据结构与算法之链表

02_数据结构与算法之链表

数据结构与算法 —— 链表linked list(02)

尚硅谷算法与数据结构学习笔记02 -- 单链表

数据结构与算法 Linux内核中双向链表的实现基础