链表结构
链表(Linked List)结构是由许多结点依次链接而成,每个结点包括两部分:

- 数据部分:保存的是该结点的实际数据
- 地址部分:保存的是下一个结点的地址
链表分类
- 单链表:每个结点中只包含一个指针
- 双向链表:若每个结点包含两个指针,,一个指向下一个结点,另一个指向上一个结点
- 单向循环链表:在单链表中,将终端结点的指针域 NULL 改为指向头结点或开始结点
- 多重链的循环链表:如果将表中结点链在多个环上,将构成多重链的循环链表
(常规)链表操作实例代码
数据准备
定义链表结构
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | typedef enum {
 RetError            = -1,
 RetSuccess          = 0,
 } RetValue;
 
 
 
 typedef struct {
 char key[15];
 char name[20];
 int age;
 }NodeData;
 
 
 
 typedef struct node {
 NodeData nodeData;
 struct node* nextNode;
 }LLType;
 
 | 
相关操作
表尾添加结点
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 | LLType* LLAddEnd_1(LLType* head, NodeData data){
 LLType* newNode;
 if (!(newNode = (LLType*) malloc(sizeof(LLType))))
 {
 printf("新添加结点内存申请失败,无法在表尾添加结点!\n");
 return NULL;
 }
 newNode->nodeData = data;
 newNode->nextNode = NULL;
 if (NULL == head)
 {
 head = newNode;
 return head;
 }
 LLType* headTemp;
 headTemp = head;
 while (NULL != headTemp->nextNode)
 {
 headTemp = headTemp->nextNode;
 }
 headTemp->nextNode = newNode;
 
 return head;
 }
 
 | 
表头添加结点
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | LLType* LLAddHead_1(LLType* head, NodeData data){
 LLType* newNode;
 if (!(newNode = (LLType*)malloc(sizeof(LLType))))
 {
 printf("新添加结点内存申请失败,无法在表头添加结点!\n");
 return NULL;
 }
 newNode->nodeData = data;
 newNode->nextNode = head;
 head = newNode;
 
 return head;
 }
 
 | 
根据关键字查找结点
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | LLType* LLFindByKey_1(LLType* head, char* key){
 if (NULL == head)
 {
 printf("链表不存在,无法查找关键字结点!\n");
 return NULL;
 }
 if (NULL == key)
 {
 printf("关键字不存在,无法查找结点!\n");
 return NULL;
 }
 LLType* headTemp = head;
 while (headTemp)
 {
 if (0 == strcmp(headTemp->nodeData.key, key))
 {
 return headTemp;
 }
 headTemp = headTemp->nextNode;
 }
 return NULL;
 }
 
 | 
在关键字结点后插入结点数据
| 12
 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
 
 | LLType* LLInsert_1(LLType* head, char* key, NodeData data){
 if (NULL == head)
 {
 printf("链表不存在,无法插入新结点!\n");
 return NULL;
 }
 if (NULL == key)
 {
 printf("关键字不存在,无法插入结点!\n");
 return head;
 }
 LLType* newNode;
 if (!(newNode = (LLType*)malloc(sizeof(LLType))))
 {
 printf("新添加结点内存申请失败,无法添加结点!\n");
 return head;
 }
 newNode->nodeData = data;
 LLType* findNode = LLFindByKey_1(head, key);
 if (findNode)
 {
 newNode->nextNode = findNode->nextNode;
 findNode->nextNode = newNode;
 }
 else
 {
 printf("找不到与关键字匹配的结点,无法插入结点!\n");
 free(newNode);
 }
 return head;
 }
 
 | 
根据关键字删除结点
| 12
 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
 
 | RetValue LLDelete_1(LLType* head, char* key){
 if (NULL == head)
 {
 printf("链表不存在,无法删除关键字结点!\n");
 return RetError;
 }
 if (NULL == key)
 {
 printf("关键字不存在,无法删除结点!\n");
 return RetError;
 }
 LLType* findNode = head;
 LLType* headTemp = head;
 while (findNode)
 {
 if (0 == strcmp(findNode->nodeData.key, key))
 {
 headTemp->nextNode = findNode->nextNode;
 findNode->nextNode = NULL;
 free(findNode);
 
 return RetSuccess;
 }
 else
 {
 headTemp = findNode;
 findNode = findNode->nextNode;
 }
 }
 
 return RetError;
 }
 
 | 
