1. 链表概述
1)概念
数据链式结构按照顺序存储的方式
2)优缺点
① 优点:插入和删除比较方便
② 缺点:查找和替换麻烦
TIPS:刚好与顺序表相反
3)操作
增删改查(遵循大原则:先练后断)
2. 函数声明部分
单向链表是只能从头往后走的一个链表,定义单向链表结构体如下:
//单向链表结构体
typedef struct link{
datatype data; //数据域
struct link *next; //指针域
}link_t;
创建函数声明文件link.h
#ifndef __LINK_H__
#define __LINK_H__
#include <stdio.h>
#include <stdlib.h>
/*单向链表*/
typedef int datatype;
typedef struct link{
datatype data; //数据域
struct link *next; //指针域
}link_t;
link_t *link_init(void); //初始化
void link_insert_head(link_t *head,datatype data); //头插
void link_insert_tail(link_t *p,datatype data); //尾插
void link_del(link_t *p,datatype data); //删除
void link_replace(link_t *p,datatype old,datatype new); //替换
void display(link_t *p); //遍历展示
#endif
3. 函数实现部分
创建文件link.c
1)初始化
单向链表的初始化包括创建头节点(一般不存数据),以及创建后序存储数据的节点,代码如下:
/***************************
@brief: 初始化(创建头结点)
@para: 无
@retval: 结构体指针
***************************/
link_t *link_init(void)
{
/*在堆中申请空间*/
link_t *p = (link_t *)malloc(sizeof(link_t));
if(p == NULL)
{
perror("malloc error");
return NULL;
}
/*将next赋值为NULL*/
p->next = NULL;
return p;
}
/***************************
@brief: 创建一个新的节点
@para: 数据域的数据
@retval: 结构体指针
***************************/
static link_t *create_node(datatype d)
{
/*在堆中申请空间*/
link_t *p = (link_t *)malloc(sizeof(link_t));
if(p == NULL)
{
perror("malloc error");
return NULL;
}
/*将next赋值为NULL*/
p->data = d;
p->next = NULL;
return p;
}
2)插入操作
插入操作分为头插法以及尾插法。头插法是指将新节点插到头节点的后面,如输入1,2,3,4,5正序遍历输出的结果为5,4,3,2,1。而尾插法则相反,其将新节点插入到尾结点后面,同样的输入,而输出的结果为1,2,3,4,5。
插入操作的函数如下:
/***************************
@brief: 插入的功能函数,一个节点node插在另一个节点p的后面
@para: 数据域的数据
@retval: 结构体指针
***************************/
static void insert_behind(link_t *p,link_t *node)
{
/*遵循大原则:先连后断*/
node->next = p->next; //先将要插入的节点的next指向p节点的下一个节点(先连)
p->next = node; //再将p节点的next指向新插入的节点node(后断)
}
/***************************
@brief: 头(被)插
@para: 数据域的数据
@retval: 结构体指针
***************************/
void link_insert_head(link_t *head,datatype data)
{
/*创建节点*/
link_t *node = creat_node(data);
if(node == NULL)
return;
}
/*调用插入功能函数*/
insert_behind(head,node);
}
/***************************
@brief: 尾(被)插
@para: 数据域的数据
@retval: 结构体指针
***************************/
void link_insert_tail(link_t *p,datatype data)
{
/*创建节点*/
link_t *node = create_node(data);
if(node == NULL)
return;
/*遍历链表找到尾结点*/
while(p-next != NULL)
p=p->next;
/*调用插入功能函数*/
insert_behind(p,node);
}
通过画图来直观的理解链表插入的过程,依旧举商场货柜的例子,有一天柜员觉得用顺序表那种插入方法太麻烦了,因此想到了一种效率更高的方法。只需增加一个新的货柜放在前面或者后面就行了,这样就提高了效率。
链表的插入操作必须要遵循先连后断的原则,否则会导致链表断裂,造成数据的丢失,因此在新的节点插入时,如果用头插法先将其的next指针指向头节点的下一个节点,后再将头节点的next指向新插入的节点。
若使用尾插,则比头插多了一步遍历找到最后一个节点的操作。
3) 剪切操作
剪切操作指的是把链表中的某个节点“拿”出来,再编程中我们通常剪切p后面的节点,函数实现如下:
static link_t *cut_behind(link_t *p)
{
/*剪切时p和p->next不能为NULL*/
if(p == NULL || p->next == NULL)
return NULL;
/*保存需要剪切的节点*/
link_t *node = p->next;
/*将p的next指向node的next*/
p->next = node->next;
/*为了安全,让node的next指向NULL*/
node->next = NULL;
return node;
}
将西瓜节点剪切出来,画图如下
4) 删除操作
删除操作是在剪切操作的基础上,遍历查找所需要的节点,然后将其剪切出来,用free()将剪切出来的节点释放。代码如下
/***************************
@brief: 删除
@para: 数据域的数据
@retval: 结构体指针
***************************/
void link_del(link_t *p,datatype data)
{
link_t *node;
/*遍历*/
while(p->next != NULL)
{
/*查找比对*/
if(p->next->data == data)
{
/*将需要的节点剪切出来*/
node = cut_behind(p);
if(node == NULL)
return;
/*释放剪切出来的节点*/
free(node);
continue;
}
//继续移动去遍历后续可能存在的符合条件数据,如果只需删除一个则continue改为break。
p = p->next;
}
}
5)替换操作
替换操作指的时在链表中,将某个节点替换成另一个新的节点。函数实现代码如下:
/***************************
@brief: 替换
@para: 节点的结构体指针
@para: 旧数据
@para: 新数据
@retval: 结构体指针
***************************/
void link_replace(link_t *p,datatype old,datatype new)
{
link_t * old_node = NULL;
link_t * new_node = NULL;
/*遍历*/
while(p->next != NULL)
{
/*匹配数据*/
if(p->next->data == old)
{
/*保存旧节点*/
old_node = p->next;
/*创建新节点*/
new_node = create_node(new);
if(new_node == NULL)
return;
/*新节点替换旧节点*/
new_node->next = old_node->next;
p->next = new_node;
/*释放旧节点*/
old_node->next = NULL;
free(old_node);
continue;
}
p = p->next;
}
}
用画图来理解:
6)遍历展示
通过遍历操作将运行结构展示出来,通过从头节点遍历,依次将节点的数据域data进行输出,函数实现代码如下:
/***************************
@brief: 遍历展示
@para: 数据域的数据
@retval: 结构体指针
***************************/
void display(link_t *p)
{
printf("遍历结果为:\n")
while(p-next != NULL)
{
p = p->next;
printf("%d ->",p->data);
}
printf("\n");
}
4. main函数实现
创建文件main.c
#include "link.h"
int main(void)
{
/*初始化头节点head*/
link_t *head = link_init();
if(head == NULL)
{
return -1;
}
datatype d;
int ret;
//头插
printf("开始头插\n");
while(1)
{
ret = scanf("%d",&d);
if(ret == 0)
break;
/*插入数据*/
link_insert_head(head,d);
display(head);
}
getchar();
//尾插
printf("开始尾插\n");
while(1)
{
ret = scanf("%d",&d);
if(ret == 0)
break;
/*插入数据*/
link_insert_tail(head,d);
display(head);
}
getchar();
//删除
printf("开始删除\n");
while(1)
{
ret = scanf("%d",&d);
if(ret == 0)
break;
/*删除数据*/
link_del(head,d);
display(head);
}
getchar();
//替换
datatype old,new;
printf("开始替换\n");
while(1)
{
ret = scanf("%d %d",&old,&new);
if(ret == 0)
break;
/*替换数据*/
link_replace(head,old,new);
display(head);
}
return 0;
}