用C写一个链表
链表(Linked List)是一种非连续的线性数据结构,相对于数组,它允许数据在内存中非连续存储,但是不支持随机读取。

链表由一个个节点(Node)组成,每个节点除了记录数据以外,还需要记录下一个节点的位置(如果是双向链表,还需要记录上一个节点的位置)
1 2 3 4 5 6 7
| struct _Node; typedef struct _Node Node;
struct _Node{ int data; Node *next; };
|
对于第一个节点,我们有一个指针指向它的地址,对于最后一个节点,它需要指向NULL,表示链表结束了。
1 2 3 4 5
| typedef struct _List { Node *head; Node *tail; int num; } List;
|
有了链表的数据结构后,我们需要定义三个基本函数,用于创建链表,往链表中加入数据和删除链表
创建链表比较简单,就是为链表分配内存,并将其赋值给一个指针,然后返回
1 2 3 4 5 6 7 8
| List *CreateList() { List *list; list = (List*)malloc( sizeof(List) ); list->num = 0; return list; }
|
加入数据时,我们需要先声明两个节点指针,第一个用于记录当前节点的位置,第二个是记录新节点的位置。如果链表中没有节点,也就是head指向为NULL,那么直接插入新节点即可。如果链表中已经有了节点,那么获取最后第一个节点的位置, 然后在它的后面加入节点,同时将tail指向新的节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| bool AddNode(List *list, int data) { Node *node; Node *new_node;
new_node = (Node *)malloc( sizeof(Node) ); if ( new_node == NULL) return false; new_node->data = data; new_node->next = NULL;
node = list->head ; if ( node == NULL){ list->head = new_node; list->tail = new_node; list->num = 1; return true; } node = list->tail ; node->next = new_node; list->tail = new_node; list->num+=1;
return true;
}
|
删除列表分为两步,先删除节点内容,然后删除列表这个结构。如果节点存放的数据是其他结构,那么还需要先删除节点存放的其他数据。
1 2 3 4 5 6 7 8 9 10 11 12
| void DestroyList(List *list) { Node *current; Node *next; current = list->head; while (current->next != NULL){ next = current->next; free(current); current = next; } free(list); }
|
我们还可以定一个输出函数,将链表里存放的数据依次输出
1 2 3 4 5 6 7 8 9
| void dump(List *list){ Node *node; node = list->head; while (node != NULL){ printf("%08d\n", node->data); node = node->next; } }
|
有了上面的基本函数时候,我们就能够读取存放数字的文本,将其加入到链表中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| int main(int argc, char const *argv[]) { if (argc == 1) exit(EXIT_FAILURE);
FILE *fp; fp = fopen(argv[1], "r"); if (fp == NULL){ perror(argv[1]); exit(EXIT_FAILURE); } int data; List *list; list = CreateList(); while (fscanf(fp, "%d", &data) != EOF){ AddNode(list, data); } dump(list) return 0;
|
我们的链表还应该支持插入操作和删除操作。对于插入操作,我们要分为是插入到给定位置前,还是给定位置后。对于删除而言,也就是都是删除当前节点,而为了删除当前节点,我们需要前一个节点的位置。
无论是插入还是删除,我们都需要知道插入的位置和删除的位置,因此我们还需要一个搜索函数,用于搜索等于给定值的节点位置或者是上一个位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
Node *Search(List *list, int data, bool situ) { Node *node; node = list->head; if ( situ ){ while ( node->next != NULL ){ if ( node->data == data) return node; node = node->next; } } else { while ( node->next->next != NULL) { if (node->next->data == data) return node; node = node->next; } } return NULL; }
|
我们先写一个删除操作, 用于删除等于给定的节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| bool DeleteNode( List* list, int data){
Node *node; Node *tmp; node = list->head; if ( node->data == data ){ free(list->head); list->head = NULL; list->tail = list->head; list->num = 0; return true; } node = Search(list, data, false); if ( node == NULL){ return false; } tmp = node->next->next; free(node->next); node->next = tmp; return true; }
|
然后将元素加入函数分为两种,一种是插入(当前位置前),一种是追加(当前位置后)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| bool InsertNode( List* list, int query, int data){
Node *node; Node *new_node;
new_node = (Node *)malloc( sizeof(Node) ); if ( new_node == NULL) return false; new_node->data = data;
node = list->head; if ( node->data == query ){ new_node->next = node->next ; node->next = new_node; return true; }
node = Search(list, query, false); if ( node == NULL){ return false; } new_node->next = node->next ; node->next = new_node;
return true;
}
bool AppendNode( List* list, int query, int data){
Node *node; Node *new_node;
new_node = (Node *)malloc( sizeof(Node) ); if ( new_node == NULL) return false; new_node->data = data;
node = Search(list, query, true); if ( node == NULL){ return false; } new_node->next = node->next; node->next = new_node;
return true;
}
|
进阶操作
上面都是链表的基础操作,创建、摧毁,增加,删除。下面几个则是考验对链表对深刻理解,
- 单链表反转
- 链表中环的检测
- 两个有序链表的合并
- 删除链表倒数第N个结点
- 求链表的中间结点
单链表反转
如果要将单链表进行反转,每次移动的时候需要三个位置,前一个位置,当前位置和head。每次将head向后移动,记录了当前位置的下一个节点,然后将当前位置指向前一个位置。最后将前一个位置和当前位置向后移动。图解如下, 首先head指向链表第一个节点

然后将cur设置到当前的head

接着将head往后移动一个位置, 保存了原本在cur后面的位置

然后将cur指向到res,也就是前面的位置

上面的操作后,就将res和cur的顺序反转了。接着就是将res和cur往后移动

代码为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| List* reverseList(List* list){
Node *curr, *res; res = NULL; curr = list->head; list->tail = list->head; while ( curr ){ list->head = list->head->next; curr->next = res; res = curr; curr = list->head; } list->head = res; return list; }
|
中间节点
为了寻找中间节点,我们可以定义两个指针,快指针和慢指针。慢指针一次一步,快指针一次两步. 如果是偶数,那么快指针最后是NULL,如果是奇数,那么快指针的下一个是NULL。
1 2 3 4 5 6 7 8 9 10 11 12
| Node *FindMidlle(List *list) { if (list->num == 0) return NULL; Node *fast = list->head; Node *slow = list->head; while ( fast != NULL && fast->next != NULL){ slow = slow->next; fast = fast->next->next; } return slow;
}
|
删除倒数第N个指针
同上,也是快慢两个指针,快指针先走N步,然后两个指针再一起走。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| bool RemoveLastN(List *list, int n) { if ( list->num == n){ list->head = list->head->next; return true; } Node *fast = list->head; Node *slow = list->head; Node *tmp; while (n-- > 0){ fast = fast->next; } while (fast->next != NULL){ fast = fast->next; slow = slow->next; } tmp = slow->next; slow->next = slow->next->next; free(tmp); return true; }
|
有序链表合并
假设两个有序链表分别为1->3->5->7->8
,2->3->4->5->8
, 那么合并之后应该是1->2->3->3->4->5->7->8
.
我们需要创建一个新的链表用于存放两个链表排序的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| List *MergeSortedList(List *list_a, List *list_b) { List *list_c; list_c = CreateList(); Node *node_a, *node_b, *node_c; node_a = list_a->head; node_b = list_b->head;
if ( node_a->data < node_b->data ){ list_c->head = node_a; node_a = node_a->next; } else { list_c->head = node_b; node_b = node_b->next;
} node_c = list_c->head; while( true ){ if (node_a->data < node_b->data){ node_c->next = node_a; node_a = node_a->next; if (node_a == NULL) break; } else{ node_c->next = node_b; node_b = node_b->next; if (node_b == NULL) break; } node_c = node_c->next; } while ( node_a != NULL){ node_c->next = node_a; node_a = node_a->next; node_c = node_c->next;
} while ( node_b != NULL){ node_c->next = node_b; node_b = node_b->next; node_c = node_c->next; } return list_c; }
|
为了测试这个代码正确性,我写了一个测试函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| int MergeTest( const char *file1, const char *file2){ FILE *f1; FILE *f2;
int data; f1 = fopen(file1, "r");
List *list1; list1 = CreateList(); while (fscanf(f1, "%d", &data) != EOF){ AddNode(list1, data); } dump(list1); fclose(f1);
f2 = fopen(file2, "r"); if (f2 == NULL){ perror(file2); exit(EXIT_FAILURE); } List *list2; list2 = CreateList();
while (fscanf(f2, "%d", &data) != EOF){ AddNode(list2, data); } dump(list2); fclose(f2);
List *res; res = MergeSortedList(list1, list2); dump(res); return 0;
}
|
最终的代码在GitHub上https://github.com/xuzhougeng/learn-algo/blob/master/link_list.c
LeetCode和链表有关的几个题目