计算链表长度
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | unsigned int LLLength_1(LLType* head){
 if (NULL == head)
 {
 printf("链表不存在,无法计算链表长度!\n");
 return 0;
 }
 LLType* headTemp = head;
 int length = 0;
 while (headTemp)
 {
 length++;
 headTemp = headTemp->nextNode;
 }
 
 return length;
 }
 
 | 
显示所有结点数据
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | void LLShowAll_1(LLType* head){
 if (NULL == head)
 {
 printf("链表不存在,无法显示所有结点!\n");
 return;
 }
 LLType* headTemp = head;
 while (headTemp)
 {
 printf("key = %-15s name = %-20s age = %d\n", headTemp->nodeData.key, headTemp->nodeData.name, headTemp->nodeData.age);
 headTemp = headTemp->nextNode;
 }
 }
 
 | 
(常规)链表优缺点
- 优点:
- 不需要分配一快连续的存储空间
- 添加或删除结点时,不需要移动大量的结点数据
 
- 缺点:
- 浪费存储空间,需要额外保存一个指针变量
- 无法随机访问,只能从链表头逐个查找
- 表尾添加结点时,需要逐个结点循环查找表尾结点指针,速度慢
 
为了解决添加结点或者计算链表长度时,无需逐个结点循环查找表尾结点指针,可以特别定义一个 头,该头保存头结点地址,尾结点地址,链表长度等信息,以方便添加结点或者计算链表长度操作。
(优化)链表操作实例代码
数据准备
定义链表结构
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | typedef enum {
 RetError            = -1,
 RetSuccess          = 0,
 } RetValue;
 
 
 
 typedef struct {
 char key[15];
 char name[20];
 int age;
 }NodeData;
 
 
 
 typedef struct node {
 NodeData nodeData;
 struct node* nextNode;
 }LLType;
 
 | 
定义“头”结构
| 12
 3
 4
 5
 6
 
 | typedef struct _head {
 LLType* headNode;
 LLType* tailNode;
 unsigned int clLength;
 }Head;
 
 | 
相关操作
表尾添加结点
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | Head* LLAddEnd_2(Head* head, NodeData data){
 LLType* newNode;
 if (!(newNode = (LLType*) malloc(sizeof(LLType))))
 {
 printf("新添加结点内存申请失败,无法在表尾添加结点!\n");
 return NULL;
 }
 newNode->nodeData = data;
 newNode->nextNode = NULL;
 if (NULL == head)
 {
 head = (Head*)malloc(sizeof(Head));
 head->headNode = newNode;
 head->tailNode = newNode;
 head->clLength = 1;
 return head;
 }
 head->tailNode->nextNode = newNode;
 head->tailNode = newNode;
 head->clLength++;
 
 return head;
 }
 
 | 
表头添加结点
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | Head* LLAddHead_2(Head* head, NodeData data){
 LLType* newNode;
 if (!(newNode = (LLType*)malloc(sizeof(LLType))))
 {
 printf("新添加结点内存申请失败,无法在表头添加结点!\n");
 return NULL;
 }
 newNode->nodeData = data;
 if (NULL == head)
 {
 newNode->nextNode = NULL;
 head = (Head*)malloc(sizeof(Head));
 head->headNode = newNode;
 head->tailNode = newNode;
 head->clLength = 1;
 return head;
 }
 newNode->nextNode = head->headNode;
 head->headNode = newNode;
 head->clLength++;
 
 return head;
 }
 
 | 
根据关键字查找结点
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | LLType* LLFindByKey_2(Head* head, char* key){
 if (NULL == head)
 {
 printf("链表不存在,无法查找关键字结点!\n");
 return NULL;
 }
 if (NULL == key)
 {
 printf("关键字不存在,无法查找结点!\n");
 return NULL;
 }
 LLType* headTemp = head->headNode;
 while (headTemp)
 {
 if (0 == strcmp(headTemp->nodeData.key, key))
 {
 return headTemp;
 }
 headTemp = headTemp->nextNode;
 }
 
 return NULL;
 }
 
 | 
