与数组一样,链表也是线性结构。不同的是,链表元素并不存储在一个连续的位置,元素之前是用指针链接的

  1. +---+---+ +---+---+ +---+------+
  2. | 1 | o---->| 2 | o---->| 3 | NULL |
  3. +---+---+ +---+---+ +---+------+

为什么使用链表?

数组是存储的是同一类型的线性数据,但有以下限制:

  • 数组空间是固定的。
  • 插入数据和删除数据都非常低效。

    链表优于数组的地方

  • 动态大小

  • 插入和删除都非常高效且简单

    链表的缺点

  • 不支持随机访问(random access)。你要访问元素,就必须从第一个结点开始顺序的遍历整个链表。所以二分查找就无法达到随机访问的效果。

  • 列表中的每个元素都要为指针提供额外的空间。
  • 对缓存不友好。因为数组是连续的,因此访问局部性(locality of reference)只存在于数组中,而链表这没有这一特性。

    实现

    链表用指向链表的第一个结点表示。第一个结点叫头指针(head)。如果链表为空,头指针的值为 NULL。
    每个结点至少包含两个部分:

  • 数据(data)

  • 指针(pointer)或引用(reference),用来指向下一结点。

在 C 语言中,可以使用结构来表示一个结点。例如,用 int 表示一个链表结点:

  1. struct Node {
  2. int data;
  3. struct Node* next;
  4. }
  5. // 通常使用 typedef 来少些几个字
  6. typedef struct Node node;

在 Java 或 C# 中,LinkedList 可以表示一个类,Node 可以表示为一个单独的类。LinkedList 中包含 Node 的引用:

  1. class LinkedList {
  2. Node head;
  3. class Node {
  4. int data;
  5. Node next;
  6. Node(int d) {data = d};
  7. }
  8. }

代码实现

用 C 实现一个带有三个结点的链表:

  1. // A simple C program to introduce
  2. // a linked list
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. struct Node {
  6. int data;
  7. struct Node* next;
  8. };
  9. // Program to create a simple linked
  10. // list with 3 nodes
  11. int main()
  12. {
  13. struct Node* head = NULL;
  14. struct Node* second = NULL;
  15. struct Node* third = NULL;
  16. // allocate 3 nodes in the heap
  17. head = (struct Node*)malloc(sizeof(struct Node));
  18. second = (struct Node*)malloc(sizeof(struct Node));
  19. third = (struct Node*)malloc(sizeof(struct Node));
  20. /* Three blocks have been allocated dynamically.
  21. We have pointers to these three blocks as head,
  22. second and third
  23. head second third
  24. | | |
  25. | | |
  26. +---+-----+ +----+----+ +----+----+
  27. | # | # | | # | # | | # | # |
  28. +---+-----+ +----+----+ +----+----+
  29. # represents any random value.
  30. Data is random because we haven’t assigned
  31. anything yet */
  32. head->data = 1; // assign data in first node
  33. head->next = second; // Link first node with
  34. // the second node
  35. /* data has been assigned to the data part of the first
  36. block (block pointed by the head). And next
  37. pointer of first block points to second.
  38. So they both are linked.
  39. head second third
  40. | | |
  41. | | |
  42. +---+---+ +----+----+ +-----+----+
  43. | 1 | o----->| # | # | | # | # |
  44. +---+---+ +----+----+ +-----+----+
  45. */
  46. // assign data to second node
  47. second->data = 2;
  48. // Link second node with the third node
  49. second->next = third;
  50. /* data has been assigned to the data part of the second
  51. block (block pointed by second). And next
  52. pointer of the second block points to the third
  53. block. So all three blocks are linked.
  54. head second third
  55. | | |
  56. | | |
  57. +---+---+ +---+---+ +----+----+
  58. | 1 | o----->| 2 | o---->| # | # |
  59. +---+---+ +---+---+ +----+----+
  60. */
  61. third->data = 3; // assign data to third node
  62. third->next = NULL;
  63. /* data has been assigned to data part of third
  64. block (block pointed by third). And next pointer
  65. of the third block is made NULL to indicate
  66. that the linked list is terminated here.
  67. We have the linked list ready.
  68. head
  69. |
  70. |
  71. +---+---+ +---+---+ +----+------+
  72. | 1 | o----->| 2 | o---->| 3 | NULL |
  73. +---+---+ +---+---+ +----+------+
  74. Note that only head is sufficient to represent
  75. the whole list. We can traverse the complete
  76. list by following next pointers. */
  77. return 0;
  78. }

遍历数组

这里创建一个通用函数(general-purpose function)来遍历链表:

  1. void printList(struct Node* n)
  2. {
  3. while(n != NULL) {
  4. printf("%d ", n->data);
  5. n = n->next;
  6. }
  7. }