一、链表的概念
链表是由若干个结点组成,且结点在内存中的存储位置通常是不连续的。除此之外,链表的两个结点之间一般通过一个指针来从一个结点指向另一个结点。定义如下:
1 #define typename int 2 // 链表结构 3 struct node { 4 typename data; 5 node* next; 6 };
二、使用 malloc 函数为链表结点分配内存空间
1. malloc 函数
malloc 函数是 C 语言中 stdlib.h 头文件下用于申请动态内存的函数,其返回类型是申请同变量变型的指针,其基本用法如下:
typename* p = (typename*)malloc(sizeof(typename));
那么创建一个链表结点的代码如下:
node* p = (node*)malloc(sizeof(node));
2. free 函数
free 函数对应 malloc 函数,同样是在 stdlib.h 头文件下。其使用方法如下:
free(p);
三、链表的基本操作
1. 创建链表
根据数组创建链表,代码如下:
1 /* 2 链表处理 3 */ 4 5 #include6 #include <string.h> 7 #include 8 #include 9 #include 10 #include 11 12 #define typename int 13 // 链表结构 14 typedef struct _node { 15 typename data; 16 struct _node* next; 17 } node; 18 19 /* 20 // 分配内存 21 typename* p = (typename*)malloc(sizeof(typename)); 22 node* p = (node*)malloc(sizeof(node)); 23 free(p); 24 */ 25 26 // 创建链表 27 node* create(int Array[], int n) { 28 node *p, *pre, *head; // 当前结点,前置节点,头结点 29 head = (node*)malloc(sizeof(node)); // 创建头结点 30 head->next = NULL; 31 pre = head; // 记录 pre 为 head 32 int i; 33 for(i=0; i i) { 34 p = (node*)malloc(sizeof(node)); // 新建结点 35 p->data = Array[i]; // 赋值 36 p->next = NULL; 37 pre->next = p; // 尾插法 38 pre = p; 39 } 40 return head; 41 } 42 43 int main() { 44 int Array[5] = {5, 3, 6, 1, 2}; 45 node* L = create(Array, 5); 46 L = L->next; // 第一个结点 47 while(L != NULL) { // 顺序输出 48 printf("%d ", L->data); 49 L = L->next; 50 } 51 52 return 0; 53 }
2. 查找元素
只需从第一个结点开始,不断判断当前结点的数据域是否等于 x,如果等于,那么就给计数器 count 加 1。这样当到达链表结尾时,count 的值就是链表中元素 x 的个数。代码如下:
1 // 在以 head 为头结点的链表上计数元素 x 的个数 2 int search(node* head, int x) { 3 int count = 0; // 计数 4 node* p = head->next; // 从第一个结点开始 5 while(p != NULL) { 6 if(p->data == x) { // 当前结点数据域为 x 7 count ++; 8 } 9 p = p->next; // 移动到下一个结点 10 } 11 return count; 12 }
3. 插入元素
在指定元素插入元素,代码如下:
1 // 将 x 插入以 head 为头结点的链表的第 pos 个位置上 2 void insert(node* head, int pos, int x) { 3 node* p = head; 4 int i; 5 for(i=0; i1; ++i) { // 指针移到第 pos-1 个位置 6 p = p->next; 7 } 8 node* q = (node*)malloc(sizeof(node)); // 新结点 9 q->data = x; 10 q->next = p->next; // 插入新结点 11 p->next = q; 12 }
4. 删除元素
对链表来说,删除元素是指删除链表上所有值为给定的数 x 的结点。代码如下:
1 // 删除以 head 为头结点的链表中所有数据域为 x 的结点 2 void del(node* head, int x) { 3 node* p = head->next; // 从第一个结点开始遍历 4 node* pre = head; // pre 为 p 的前驱结点 5 while(p != NULL) { 6 if(p->data == x) { // 当前结点数据域为 x 7 pre->next = p->next; // 删除当前结点 8 free(p); 9 p = pre->next; // 更新当前结点 10 } else { // 当前结点数据域不是 x 11 pre = p; // p pre 同时后移 12 p = p->next; 13 } 14 } 15 }
完整 C 代码如下:
1 /* 2 链表处理 3 */ 4 5 #include6 #include <string.h> 7 #include 8 #include 9 #include 10 #include 11 12 #define typename int 13 // 链表结构 14 typedef struct _node { 15 typename data; 16 struct _node* next; 17 } node; 18 19 /* 20 // 分配内存 21 typename* p = (typename*)malloc(sizeof(typename)); 22 node* p = (node*)malloc(sizeof(node)); 23 free(p); 24 */ 25 26 // 创建链表 27 node* create(int Array[], int n) { 28 node *p, *pre, *head; // 当前结点,前置节点,头结点 29 head = (node*)malloc(sizeof(node)); // 创建头结点 30 head->next = NULL; 31 pre = head; // 记录 pre 为 head 32 int i; 33 for(i=0; i i) { 34 p = (node*)malloc(sizeof(node)); // 新建结点 35 p->data = Array[i]; // 赋值 36 p->next = NULL; 37 pre->next = p; // 尾插法 38 pre = p; 39 } 40 return head; 41 } 42 43 // 在以 head 为头结点的链表上计数元素 x 的个数 44 int search(node* head, int x) { 45 int count = 0; // 计数 46 node* p = head->next; // 从第一个结点开始 47 while(p != NULL) { 48 if(p->data == x) { // 当前结点数据域为 x 49 count ++; 50 } 51 p = p->next; // 移动到下一个结点 52 } 53 return count; 54 } 55 56 // 将 x 插入以 head 为头结点的链表的第 pos 个位置上 57 void insert(node* head, int pos, int x) { 58 node* p = head; 59 int i; 60 for(i=0; i 1; ++i) { // 指针移到第 pos-1 个位置 61 p = p->next; 62 } 63 node* q = (node*)malloc(sizeof(node)); // 新结点 64 q->data = x; 65 q->next = p->next; // 插入新结点 66 p->next = q; 67 } 68 69 // 删除以 head 为头结点的链表中所有数据域为 x 的结点 70 void del(node* head, int x) { 71 node* p = head->next; // 从第一个结点开始遍历 72 node* pre = head; // pre 为 p 的前驱结点 73 while(p != NULL) { 74 if(p->data == x) { // 当前结点数据域为 x 75 pre->next = p->next; // 删除当前结点 76 free(p); 77 p = pre->next; // 更新当前结点 78 } else { // 当前结点数据域不是 x 79 pre = p; // p pre 同时后移 80 p = p->next; 81 } 82 } 83 } 84 85 // 顺序输出以 head 为头结点的链表 86 void print(node* head) { 87 node* p = head->next; // 第一个结点 88 while(p != NULL) { // 顺序输出 89 printf("%d ", p->data); 90 p = p->next; 91 } 92 printf("\n"); 93 } 94 95 int main() { 96 int Array[10] = {5, 3, 6, 5, 4, 3, 5, 5, 1, 2}; 97 node* L = create(Array, 10); 98 print(L); 99 insert(L, 3, 5); // 在第 3 位插入 5 100 print(L); 101 del(L, 3); // 删除数据域为 3 的结点 102 print(L); 103 printf("count = %d\n", search(L, 5)); 104 105 return 0; 106 }
四、静态链表
静态链表的实现原理是 hash,即通过建立一个结构体数组,并令数组的下标直接表示结点的地址,来达到直接访问数组中的元素就能访问结点的效果。结点定义如下:
1 #define typename int 2 #define size 100 3 // 静态链表结构 4 struct Node { 5 typename data; 6 int next; 7 } node[size];
另外,在使用静态链表时,尽量不要把结构体类型名和结构体变量名取成相同的名字。
五、【PAT A1032】Sharing
题意:
给出两条链表的首地址以及若干结点的地址、数据、下一个结点的地址,求两条链表的首个共用结点的地址。如果两个链表没有共用结点,则输出 -1。
思路:
- 由于地址的范围很小,因此可以直接用静态链表,依题目的要求,在结点的结构体中再定义一个 int 型变量 flag ,表示结点是否在第一条链表中出现,是则为 1,不是则为 0。
- 由题目给出的第一条链表的首地址出发遍历第一条链表,将经过的所有结点的 flag 值赋为 1。
- 接下来枚举第二条链表,当发现第一个 flag 值为 1的结点,说明是第一条链表中出现过的结果,即为两条链表的第一个共用结点。
- 如果第二条链表枚举完仍然没有发现共用结点,则输出 -1。
注意点:
- 使用 %05d 格式输出地址
- scanf 使用 %c 格式时可以读入空格,因此在输入地址、数据、后继结点地址时,格式应写成 %d %c %d,中间加空格。
代码如下:
1 /* 2 【PAT A1032】Sharing 3 题意: 4 给出两条链表的首地址以及若干结点的地址、数据、下一个结点的地址,求两条链表的首个共用结点的地址。如果两个链表没有共用结点,则输出 -1。 5 */ 6 7 #include8 #include <string.h> 9 #include 10 #include 11 #include 12 #include 13 14 #define maxn 100010 15 typedef struct { 16 char data; // 数据域 17 int next; // 指针域 18 bool flag; // 结点在第一条链表是否出现过 19 } NODE; 20 NODE node[maxn]; 21 22 int main() { 23 int i; 24 for(i=0; i // 初始化 25 node[i].flag = false; 26 } 27 int s1, s2, n; // 两条链表首地址,结点个数 28 scanf("%d %d %d", &s1, &s2, &n); 29 int address, next; // 地址,后继结点地址 30 char data; // 数据 31 for(i=0; i // 输入链表结点 32 scanf("%d %c %d", &address, &data, &next); 33 node[address].data = data; 34 node[address].next = next; 35 } 36 int p; 37 // 遍历第一条链表 38 for(p=s1; p!=-1; p=node[p].next) { 39 node[p].flag = true; 40 } 41 // 遍历第二条链表 42 for(p=s2; p!=-1; p=node[p].next) { 43 // 找到第一个共用结点 44 if(node[p].flag) break; 45 } 46 if(p != -1) { // 存在共用结点 47 printf("%05d\n", p); 48 } else { // 不存在 49 printf("-1\n"); 50 } 51 52 return 0; 53 }
六、【PAT A1052】Linked List Sorting
题意:
给出 N 个结点的地址 address、数据域 data 以及指针域 next,然后给出链表的首地址,要求把在这个链表上的结点按 data 值从小到大输出。
思路:
- 定义静态链表,其中结点性质由 bool 型变量 flag 定义,表示为结点在链表中是否出现。flag 为 false 表示无效结点。
- 初始化,令 flag 均为 false。
- 由题目给出的链表首地址 begin 遍历整条链表,并标记有效结点的 flag 为 true,同时计数有效结点的个数 count。
- 对结点进行排序,排序函数 cmp 的排序原则是:如果 cmp 的两个参数结点中有无效结点的话,则按 flag 从大到小排序,以把有效结点排在数组左端;否则按数据域从小到大排序。
- 按要求输出
注意点:
- 题目可能会有无效结点,即不在题目给出的首地址开始的链表上。
- 数据里面还有均为无效的情况,这时就要根据有效结点的个数特判输出 "0 -1"
代码如下:
1 /* 2 【PAT A1052】Linked List Sorting 3 题意: 4 给出 N 个结点的地址 address、数据域 data 以及指针域 next, 5 然后给出链表的首地址,要求把在这个链表上的结点按 data 值从小到大输出。 6 */ 7 8 #include9 #include <string.h> 10 #include 11 #include 12 #include 13 #include 14 15 #define maxn 100010 16 typedef struct { // 定义静态链表 17 int address, data, next; 18 bool flag; // 结点是否在链表上 19 } NODE; 20 NODE node[maxn]; 21 22 // 对结点进行排序, 23 // 如果 cmp 的两个参数结点中有无效结点的话,则按 flag 从大到小排序, 24 // 以把有效结点排在数组左端;否则按数据域从小到大排序。 25 int cmp(const void* a, const void* b) { 26 NODE* c = (NODE*)a; 27 NODE* d = (NODE*)b; 28 if(!c->flag || !d->flag) { // 将有效结点放在左边 29 return (c->flag > d->flag) ? -1 : 1; 30 } else { // 按数据域从小到大排序 31 return c->data - d->data; 32 } 33 } 34 35 int main() { 36 int i; 37 for(i=0; i // 初始化 38 node[i].flag = false; 39 } 40 int n, begin, address; // 结点个数,首地址,地址 41 scanf("%d %d", &n, &begin); 42 for(i=0; i // 输入结点 43 scanf("%d", &address); 44 scanf("%d %d", &node[address].data, &node[address].next); 45 node[address].address = address; 46 } 47 int count=0, p=begin; 48 while(p != -1) { // 枚举链表,标记有效结点,并计数 49 node[p].flag = true; 50 count++; 51 p = node[p].next; 52 } 53 if(count == 0) { // 特判,没有有效结点 54 printf("0 -1"); 55 } else { 56 qsort(node, maxn, sizeof(NODE), cmp); // 排序 57 // 按要求输出 58 printf("%d %05d\n", count, node[0].address); 59 for(i=0; i 1; ++i) { 60 printf("%05d %d %05d\n", node[i].address, node[i].data, node[i+1].address); 61 } 62 printf("%05d %d -1", node[i].address, node[i].data); 63 } 64 65 return 0; 66 }