在关键字结点后插入结点数据
| 12
 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
 
 | Head* LLInsert_2(Head* head, char* key, NodeData data){
 if (NULL == head)
 {
 printf("链表不存在,无法插入新结点!\n");
 return NULL;
 }
 if (NULL == key)
 {
 printf("关键字不存在,无法插入结点!\n");
 return head;
 }
 LLType* newNode;
 if (!(newNode = (LLType*)malloc(sizeof(LLType))))
 {
 printf("新添加结点内存申请失败,无法添加结点!\n");
 return head;
 }
 newNode->nodeData = data;
 LLType* findNode = LLFindByKey_2(head, key);
 if (findNode)
 {
 newNode->nextNode = findNode->nextNode;
 findNode->nextNode = newNode;
 head->clLength++;
 }
 else
 {
 printf("找不到与关键字匹配的结点,无法插入结点!\n");
 free(newNode);
 }
 
 return head;
 }
 
 | 
根据关键字删除结点
| 12
 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
 
 | RetValue LLDelete_2(Head* head, char* key){
 if (NULL == head)
 {
 printf("链表不存在,无法删除关键字结点!\n");
 return RetError;
 }
 if (NULL == key)
 {
 printf("关键字不存在,无法删除结点!\n");
 return RetError;
 }
 LLType* findNode = head->headNode;
 LLType* headTemp = head->headNode;
 while (findNode)
 {
 if (0 == strcmp(findNode->nodeData.key, key))
 {
 headTemp->nextNode = findNode->nextNode;
 findNode->nextNode = NULL;
 free(findNode);
 head->clLength--;
 
 return RetSuccess;
 }
 else
 {
 headTemp = findNode;
 findNode = findNode->nextNode;
 }
 }
 
 return RetError;
 }
 
 | 
计算链表长度
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | unsigned int LLLength_2(Head* head){
 if (NULL == head)
 {
 printf("链表不存在,无法计算链表长度!\n");
 return 0;
 }
 return head->clLength;
 }
 
 | 
显示所有结点数据
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | void LLShowAll_2(Head* head){
 if (NULL == head)
 {
 printf("链表不存在,无法显示所有结点!\n");
 return;
 }
 LLType* headTemp = head->headNode;
 while (headTemp)
 {
 printf("key = %-15s name = %-20s age = %d\n", headTemp->nodeData.key, headTemp->nodeData.name, headTemp->nodeData.age);
 headTemp = headTemp->nextNode;
 }
 }
 
 | 
测试
获取系统时间(微秒)
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | long long getCurTimeOfUsec(){
 struct timeval tv;
 gettimeofday(&tv,NULL);
 
 long long curUsec = tv.tv_sec * 1000000 + tv.tv_usec;
 
 return curUsec;
 }
 
 | 
(常规)链表添加节点测试
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | void test1(){
 long long start = getCurTimeOfUsec();
 
 LLType* head = NULL;
 NodeData data;
 for (int i = 0; i < COUNT; i++)
 {
 strcpy(data.key, "1001");
 strcpy(data.name, "张三");
 data.age = i;
 
 head = LLAddEnd_1(head, data);
 }
 
 long long end = getCurTimeOfUsec();
 printf("常规总花费时间:%lld\n", end - start);
 }
 
 | 
(优化)链表添加节点测试
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | void test2(){
 long long start = getCurTimeOfUsec();
 
 Head* head = NULL;
 NodeData data;
 for (int i = 0; i < COUNT; i++)
 {
 strcpy(data.key, "1002");
 strcpy(data.name, "李四");
 data.age = i;
 
 head = LLAddEnd_2(head, data);
 }
 
 long long end = getCurTimeOfUsec();
 printf("优化总花费时间:%lld\n", end - start);
 }
 
 | 
