概念
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
注意
1、单链表的结构:由节点构成,每个节点包含数据域(存储元素内容)和指针域(指向后继元素)两部分。
2、头指针:永远指向链表第一个节点的位置。用于指明链表的位置,便于后期找到链表并使用表中的数据。
3、头节点:通常作为链表的第一个节点。并非必须的,只是为了方便解决某些实际问题而引入的,另外头节点数据域可以用来存储链表元素个数。
4、首元节点:头节点的下一个节点,第一个真正意义上存储了数据的节点。
5、最后一个节点没有后继节点,指向NULL,也是链表结束条件的唯一判据。
核心
单链表由指针域和数据域构成,并且声明头指针指明链表的位置,节点与节点之间通过指针链接,如下图代码和图形所示。
/* 链表元素定义 */ struct t_node{ int data; /* 元素值 */ struct t_node *p_next; /* 指向下一个元素 */ }; /* 链表定义 */ struct t_link{ struct t_node *p_node; /* 指向链表头结点 */ };
单链表的插入分为在链表头插入和链表尾插入两种,因为链表头的位置可以很容易得到,所以链表头插入相对比较简单。
链表头插入:整个过程分为三步,a、创建节点;b、找到链表头位置;c、插入节点,具体执行如下图代码和图形所示。
/** * @在链表头增加元素 * @p_link:链表 data:元素值 * @返回1表示成功,0表示失败 **/ int link_add_front(struct t_link *p_link, int data) { struct t_node *p_node = NULL; if (link_is_full(p_link)) return 0; if ((p_node = creat_node(data)) == NULL) return 0; p_node->p_next = p_link->p_node->p_next; p_link->p_node->p_next = p_node; p_link->p_node->data++; return 1; }
链表尾插入:过程同链表头插入一样,只不过在寻找链表尾的时候需要把整个链表遍历一遍,具体执行如下图代码和图形所示。
/** * @在链表尾增加元素 * @p_link:链表 data:元素值 * @返回1表示成功,0表示失败 **/ int link_add_rear(struct t_link *p_link, int data) { struct t_node *p_rear = p_link->p_node; struct t_node *p_node = NULL; if (link_is_full(p_link)) return 0; /* 1. 创建节点 */ if ((p_node = creat_node(data)) == NULL) return 0; /* 2. 查找尾节点 */ while(p_rear->p_next != NULL) p_rear = p_rear->p_next; /* 3. 插入元素 */ p_rear->p_next = p_node; p_node->p_next = NULL; p_link->p_node->data++; return 1; }
单链表的删除同样包括删除链表头和删除链表尾,删除的核心在于让目标节点的前驱节点和后继节点直接相连,然后删除目标节点即可。
删除链表头:代码和示意图如下所示。
/** * @删除链表头元素 * @p_link:链表 * @返回1表示成功,0表示失败 **/ int link_del_head(struct t_link *p_link) { struct t_node *p_node = NULL; if (link_is_empty(p_link)) return 0; p_node = p_link->p_node->p_next; p_link->p_node->p_next = p_node->p_next; p_node->p_next = NULL; free(p_node); p_node = NULL; p_link->p_node->data--; return 1; }
删除链表尾:代码和示意图如下所示。
/** * @删除链表尾元素 * @p_link:链表 * @返回1表示成功,0表示失败 **/ int link_del_rear(struct t_link *p_link) { struct t_node *p_rear = p_link->p_node, *p_prev = NULL; if (link_is_empty(p_link)) return 0; /* 查找尾节点 */ while(p_rear->p_next != NULL) { p_prev = p_rear; /* 保存倒数第二个节点 */ p_rear = p_rear->p_next; } p_rear->p_next = NULL; free(p_rear); p_rear = NULL; p_link->p_node->data--; p_prev->p_next = NULL; return 1; }
其余操作相对比较简单,这里就不再一一阐述了,具体参见示例代码,相信大家应该能看懂。
示例
★包含头文件sll.h和源文件sll.c(均已验证通过),sll = Singly Linked List。
sll.h
/** * @Filename : sll.h * @Revision : $Revision: 1.0 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 单链表示例(sll = Singly Linked List) **/ #ifndef __SLL_H__ #define __SLL_H__ #include <stdio.h> #include <stdlib.h> #define MAX_SIZE 15 /* 链表最大元素个数 */ /* 链表元素定义 */ struct t_node{ int data; /* 元素值 */ struct t_node *p_next; /* 指向下一个元素 */ }; /* 链表定义 */ struct t_link{ struct t_node *p_node; /* 指向链表头结点 */ }; /** * @初始化链表 * @p_link:链表 * @创建头结点,让链表头指针指向头结点,头结点数据存储链表数据长度 **/ void link_init(struct t_link *p_link); /** * @清除链表 * @p_link:链表 **/ void link_deinit(struct t_link *p_link); /** * @判断链表是否已满 * @p_link:链表 * @返回1表示已满,0表示未满 **/ int link_is_full(const struct t_link *p_link); /** * @判断链表是否已空 * @p_link:链表 * @返回1表示已空,0表示未空 **/ int link_is_empty(const struct t_link *p_link); /** * @获取链表元素个数 * @p_link:链表 * @返回元素个数 **/ int link_get_size(const struct t_link *p_link); /** * @在链表头增加元素 * @p_link:链表 data:元素值 * @返回1表示成功,0表示失败 **/ int link_add_front(struct t_link *p_link, int data); /** * @在链表尾增加元素 * @p_link:链表 data:元素值 * @返回1表示成功,0表示失败 **/ int link_add_rear(struct t_link *p_link, int data); /** * @删除链表头元素 * @p_link:链表 * @返回1表示成功,0表示失败 **/ int link_del_head(struct t_link *p_link); /** * @删除链表尾元素 * @p_link:链表 * @返回1表示成功,0表示失败 **/ int link_del_rear(struct t_link *p_link); /** * @获取链表头元素 * @p_link:链表 p_data:保存链表头元素值 * @返回1表示成功,0表示失败 **/ int link_get_front(const struct t_link *p_link, int *p_data); /** * @获取链表尾元素 * @p_link:链表 p_data:保存链表尾元素值 * @返回1表示成功,0表示失败 **/ int link_get_rear(const struct t_link *p_link, int *p_data); /** * @获取链表第n个元素 * @p_link:链表 p_data:保存元素值 n:第n个元素 * @返回1表示成功,0表示失败 **/ int link_get_nth(const struct t_link *p_link, int *p_data, int n); /** * @打印链表元素内容 * @p_link:链表 **/ void link_print(const struct t_link *p_link); #endif
sll.c
/** * @Filename : sll.c * @Revision : $Revision: 1.0 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 单链表示例(sll = Singly Linked List) **/ #include "sll.h" /** * @创建节点 * @data:元素值 * @返回节点地址,NULL表示失败 **/ static struct t_node *creat_node(int data) { struct t_node *p_node = (struct t_node *)malloc(sizeof(struct t_node)); if (p_node == NULL) return NULL; p_node->data = data; p_node->p_next = NULL; return p_node; } /** * @初始化链表 * @p_link:链表 * @创建头结点,让链表头指针指向头结点,头结点数据存储链表数据长度 **/ void link_init(struct t_link *p_link) { struct t_node *p_head = creat_node(0); /* 头结点 */ if (p_head == NULL) return; p_link->p_node = p_head; } /** * @清除链表 * @p_link:链表 **/ void link_deinit(struct t_link *p_link) { struct t_node *p_tmp = NULL, *p_node = p_link->p_node->p_next; while(p_node) { p_tmp = p_node; p_node = p_node->p_next; free(p_tmp); } p_link->p_node->p_next = NULL; p_link->p_node->data = 0; } /** * @判断链表是否已满 * @p_link:链表 * @返回1表示已满,0表示未满 **/ int link_is_full(const struct t_link *p_link) { return (p_link->p_node->data >= MAX_SIZE); } /** * @判断链表是否已空 * @p_link:链表 * @返回1表示已空,0表示未空 **/ int link_is_empty(const struct t_link *p_link) { return (p_link->p_node->data == 0); } /** * @获取链表元素个数 * @p_link:链表 * @返回元素个数 **/ int link_get_size(const struct t_link *p_link) { return (p_link->p_node->data); } /** * @在链表头增加元素 * @p_link:链表 data:元素值 * @返回1表示成功,0表示失败 **/ int link_add_front(struct t_link *p_link, int data) { struct t_node *p_node = NULL; if (link_is_full(p_link)) return 0; if ((p_node = creat_node(data)) == NULL) return 0; p_node->p_next = p_link->p_node->p_next; p_link->p_node->p_next = p_node; p_link->p_node->data++; return 1; } /** * @在链表尾增加元素 * @p_link:链表 data:元素值 * @返回1表示成功,0表示失败 **/ int link_add_rear(struct t_link *p_link, int data) { struct t_node *p_rear = p_link->p_node; struct t_node *p_node = NULL; if (link_is_full(p_link)) return 0; /* 1. 创建节点 */ if ((p_node = creat_node(data)) == NULL) return 0; /* 2. 查找尾节点 */ while(p_rear->p_next != NULL) p_rear = p_rear->p_next; /* 3. 插入元素 */ p_rear->p_next = p_node; p_node->p_next = NULL; p_link->p_node->data++; return 1; } /** * @删除链表头元素 * @p_link:链表 * @返回1表示成功,0表示失败 **/ int link_del_head(struct t_link *p_link) { struct t_node *p_node = NULL; if (link_is_empty(p_link)) return 0; p_node = p_link->p_node->p_next; p_link->p_node->p_next = p_node->p_next; p_node->p_next = NULL; free(p_node); p_node = NULL; p_link->p_node->data--; return 1; } /** * @删除链表尾元素 * @p_link:链表 * @返回1表示成功,0表示失败 **/ int link_del_rear(struct t_link *p_link) { struct t_node *p_rear = p_link->p_node, *p_prev = NULL; if (link_is_empty(p_link)) return 0; /* 查找尾节点 */ while(p_rear->p_next != NULL) { p_prev = p_rear; /* 保存倒数第二个节点 */ p_rear = p_rear->p_next; } p_rear->p_next = NULL; free(p_rear); p_rear = NULL; p_link->p_node->data--; p_prev->p_next = NULL; return 1; } /** * @获取链表头元素 * @p_link:链表 p_data:保存链表头元素值 * @返回1表示成功,0表示失败 **/ int link_get_front(const struct t_link *p_link, int *p_data) { if (link_is_empty(p_link)) return 0; *p_data = p_link->p_node->p_next->data; return 1; } /** * @获取链表尾元素 * @p_link:链表 p_data:保存链表尾元素值 * @返回1表示成功,0表示失败 **/ int link_get_rear(const struct t_link *p_link, int *p_data) { struct t_node *p_rear = p_link->p_node; if (link_is_empty(p_link)) return 0; /* 查找尾节点 */ while(p_rear->p_next != NULL) p_rear = p_rear->p_next; *p_data = p_rear->data; return 1; } /** * @获取链表第n个元素 * @p_link:链表 p_data:保存元素值 n:第n个元素 * @返回1表示成功,0表示失败 **/ int link_get_nth(const struct t_link *p_link, int *p_data, int n) { int cnt = 0; const struct t_node *p_tmp = p_link->p_node; if (link_get_size(p_link) < n) return 0; while (p_tmp->p_next != NULL) { p_tmp = p_tmp->p_next; if (++cnt == n) { *p_data = p_tmp->data; return 1; } } return 0; } /** * @打印链表元素内容 * @p_link:链表 **/ void link_print(const struct t_link *p_link) { struct t_node *p_tmp = NULL; for (p_tmp=p_link->p_node->p_next; p_tmp!=NULL; p_tmp=p_tmp->p_next) printf("%d ", p_tmp->data); printf("\n"); } /** * @单链表测试代码 **/ int main(void) { int val; struct t_link mylink; link_init(&mylink); link_add_front(&mylink, 25); link_add_front(&mylink, 41); link_add_front(&mylink, 16); link_add_front(&mylink, 7); link_add_rear(&mylink, 8); link_add_rear(&mylink, 19); link_add_front(&mylink, 22); link_add_front(&mylink, 16); link_add_front(&mylink, 7); link_add_rear(&mylink, 8); link_add_rear(&mylink, 19); link_add_front(&mylink, 22); /* 插入元素,链表:22 7 16 22 7 16 41 25 8 19 8 19,元素个数为12 */ printf("the size of link is: %d\n", link_get_size(&mylink)); link_print(&mylink); printf("---------------------------------------\n"); /* 删除首尾元素,链表:7 16 22 7 16 41 25 8 19 8,元素个数为10 */ link_del_head(&mylink); link_del_rear(&mylink); printf("the size of link is: %d\n", link_get_size(&mylink)); link_print(&mylink); printf("---------------------------------------\n"); /* 第一个元素7,最后一个元素8,第三个元素22 */ if (link_get_front(&mylink, &val)) printf("the front link data is: %d\n", val); if (link_get_rear(&mylink, &val)) printf("the tail link data is: %d\n", val); if (link_get_nth(&mylink, &val, 3)) printf("the 3th link data is: %d\n", val); return 0; }
结论
feng:link$ gcc -o sll sll.c feng:link$ ./sll the size of link is: 12 22 7 16 22 7 16 41 25 8 19 8 19 --------------------------------------- the size of link is: 10 7 16 22 7 16 41 25 8 19 8 --------------------------------------- the front link data is: 7 the tail link data is: 8 the 3th link data is: 22 feng:link$
本示例仅为单链表示例,公众号也提供双向链表示例《也没想象中那么神秘的数据结构-一环扣一环的“链表”(双链表)》、循环链表示例《也没想象中那么神秘的数据结构-一环扣一环的“链表”(循环链表)》以及链表的典型应用示例。
往期 · 推荐
也没想象中那么神秘的数据结构-先来后到的“队列”(链式队列)
关注
更多精彩内容,请关注微信公众号:不只会拍照的程序猿,本人致力分享linux、设计模式、C语言、嵌入式、编程相关知识,也会抽空分享些摄影相关内容,同样也分享大量摄影、编程相关视频和源码,另外你若想要本文章源码请关注公众号:不只会拍照的程序猿,后台回复:数据结构源码,也可点击此处下载。