目录
一. 背景介绍
1. 链表简介
链表作为一种常用的数据结构,在工作中会被广泛的使用,因此链表的原理和使用方法是每一个C语言开发工程师必不可少的学习要点。
链表是一种线性的数据结构,由多个结构相同的节点相连接而成,每一个节点都是一个结构体变量,其中包含了数据域和指针域,数据域用于存储想要保存的数据,而指针域存储一个指向下一个节点的指针。
根据结构区分,链表又分为单链表,双链表和循环链表,本章节只讲解单链表如何应用。其余两种链表的应用在后续章节中讲解。
2. 链表的优势
下表列出了链表和数组的优缺点。通过对比我们会发现,链表适用于插入和删除元素比较频繁的场景。
存储方式 | 占用开销 | 初始化 | 查询节点速度 | 插入和删除节点速度 | |
链表 | 不连续 | 包括指针域,因此占用开销大。 | 初始化时不需要分配固定空间。 | 慢,查询特定节点需要从头节点依次遍历。 | 快,插入和删除只需要移动前后两个节点。 |
数组 | 连续 | 只包含数据域,因此占用开销小。 | 初始化时需要把分配固定空间。 | 快,可以直接通过下标索引值进行查询。 | 慢,插入和删除需要移动当前索引后面的所有元素。 |
3. 链表支持操作
链表支持的基本操作如下表所示。
/*单链表支持的基本操作*/
//初始化链表
void init_list(struct node **pNode);
//链表判空
bool list_is_empty(struct node *pHead);
//头部插入数据
void insert_Firstnode(struct node *pHead, int element);
//尾部插入数据
void insert_Lastnode(struct node *pHead, int element);
//查询指定数据
struct node* find_element(struct node *pHead, int element);
//删除指定数据
void delete_last_element(struct node* pHead, int element);
//打印链表
void print_list(struct node *pHead);
二. 单链表应用实例
1. 单链表初始化
下面进行了链表中头节点的初始化,个人建议头结点不要用来存储数据,头节点的作用只是为了固定链表的初始地址,因此通常要从头结点的后一个结点开始存储数据。
/*声明链表节点*/
struct node
{
int element; //数据域
struct node *next; //指针域
}
/*链表初始化函数*/
void init_list(struct node **pNode)
{
*pNode = (struct node*)malloc(sizeof(struct node));
if(NULL == *pNode)
{
printf("链表初始化申请内存失败");
return NULL;
}
*pNode ->next = NULL;
}
/*测试函数*/
void test_code(void)
{
struct node *ptr = NULL;
init_list(&ptr);
....
}
2. 单链表判空
单链表的判空实际上就是对头结点指针域的判空,只要头结点的指针域为NULL,就说明链表是空链表。
(1)这里需要特别注意链表判空的条件是头节点的指针域pHead->next为NULL,而并非头节点地址pHead为NULL,头节点地址为NULL实际上代表头节点不存在,也就是链表不存在。
/*判断链表是否为空的函数*/
bool list_is_empty(struct node *pHead)
{
//链表是否存在
assert(NULL != pHead);
if(NULL == pHead->next)
{
return FALSE;
}
return TRUE;
}
/*测试函数*/
void test_code(void)
{
//传入链表的头结点
list_is_empty(ptr);
....
}
3. 头部插入数据
头部插入新节点,实际上就是在头节点之后插入新节点,需要考虑以下几种情况。
(1)插入前必须保证链表存在。
(2)链表中只有头节点时,我们只需要创建新节点,然后将头节点和新节点连接即可。
(3)链表中除了头节点之外还有其他节点时,我们需要创建新节点,将头节点的指针域指向新节点,然后将新节点的指针域指向原来头节点的下一个节点即可。
/*函数功能:插入新节点函数*/
/*函数参数:head-链表的头节点 element-要插入的元素值*/
void insert_Firstnode(struct node *pHead, int element)
{
//链表是否存在
assert(NULL != pHead);
//避免直接使用参数指针
struct node *pNode = pHead;
//初始化要插入的节点
struct node *pNew = NULL;
pNew =(struct node *)malloc(sizeof(struct node));
if(NULL == pNew)
{
printf("创建插入节点失败");
return NULL;
}
pNew->element = element;
//插入数据
if(NULL == pNode) //原来的链表中只有头节点
{
pNode->next = pNew;
pNew->next = NULL;
}
else //原来的链表中除了头结点外还有其他节点
{
pNode->next = pNew;
pNew->next = pNode->next;
}
}
/*测试函数*/
void test_code(void)
{
//往链表ptr中添加元素element
sinsert_Firstnode(ptr, element);
.....
}
4. 尾部插入数据
尾部插入数据的方法是将指针指向尾节点,并在最后进行插入,并将插入的节点作为尾节点。
/*函数功能:插入新节点函数*/
/*函数参数:head-链表的头节点 element-要插入的元素值*/
void insert_Lastnode(struct node *pHead, int element)
{
//链表是否存在
assert(NULL != pHead);
//避免直接使用参数指针
struct node *pNode = pHead;
//初始化要插入的节点
struct node *pNew = NULL;
pNew =(struct node *)malloc(sizeof(struct node));
if(NULL == pNew)
{
printf("创建插入节点失败");
return NULL;
}
pNew->element = element;
//遍历链表直到指向最后一个节点
while(NULL == pNode->next)
{
pNode = pNode->next;
}
//将尾节点和插入节点连接
pNode->next = pNew;
pNew->next = NULL;
}
/*测试函数*/
void test_code(void)
{
//往链表ptr中添加元素element
insert_Lastnode(ptr, element);
.....
}
5. 查询指定数据
查询指定数据的思路也很简单,直接从头结点开始遍历节点,直到查到数据为止。
/*查询链表函数*/
struct node* find_element(struct node *pHead, int element)
{
//链表是否存在
assert(NULL != pHead);
//避免直接使用指针
struct node *pNode = pHead;
//遍历链表
while((NULL != pNode) && (element != pNode->element))
{
pNode = pNode->next;
}
if(element == pNode->element)
{
returm pNode;
}
else
{
printf("链表中查找不到元素");
return NULL;
}
}
/*测试函数*/
void test_code(void)
{
//在pNode中查询数据element,并且返回对应的节点地址
pNode = find_element(pNode, element);
....
}
6. 删除指定数据
删除指定的数据可以拆分成两个功能,先查找指定数据的上一个节点,然后将对应的数据删除即可。需要注意的点有
(1)当链表为空时,直接返回NULL即可。
(2)当删除的链表节点为中间节点时,需要把当前数据所在节点的前一个节点指向后一个节点,并且将当前节点内存释放。
(3)当删除的链表节点为最后一个节点时,只需要将当前数据所在节点的前一个节点指向NULL,然后释放当前节点内存即可。
/*删除链表中的指定元素*/
void delete_last_element(struct node* pHead, int element)
{
//链表是否存在
assert(NULL != pHead);
//避免直接使用指针
struct node* pNode = pHead;
//查找当前数据的前一个节点p
while((NULL != pNode->next) && (element != pNode->next->element))
{
pNode == pNode->next;
}
//删除目标节点p->next
if(NULL == pNode->next->next) //目标节点是链表的最后一个节点
{
pNode = NULL;
free(pNode->next);
}
else //目标节点不是链表的最后一个节点
{
pNode = pNode->next->next;
free(pNode->next);
}
}
7. 打印链表数据
打印链表数据的操作非常的简单,就是遍历链表然后将数据打印即可。
/*打印链表数据*/
void print_list(struct node *pHead)
{
//链表是否存在
assert(NULL != pHead);
//避免直接使用指针
struct node *pNode = pHead;
while(NULL != pNode)
{
print("address %p value is %d", pNode, pNode->element);
}
return ;
}