经过测试,两种方式的链表结构,在链表尾添加结点时的时间对比数据如下:
测试结果
第一轮
| 循环次数 | 常规(单位:µs) | 优化(单位:µs) | 时间差(单位:µs) | 
| 1 | 28 | 6 | 22 | 
| 10 | 25 | 6 | 19 | 
| 100 | 44 | 15 | 29 | 
| 1000 | 2320 | 210 | 2110 | 
| 10000 | 237870 | 1264 | 236606 | 
| 100000 | 21308344 | 10792 | 21297552 | 
第二轮
| 循环次数 | 常规(单位:µs) | 优化(单位:µs) | 时间差(单位:µs) | 
| 1 | 33 | 17 | 16 | 
| 10 | 34 | 17 | 17 | 
| 100 | 80 | 29 | 51 | 
| 1000 | 2311 | 134 | 2177 | 
| 10000 | 208948 | 1203 | 207745 | 
| 100000 | 21355188 | 17013 | 21338175 | 
第三论
| 循环次数 | 常规(单位:µs) | 优化(单位:µs) | 时间差(单位:µs) | 
| 1 | 18 | 17 | 1 | 
| 10 | 35 | 18 | 17 | 
| 100 | 63 | 40 | 23 | 
| 1000 | 1801 | 149 | 1652 | 
| 10000 | 205462 | 1184 | 204278 | 
| 100000 | 22253177 | 15876 | 22237301 | 
第四轮
| 循环次数 | 常规(单位:µs) | 优化(单位:µs) | 时间差(单位:µs) | 
| 1 | 29 | 19 | 10 | 
| 10 | 20 | 18 | 2 | 
| 100 | 49 | 30 | 19 | 
| 1000 | 2229 | 127 | 2102 | 
| 10000 | 281563 | 1235 | 280328 | 
| 100000 | 21770705 | 11681 | 21759024 | 
第五轮
| 循环次数 | 常规(单位:µs) | 优化(单位:µs) | 时间差(单位:µs) | 
| 1 | 21 | 17 | 4 | 
| 10 | 20 | 18 | 2 | 
| 100 | 48 | 34 | 14 | 
| 1000 | 2017 | 130 | 1887 | 
| 10000 | 203077 | 1369 | 201708 | 
| 100000 | 21622187 | 15273 | 21606914 | 
第六轮
| 循环次数 | 常规(单位:µs) | 优化(单位:µs) | 时间差(单位:µs) | 
| 1 | 20 | 16 | 4 | 
| 10 | 20 | 20 | 0 | 
| 100 | 48 | 27 | 21 | 
| 1000 | 1957 | 128 | 1829 | 
| 10000 | 204941 | 1282 | 203659 | 
| 100000 | 22003134 | 11628 | 21991506 | 
第七轮
| 循环次数 | 常规(单位:µs) | 优化(单位:µs) | 时间差(单位:µs) | 
| 1 | 18 | 17 | 1 | 
| 10 | 21 | 22 | -1 | 
| 100 | 44 | 31 | 13 | 
| 1000 | 1925 | 133 | 1792 | 
| 10000 | 210657 | 1232 | 209425 | 
| 100000 | 22019334 | 12332 | 22007002 | 
第八轮
| 循环次数 | 常规(单位:µs) | 优化(单位:µs) | 时间差(单位:µs) | 
| 1 | 19 | 18 | 1 | 
| 10 | 20 | 19 | 1 | 
| 100 | 102 | 26 | 76 | 
| 1000 | 1921 | 174 | 1747 | 
| 10000 | 219983 | 1230 | 218753 | 
| 100000 | 22104905 | 12201 | 22092704 | 
第九轮
| 循环次数 | 常规(单位:µs) | 优化(单位:µs) | 时间差(单位:µs) | 
| 1 | 22 | 17 | 5 | 
| 10 | 20 | 20 | 0 | 
| 100 | 50 | 29 | 21 | 
| 1000 | 1971 | 132 | 1839 | 
| 10000 | 209878 | 1231 | 208647 | 
| 100000 | 22326454 | 11719 | 22314735 | 
第十轮
| 循环次数 | 常规(单位:µs) | 优化(单位:µs) | 时间差(单位:µs) | 
| 1 | 36 | 17 | 19 | 
| 10 | 21 | 17 | 4 | 
| 100 | 66 | 28 | 38 | 
| 1000 | 1911 | 131 | 1780 | 
| 10000 | 211644 | 1156 | 210488 | 
| 100000 | 21856814 | 11484 | 21845330 | 
平均值
| 循环次数 | 常规(单位:µs) | 优化(单位:µs) | 时间差(单位:µs) | 
| 1 | 24.4 | 16.1 | 8.3 | 
| 10 | 23.6 | 17.5 | 6.1 | 
| 100 | 59.4 | 28.9 | 30.5 | 
| 1000 | 2036.3 | 144.8 | 1891.5 | 
| 10000 | 291402.3 | 1238.6 | 218163.7 | 
| 100000 | 21862024. | 12999.9 | 21849024.3 | 
结论
由此可见,在链表数据结构中,如果添加结点的操作比较频繁,使用 优化 的链表结构更快捷、方便。