链表与数组 在编程语言中,数组数据结构(array data structure),简称数组(Array),是一种数据结构,是数据元素(elements)的集合。有限个相同类型的元素按顺序存储,用一个名字命名,然后用编号区分他们的变量的集合;这个名字称为数组名,编号称为下标。
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不同与数组的是,并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储。
一、链表与数组的优缺点 首先让我们来介绍下链表与数组之间的优缺点:
// 数组的缺点:
//
// 1.一旦数组定义,则大小固定,无法进行修改(数组的大小)。
// 2.数组插入和删除的效率太低,时间复杂度O(n)。
//
// 数组的优点:
//
// 1.下标访问,速度快,时间复杂度是O(1)
//
//////////////////////////////////////////////////////////
//
// 链表:
//
// 链表的优点:
//
// 1.资源允许的情况下,规模可以不断的增长或减小。
// 2.删除和添加效率高,O(1)
//
// 链表的缺点:
//
// 链表的遍历过程效率比较低。
为什么会有这样的差异呢。 接下来我们一步步来看。
二、数据结构类型的定义 数组的定义: 1 2 3 4 5 6 7 8 9 10 11 12 13 //方法1 int array1[10] = {0}; //方法2 int array2[] = {12, 23, 34, 45, 56, 67, 78}; //方法3 int *array3 = NULL; array3 = (int *)malloc(sizeof(int) * 100); if(array3 != NULL){ fprintf(stderr, "the memory is full!\n"); exit(1); }
这里,我们的array1
和array2
都在定义时直接分配了栈 上的内存空间; 而array3
是一个int类型的指针,指向我们在堆 上用malloc
分配的4bytes ×100 = 400 bytes
大小的空间。
我们的数组虽然在O(1)的时间复杂度访问下标进行数据存取查找,可是一般数组定义后,大小不能够进行动态变化,且插入删除效率过低,时间复杂度为O(n). 所以,为了弥补这些不足,我们又学习到了链表。
链表的定义 链表是一种物理存储单元上非连续、非顺序的存储结构,链表由相同结构的链表节点构成,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。可以关注链表节点的结构:
1 2 3 4 5 6 7 typedef struct List_node { int data; //数据域 struct List_node *next; //指针域 }List_node; typedef List_node *List_head;
链表节点的两个组成部分: 我们可以看到链表节点主要分为两大部分:数据域和链域。 数据域是我们最要存储的内容,而链域则代表着一种指针。
如何把两个节点进行连接: 这个指针指向类型为链表节点,这样每一个节点就可以记录下下一个节点的位置,从而把原本毫不相干的链表节点串接起来。
一个链表节点定义;
1 2 3 4 5 6 7 8 9 typedef struct Node{ //数据域 char name[20]; //姓名 char sex; //m 男 w 女 性别 char age; //年龄 char jobs[20]; //职业 //链域 struct Node *next; }Node;
再申请一个链表节点;
1 2 3 4 5 Node node1 = {0}; Node *p_node = (Node *)malloc(sizeof(Node)); node1.next = p_node;
malloc 函数: void *malloc(int size);
说明:malloc 向系统申请分配指定size个字节的内存空间。返回类型是 void* 类型。 man malloc
可在bash中查看帮助文档。
三、链表的常见接口 我们把可以对链表的操作是在.h文件中进行声明的,我们叫这样的声明为接口,如下是单链表的接口声明:
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 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #define TRUE (1) #define FALSE (0) typedef unsigned char Boolean;typedef struct List_node { int data; struct List_node *next ; }List_node;typedef List_node *List_head; List_head init_list (void ) ; void destroy_list (List_head *head) ; Boolean push_front (List_head head, int value) ; Boolean push_back (List_head head, int value) ; Boolean pop_front (List_head head) ; Boolean pop_back (List_head head) ; Boolean find_node (List_head head, int value, List_node **node) ; void modify_node (List_node *node, int value) ; Boolean insert_node (List_head head, int index, int value) ; void sort_list_ascend (List_head head) ; void sort_list_descend (List_head head) ; void show_list (List_head head) ; int get_list_length (List_head head) ;
我们可以通过定义 包裹函数 来缩短程序。每个包裹函数完成实际的函数调用,检查返回值,并在发生错误时终止进程。 包裹函数其实就是封装函数,调用一个函数来实现这个功能。既然我们会经常用到一些函数并且还要进行错误处理,那么,我们可以将这些函数封装起来,也放入头文件.h当中。
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 //包裹函数 static void *Malloc(size_t size); static List_node *create_node(void); static void swap(void *a, void *b, int length); static void *Malloc(size_t size) { void *result = malloc(size); if(result == NULL){ //失败情况处理 fprintf(stderr, "the memory is full!\n"); exit(1); } return result; } static List_node *create_node(void) { //申请节点,并且对节点初始化(置为0) List_node *node = (List_node *)Malloc(sizeof(List_node)); bzero(node, sizeof(List_node)); return node; } static void swap(void *a, void *b, int length) { void *temp = Malloc(length); memcpy(temp, a, length); memcpy(a, b, length); memcpy(b, temp, length); free(temp); }
四、链表接口的实现 为了能够使用这些我们所提供的接口,如同菜名需要菜谱一样,我们要能够实现这些操作,可以在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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 ////////////////////////////////////////////////////////////////////////////////////// //接口实现 ////////////////////////////////////////////////////////////////////////////////////// List_head init_list(void) //1. 链表的初始化 { List_head head = NULL; head = create_node(); return head; } void destroy_list(List_head *head) //2. 链表的销毁 { List_node *p = NULL; List_node *q = NULL; if(head == NULL || *head == NULL){ return ; } //从链表头节点开始,依次对节点进行删除 p = *head; while(p != NULL){ q = p; p = p->next; free(q); } *head = NULL; } Boolean push_front(List_head head, int value) //3.1 头部添加 { List_node *node = NULL; if(head == NULL){ //判断链表是否存在 return FALSE; } //生成链表节点并且赋初值 node = create_node(); node->data = value; node->next = head->next; head->next = node; head->data++; return TRUE; } Boolean push_back(List_head head, int value) //3.2 尾部添加(效率低,从头找到尾才能添加) { List_node *p_node = NULL; if(head == NULL){ //参数检测 return FALSE; } //查找尾部节点的位置 p_node = head; while(p_node->next != NULL){ p_node = p_node->next; } //把新生成的节点追加到链表末尾 p_node->next = create_node(); p_node->next->data = value; head->data++; return TRUE; } Boolean pop_front(List_head head) //4.1 头部删除 { List_node *p_node = NULL; if(head == NULL || head->next == NULL){ //链表不存在或者没有有效节点 return FALSE; } p_node = head->next; head->next = p_node->next; free(p_node); head->data--; return TRUE; } Boolean pop_back(List_head head) //4.2 尾部删除(效率低) { List_node *p_node = NULL; //参数检测 if(head == NULL || head->next == NULL){ return FALSE; } //从头结点开始查找倒数第二个节点 p_node = head; while(p_node->next->next != NULL){ p_node = p_node->next; } //删除最后一个节点,把倒数第二个置为最后一个节点 free(p_node->next); p_node->next = NULL; head->data--; return TRUE; } Boolean find_node(List_head head, int value, List_node **node) //5. 链表的查找 { List_node *p_node = NULL; if(head == NULL){ return FALSE; } //从第一个有效节点开始查找值为value的节点 p_node = head->next; while(p_node != NULL){ if(p_node->data == value){ //找到value所在节点 if(node != NULL){ *node = p_node; } return TRUE; } p_node = p_node->next; } return FALSE; } void modify_node(List_node *node, int value) //6.1 链表节点的修改 { node->data = value; } Boolean insert_node(List_head head, int index, int value) //6.2 链表节点的插入 { List_node *node = NULL; List_node *p_node = NULL; int count = index; if(head == NULL || index < 0 || index > head->data){ //链表不存在并且下标不符合规则 return FALSE; } //创建新的节点 node = create_node(); node->data = value; //寻找插入的位置 p_node = head; while(count--){ //找到被插入节点的前一个节点 p_node = p_node->next; } //插入新的节点 node->next = p_node->next; p_node->next = node; head->data++; return TRUE; } void sort_list_ascend(List_head head) //7.1 链表升序 { List_node *p_node = NULL; List_node *q_node = NULL; if(head == NULL || head->data < 2){ return ; } for(p_node = head->next; p_node->next ; p_node = p_node->next){ for(q_node = p_node->next; q_node; q_node = q_node->next){ if(p_node->data > q_node->data){ swap(p_node, q_node, (unsigned long)(&((List_node *)0)->next)); } } } } void sort_list_descend(List_head head) //7.2 链表降序 { List_node *p_node = NULL; List_node *q_node = NULL; if(head == NULL || head->data < 2){ return ; } for(p_node = head->next; p_node->next ; p_node = p_node->next){ for(q_node = p_node->next; q_node; q_node = q_node->next){ if(p_node->data < q_node->data){ swap(p_node, q_node, sizeof(List_node) - sizeof(List_node *)); } } } } void show_list(List_head head) //8.1 显示链表信息 { List_node *p_node = NULL; if(head == NULL){ return ; } for(p_node = head->next; p_node != NULL; p_node = p_node->next){ printf("%d ", p_node->data); } printf("\n"); } int get_list_length(List_head head) //8.2 链表的长度 { if(head == NULL){ return -1; } return head->data; }
设计完这个单链表的接口与实现之后,我们要对相应功能进行检测。 测试代码如下:
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 //主函数-各个函数的测试 int main(int argc, char ** argv) { int i = 0; List_head head = NULL; head = init_list(); List_node *node = create_node(); printf("\n头部插入:\n"); for(i = 0 ; i < seed ; i++ ) { push_front(head,rand()%100); } show_list(head); printf("\n尾部插入:\n"); for(i = 0 ; i < seed ; i++ ) { push_back(head,rand()%100); } show_list(head); printf("\n头部删除:\n"); pop_front(head); show_list(head); printf("\n尾部删除:\n"); pop_back(head); show_list(head); printf("\n插入节点:\n"); insert_node(head, 3, 100); show_list(head); find_node(head, 77,&node); printf("\n升序排序:\n"); sort_list_ascend(head); show_list(head); printf("\n降序排序:\n"); sort_list_descend(head); show_list(head); printf("\n修改节点数据:\n"); node = head->next->next; modify_node(node, 777); show_list(head); printf("\nthe length of this list :%d\n",get_list_length(head)); destroy_list(&head); return 0; }
运行结果:
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 root@aemonair# ./my_head_list 链表初始化成功! 头部插入: 当前链表如下: 93 15 77 86 83 尾部插入: 当前链表如下: 93 15 77 86 83 35 86 92 49 21 头部删除: 当前链表如下: 15 77 86 83 35 86 92 49 21 尾部删除: 当前链表如下: 15 77 86 83 35 86 92 49 插入节点: 当前链表如下: 15 77 86 100 83 35 86 92 49 the node is found ! 升序排序: 当前链表如下: 15 35 49 77 83 86 86 92 100 降序排序: 当前链表如下: 100 92 86 86 83 77 49 35 15 修改节点数据: 当前链表如下: 100 777 86 86 83 77 49 35 15 the length of this list :9 链表销毁完毕!
测试程序2:
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 int main(int argc, char **argv) { List_head head = init_list(); //链表的初始化 int i = 0; int value = 0; List_node *find = NULL; for(i = 0; i < 10; ++i){ //尾部追加10个元素 push_front(head, rand() % 100); } pop_front(head); pop_back(head); show_list(head); //显示链表信息 printf("the count of list:%d\n", get_list_length(head)); printf("你要找哪个节点:?\n"); scanf("%d", &value); find_node(head, value, &find); if(find == NULL){ printf("the %d is not found!\n", value); }else{ printf("found! the value is %d\n", find->data); } sort_list_ascend(head); show_list(head); //显示链表信息 sort_list_descend(head); show_list(head); //显示链表信息 destroy_list(&head); return 0; }
总结:
以上,我们关于单链表的基本操作和基本接口的实现。 对于链表的指针域在交换两个的时候很容易出现问题,之后还会进行进一步的详解。