链表
链表是一种经典的数据结构,它通过节点的指针将数据元素有序地链接在一起,在链表中,每个节点存储数据以及指向其他节点的指针(或引用)。链表具有动态性和灵活性的特点,适用于频繁插入、删除操作的场景。
定义
概念
- 将线性表
L = (a0,a1,...,an-1)
中各元素分布在存储器的不同存储块,称为结点。
通过指针或地址建立起它们之间的联系,所得到的存储结构就是链表。 - 链表都有一个头结点(一般不保存数据,不做遍历),是链表的入口。
- 链表呈现一对一关系,且有一个前驱结点和一个后继结点。
- 每一个结点都往堆申请了地址,由多个堆组成。
分类
- 单向链表(重点)
- 单向循环链表
- 双向链表
- 双向循环链表(重点)
- 内核链表(重点)
我们这里将链表分成三篇文章来写,分别是 1:单向链表(重点)和单向循环链表;2:双向链表和双向循环链表(重点);3:内核链表(重点)。因为代码片如若过多可能会导致思维混乱,所以分开来写,将单向链表和循环链表放在一篇文章之中是因为二者的理念相同,只是首位相连接,代码逻辑接近,方便理解和对比。
链表的优缺点(想要具体了解链表和顺序表之间的区别详细,请查阅这篇文章:<链接:【数据结构】顺序表和链表优劣的对比分析>)
- 优点:插入和删除非常方便。
- 缺点:查找和替换比较麻烦。
- 操作:对链表中数据的增删改查 —> 大原则:先连后断
单向链表(Singly Linked List)
定义:
- 每个节点包含两部分:
- 数据域(存储数据)。
- 指针域(存储指向下一个节点的指针)。
- 只有一个方向,从头节点开始依次访问每个节点。
特点:
- 插入和删除操作高效,不需要移动其他节点。
- 无法直接访问某个特定位置的元素,需要从头节点开始遍历。
#define datatype int
typedef struct link{
datatype data; //数据域
struct link *next; //指向后继结点的指针域
}link_t;
// 缺点:只能从头结点一直往后走
// 头插:头结点(最前面的结点)后面插入
// 尾插:尾结点(最后面的结点)后面插入
1> 初始化link_init
link_t *link_init(void) //造头结点
{
//1>向堆申请
link_t *p = (link_t *)malloc(sizeof(link_t));
if(NULL == p)
{
perror("malloc");
return NULL;
}
//将指针赋值 ,为了安全指向NULL
p->next = NULL;
return p;
}
2> 创建结点create_node(static)
static link_t *create_node(datatype d)
{
//1>向堆空间申请
link_t *p = (link_t *)malloc(sizeof(link_t));
if(NULL == p)
{
perror("malloc");
return NULL;
}
//2>赋值
p->data = d;
p->next = NULL;
return p;
}
3> 插入函数insert_behind(static)
//将一个结点(a)插到另一个结点(b)的后面
static void insert_behind(link_t *a,link_t *b)
{
//遵循先连后断
a->next = b->next; //避免b指向的地址丢失
b->next = a;
}
4> 头插函数insert_head
void insert_head(link_t *p,datatype d)
{
//利用传进来的数据,调用创建结点函数
link_t *node = create_node(d);
//将创建出来的node结点插到头结点后面
insert_behind(node,p);
}
5> 遍历展示display
void display(link_t *head)
{
//遍历整个链表
while(head->next != NULL)
{
//将head指针变量往右移
head = head->next;
printf("%d ",head->data);
}
printf("\n");
}
6> 尾插函数insert_tail
//每插入一个结点进来,将其插入到尾结点后面
void insert_tail(link_t *p,datatype data)
{
link_t *node = create_node(data);
if(NULL == node)
return;
//找到尾结点
while(p->next != NULL)
{
p = p->next;
}
insert_behind(node,p);
}
7> 删除函数link_del
void link_del(link_t *p,datatype d)
{
link_t *node = NULL;
//遍历
while(p->next != NULL)
{
//进行数据对比
if(p->next->data == d) //从头结点后第一个有数据的结点开始判断
{
//先保存要删除结点的地址
node = p->next;
// 将p的next指向node的next
p->next = node->next;
//为了安全,在释放前,令它的指针域指向NULL
node->next = NULL;
//释放
free(node);
continue;
}
//往后继续遍历,找相同数据的结点
p = p->next;
}
}
8> 修改函数link_replace
void link_replace(link_t *p,int old,int new)
{
//遍历找到旧数据所在的地址
while(p->next != NULL)
{
if(p->next->data == old)
{
p->next->data = new;
continue;
}
p = p->next;
}
}
完整代码(共三个文件:头文件、链表函数和主函数)
头文件: link.h
#ifndef __LINK_H__
#define __LINK_H__
#include <stdio.h>
#include <stdlib.h>
#define datatype int
typedef struct link{
datatype data; //数据域
struct link *next; //指向后继结点的指针域
}link_t;
extern link_t *link_init(void);
extern void insert_head(link_t *p,datatype d);
extern void display(link_t *head);
extern void insert_tail(link_t *p,datatype data);
extern void link_del(link_t *p,datatype d);
extern void link_replace(link_t *p,int old,int new);
#endif
链表函数:link.c
#include "link.h"
//初始化
link_t *link_init(void) //造头结点
{
//1>向堆申请
link_t *p = (link_t *)malloc(sizeof(link_t));
if(NULL == p)
{
perror("malloc");
return NULL;
}
//将指针赋值 ,为了安全指向NULL
p->next = NULL;
return p;
}
//创建结点
static link_t *create_node(datatype d)
{
//1>向堆空间申请
link_t *p = (link_t *)malloc(sizeof(link_t));
if(NULL == p)
{
perror("malloc");
return NULL;
}
//2>赋值
p->data = d;
p->next = NULL;
return p;
}
//插入函数insert_behind
//将一个结点(a)插到另一个结点(b)的后面
static void insert_behind(link_t *a,link_t *b)
{
//遵循先连后断
a->next = b->next; //避免b指向的地址丢失
b->next = a;
}
//头插函数insert_head
void insert_head(link_t *p,datatype d)
{
//利用传进来的数据,调用创建结点函数
link_t *node = create_node(d);
//将创建出来的node结点插到头结点后面
insert_behind(node,p);
}
//遍历操作
void display(link_t *head)
{
//遍历整个链表
while(head->next != NULL)
{
//将head指针变量往右移
head = head->next;
printf("%d ",head->data);
}
printf("\n");
}
//每插入一个结点进来,将其插入到尾结点后面
void insert_tail(link_t *p,datatype data)
{
link_t *node = create_node(data);
if(NULL == node)
return;
//找到尾结点
while(p->next != NULL)
{
p = p->next;
}
insert_behind(node,p);
}
//删除
void link_del(link_t *p,datatype d)
{
link_t *node = NULL;
//遍历
while(p->next != NULL)
{
//进行数据对比
if(p->next->data == d) //从头结点后第一个有数据的结点开始判断
{
//先保存要删除结点的地址
node = p->next;
// 将p的next指向node的next
p->next = node->next;
//为了安全,在释放前,令它的指针域指向NULL
node->next = NULL;
//释放
free(node);
continue;
}
//往后继续遍历,找相同数据的结点
p = p->next;
}
}
//替换
void link_replace(link_t *p,int old,int new)
{
//遍历找到旧数据所在的地址
while(p->next != NULL)
{
if(p->next->data == old)
{
p->next->data = new;
continue;
}
p = p->next;
}
}
主函数:main.c
#include "link.h"
int main(void)
{
link_t *head = link_init();
if(NULL == head)
return -1;
printf("%p\n",head);
int data,ret;
while(1)
{
printf("请开始头插\n");
ret = scanf("%d",&data);
if(ret == 0)
break;
insert_head(head,data);
display(head);
}
getchar();
while(1)
{
printf("请开始尾插\n");
ret = scanf("%d",&data);
if(ret == 0)
break;
insert_tail(head,data);
display(head);
}
getchar();
while(1)
{
printf("请开始删除\n");
ret = scanf("%d",&data);
if(ret == 0)
break;
link_del(head,data);
display(head);
}
getchar();
int old,new;
while(1)
{
printf("请开始替换\n");
ret = scanf("%d%d",&old,&new);
if(ret == 0)
break;
link_replace(head,old,new);
display(head);
}
return 0;
}
单向循环链表(Singly Circular Linked List)
定义:
- 单向链表的变体,最后一个节点的指针指向头节点,形成一个环。
- 从链表的任何节点出发,都可以遍历整个链表。
特点:
- 无需额外存储头尾信息,适合循环任务。
- 需要注意防止死循环。
#define datatype int
typedef struct link{
datatype data; //数据域
struct link *next; //指向后继结点的指针域
}link_t;
// 头插:头结点(最前面的结点)后面插入
// 尾插:尾结点(最后面的结点)后面插入
1> 初始化link_init
link_t *link_init(void) //造头结点
{
//1>向堆申请
link_t *p = (link_t *)malloc(sizeof(link_t));
if(NULL == p)
{
perror("malloc");
return NULL;
}
//将指针赋值 ,为了安全指向NULL //修改处
p->next = p; //自己指向自己
return p;
}
2> 创建结点create_node
static link_t *create_node(datatype d)
{
//1>向堆空间申请
link_t *p = (link_t *)malloc(sizeof(link_t));
if(NULL == p)
{
perror("malloc");
return NULL;
}
//2>赋值 //修改处
p->data = d;
p->next = p; //指向自己
return p;
}
3> 插入函数insert_behind(static)
//将一个结点(a)插到另一个结点(b)的后面
static void insert_behind(link_t *a,link_t *b)
{
//遵循先连后断
a->next = b->next; //避免b指向的地址丢失
b->next = a;
}
4> 头插函数insert_head
void insert_head(link_t *p,datatype d)
{
//利用传进来的数据,调用创建结点函数
link_t *node = create_node(d);
//将创建出来的node结点插到头结点后面
insert_behind(node,p);
}
5> 遍历展示display
void display(link_t *head)
{
//修改处
link_t *p = head;
//遍历整个链表 //修改处
while(head->next != p) //最后一个结点指向的是头结点
{
//将head指针变量往右移
head = head->next;
printf("%d ",head->data);
}
printf("\n");
}
6> 尾插函数insert_tail
//每插入一个结点进来,将其插入到尾结点后面
void insert_tail(link_t *p,datatype data)
{
//修改处
link_t *head = p;
link_t *node = create_node(data);
if(NULL == node)
return;
//找到尾结点 //修改处
while(p->next != head) //尾结点指向头结点
{
p = p->next;
}
insert_behind(node,p);
}
7> 删除函数link_del
void link_del(link_t *p,datatype d)
{
//修改处,保存头结点的地址
link_t *head = p;
link_t *node = NULL;
//遍历 //修改处
while(p->next != head)
{
//进行数据对比
if(p->next->data == d) //从头结点后第一个有数据的结点开始判断
{
//先保存要删除结点的地址
node = p->next;
// 将p的next指向node的next
p->next = node->next;
//为了安全,在释放前,令它的指针域指向自己 //修改处
node->next = node;
//释放
free(node);
continue;
}
//往后继续遍历,找相同数据的结点
p = p->next;
}
}
8> 修改函数link_replace
void link_replace(link_t *p,int old,int new)
{
//修改处,保存头结点地址
link_t *head = p;
//遍历找到旧数据所在的地址 修改处
while(p->next != head)
{
if(p->next->data == old)
{
p->next->data = new;
continue;
}
p = p->next;
}
}
完整代码(共三个文件:头文件、链表函数和主函数)
头文件: cyclelink.h
#ifndef __CYCLELINK_H__
#define __CYCLELINK_H__
#include <stdio.h>
#include <stdlib.h>
#define datatype int
typedef struct link{
datatype data; //数据域
struct link *next; //指向后继结点的指针域
}link_t;
extern link_t *link_init(void);
extern void insert_head(link_t *p,datatype d);
extern void display(link_t *head);
extern void insert_tail(link_t *p,datatype data);
extern void link_del(link_t *p,datatype d);
extern void link_replace(link_t *p,int old,int new);
#endif
链表函数: cyclelink.c
#include "cyclelink.h"
link_t *link_init(void) //造头结点
{
//1>向堆申请
link_t *p = (link_t *)malloc(sizeof(link_t));
if(NULL == p)
{
perror("malloc");
return NULL;
}
//将指针赋值 ,为了安全指向NULL //修改处**
p->next = p; //自己指向自己
return p;
}
static link_t *create_node(datatype d)
{
//1>向堆空间申请
link_t *p = (link_t *)malloc(sizeof(link_t));
if(NULL == p)
{
perror("malloc");
return NULL;
}
//2>赋值 //修改处
p->data = d;
p->next = p; //指向自己
return p;
}
//将一个结点(a)插到另一个结点(b)的后面
static void insert_behind(link_t *a,link_t *b)
{
//遵循先连后断
a->next = b->next; //避免b指向的地址丢失
b->next = a;
}
void insert_head(link_t *p,datatype d)
{
//利用传进来的数据,调用创建结点函数
link_t *node = create_node(d);
//将创建出来的node结点插到头结点后面
insert_behind(node,p);
}
void display(link_t *head)
{
//修改处
link_t *p = head;
//遍历整个链表 //修改处
while(head->next != p) //最后一个结点指向的是头结点
{
//将head指针变量往右移
head = head->next;
printf("%d ",head->data);
}
printf("\n");
}
//每插入一个结点进来,将其插入到尾结点后面
void insert_tail(link_t *p,datatype data)
{
//修改处
link_t *head = p;
link_t *node = create_node(data);
if(NULL == node)
return;
//找到尾结点 //修改处
while(p->next != head) //尾结点指向头结点
{
p = p->next;
}
insert_behind(node,p);
}
void link_del(link_t *p,datatype d)
{
//修改处,保存头结点的地址
link_t *head = p;
link_t *node = NULL;
//遍历 //修改处
while(p->next != head)
{
//进行数据对比
if(p->next->data == d) //从头结点后第一个有数据的结点开始判断
{
//先保存要删除结点的地址
node = p->next;
// 将p的next指向node的next
p->next = node->next;
//为了安全,在释放前,令它的指针域指向自己 //修改处
node->next = node;
//释放
free(node);
continue;
}
//往后继续遍历,找相同数据的结点
p = p->next;
}
}
void link_replace(link_t *p,int old,int new)
{
//修改处,保存头结点地址
link_t *head = p;
//遍历找到旧数据所在的地址 修改处
while(p->next != head)
{
if(p->next->data == old)
{
p->next->data = new;
continue;
}
p = p->next;
}
}
主函数:main.c
#include "cyclelink.h"
int main(void)
{
link_t *head = link_init();
if(NULL == head)
return -1;
printf("%p\n",head);
int data,ret;
while(1)
{
printf("请开始头插\n");
ret = scanf("%d",&data);
if(ret == 0)
break;
insert_head(head,data);
display(head);
}
getchar();
while(1)
{
printf("请开始尾插\n");
ret = scanf("%d",&data);
if(ret == 0)
break;
insert_tail(head,data);
display(head);
}
getchar();
while(1)
{
printf("请开始删除\n");
ret = scanf("%d",&data);
if(ret == 0)
break;
link_del(head,data);
display(head);
}
getchar();
int old,new;
while(1)
{
printf("请开始替换\n");
ret = scanf("%d%d",&old,&new);
if(ret == 0)
break;
link_replace(head,old,new);
display(head);
}
return 0;
}
综上。希望该内容能对你有帮助,感谢!
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!