数据结构与算法(2-2)线性表之链式存储(单链表、静态链表、循环链表、双向循环链表)

目录

一、单链表

1、存储方式

2、插入

3、删除

总代码: 

 二、静态链表

1、存储方式

2、插入

3、删除

4、遍历

总代码:

三、循环链表

 总代码:

四、双向循环链表

1、存储方式:

2、插入和删除

3、正向遍历与反向遍历

 总代码:


一、单链表

每个数据存储在结点中,单链表负责把这些结点串起来。(主要利用*next指针)

1、存储方式

从根结点开始(根结点可以不存储元素),用*next指针指向后面元素的地址,后面元素的*next继续指向更后面的元素,以此类推。(添加或插入元素要用malloc函数动态分配内存空间)头指针指向根,尾指针指向最后结点,便于插入删除。

//结点结构体
typedef struct Node
{
	int data;						//数据域
	struct Node* next;		//指针域
}Node;
Node* head;
Node* tail;

2、插入

前面元素的*next断开,指向插入元素,插入元素的*next又指向后面的元素,完成“挤入”式插入。 

//插入元素
void Insert(int index, int num)
{
	int i = 0;
	if (index < getLength() && index >= 0)		//插入下标有效
	{
		Node* s = (Node*)malloc(sizeof(Node)), * p = head;
		for (i = 0; i < index; i++)
			p = p->next;
		s->data = num;				//插入的元素值
		s->next = p->next;			//s->后面
		p->next = s;						//前面->s
	}
	else
		printf("不在插值范围内!");
}

3、删除

待删除元素前面元素的*next断开,指向待删除元素后面的元素,释放掉待删除元素的空间。(前面malloc分配的内存需要释放)

//删除结点	(必须有外变量暂时保存要删除的结点)
//如果删除的是head结点,直接把head指向后面的结点即可
void Delete_Node(int index)
{
	int i = 0;
	Node* p = head, * s = NULL;
	if (index < getLength() && index >= 0)		//删除下标有效
	{
		for (i = 0; i < index; i++)
			p = p->next;						//移动到前一位
		s = p->next;								//要删除的结点
		p->next = p->next->next;		//前结点next->后结点(跳过要删除的结点)
		free(s);
	}
	else
		printf("没有该下标的结点!\n");
}

总代码: 

//单链表
//动态内存分配malloc函数:			(指定的指针类型)malloc(要分配的字节数)
//头结点保存链表的首,可以在尾结点进行一系列操作(如添加),p表示动态的指针
#include <stdio.h>
#include <malloc.h>

int i = 0;

//结点结构体
typedef struct Node
{
	int data;						//数据域
	struct Node* next;		//指针域
}Node;
Node* head;
Node* tail;

//初始化链表
void Init_List()
{
	//初始化链表
	head = (Node*)malloc(sizeof(Node));
	tail = (Node*)malloc(sizeof(Node));
	head = tail;
}

//创建结点
void Add_Node(int Data)
{
	Node* p = (Node*)malloc(sizeof(Node));
	p->data = Data;
	p->next = NULL;
	//指针后移
	tail->next = p;
	tail = p;
}

//获取链表长度
int getLength()
{
	int num = 0;
	Node* p = head->next;					//第一个数据存放在head->next里面
	while (p)
	{
		num++;
		p = p->next;
	}
	return num;
}

//遍历链表
void Print()
{
	Node* p = head->next;					//第一个数据存放在head->next里面
	while (p)
	{
		printf("%d\n", p->data);
		p = p->next;
	}
}

//插入元素
void Insert(int index, int num)
{
	int i = 0;
	if (index < getLength() && index >= 0)		//插入下标有效
	{
		Node* s = (Node*)malloc(sizeof(Node)), * p = head;
		for (i = 0; i < index; i++)
			p = p->next;
		s->data = num;				//插入的元素值
		s->next = p->next;			//s->后面
		p->next = s;						//前面->s
	}
	else
		printf("不在插值范围内!");
}

//删除结点	(必须有外变量暂时保存要删除的结点)
//如果删除的是head结点,直接把head指向后面的结点即可
void Delete_Node(int index)
{
	int i = 0;
	Node* p = head, * s = NULL;
	if (index < getLength() && index >= 0)		//删除下标有效
	{
		for (i = 0; i < index; i++)
			p = p->next;						//移动到前一位
		s = p->next;								//要删除的结点
		p->next = p->next->next;		//前结点next->后结点(跳过要删除的结点)
		free(s);
	}
	else
		printf("没有该下标的结点!\n");
}

//清空链表
void Clear_List()
{
	Node* p = head->next, * q;
	while (p)
	{
		q = p->next;
		free(p);
		p = q;
	}
	head->next = NULL;
}

int main()
{
	//初始化链表
	Init_List();

	//创建链表
	for (i = 0; i < 5; i++)
	{
		Add_Node(i);
	}

	//插入结点
	Insert(0, 5);

	//删除结点(第二次删除注意:位置改变)
	Delete_Node(0);

	//清空链表
	Clear_List();

	//遍历
	Print();

	return 0;
}

 二、静态链表

以数组的方式存储,虽然需要分配较大的内存空间,但是插入/删除比较容易,不需要移动数组元素,只用改变游标(cur)指向即可即可。

1、存储方式

创建一个数组(index是下标,cur是游标)

cur(游标):存放前一个元素的index,通过它实现连接。

整个链表被分成两部分:1、有值链表(存放有元素)2、备用链表(不存放元素)

首元素(index=0)的cur:                           存放备用链表的首元素。

尾元素(index=MAXSIZE-1)的cur:        存放有值链表的首元素。

链表构成:1、以首元素为首构成的备用链表;        2、以尾元素为首构成的有值链表。

//静态链表结构体
typedef struct
{
	int data;			//数据
	int cur;				//游标
}StaticList;
StaticList L[MAXSIZE];

2、插入

只移动游标,不移动元素。

//插入链表
void Insert(int index, int num)
{
	int pre = MAXSIZE - 1;
	if (index <= 0 || index > L[0].cur)			//插入范围无效(第1个和最后一个元素不存放数据)
	{
		printf("%d Insert Error!\n", num);
		return;
	}
	//移动到前一个元素(有效元素)
	for (i = 1; i < index; i++)
		pre = L[pre].cur;							

	L[0].cur = L[index].cur;				//链表首元素游标指向备用链表首元素
	L[index].data = num;
}

3、删除

被删除的元素作为备用链表首元素,其游标连接原备用链表首元素,原备用链表首元素变成第二个元素(向后移)。

//删除链表(把需要删除的元素降级,变为备用链表的首元素)
void Delete(int index)
{
	int pre = MAXSIZE - 1, j;
	if (index < 1 || index >= L[0].cur)
	{
		printf("要删除的下标无效!\n");
		return;
	}

	for (i = 1; i <= index - 1; i++)
		pre = L[pre].cur;						//移动到需要删除的前一个位置(有元素位)

	L[index].cur = L[0].cur;					//1、游标指向原备用元素(被删除的元素作为备用元素的首)
	L[0].cur = index;							//2、更新备用链表首元素(被删除的元素)
	L[pre].cur = index;						//3、连接前一个元素和后一个元素(游标)
}

4、遍历

从index = MAXSIEZE-1处开始遍历(从尾元素开始遍历),因为尾元素的cur存有有效链表的首元素。

//遍历链表
void Traverse()
{
	int cur;
	cur = L[MAXSIZE - 1].cur;			//指向有效链表的首元素
	for (i = 1; i < L[0].cur; i++)			//从第一个开始,到最后一个结束(备用链表的首元素就是最后一个元素的下一个元素)
	{
		printf("%d\n", L[cur].data);
		cur = L[cur].cur;						//有效链表中继续后移
	}
}

总代码:

//静态链表
//插入/删除时只修改游标,不移动结点
//游标存放下标,前一元素游标存放后一元素的下标
//第一个元素游标:	存放备用链表第一个元素的下标
//最后一个元素游标:存放有元素链表第一个元素的下标
//把有效元素作为一个循环,用pre=MAXSIZE-1标志开始,因为L[MAXSIZE-1].cur指向有效链表首元素,相当于在有效链表内循环
#include <stdio.h>

#define MAXSIZE 20
int i = 0;

typedef struct
{
	int data;
	int cur;
}StaticList;
StaticList L[MAXSIZE];

//链表初始化
void List_Init()
{
	//初始化游标
	for (i = 0; i < MAXSIZE; i++)
	{
		L[i].cur = i + 1;							//游标指向下一个
	}
	L[MAXSIZE - 1].cur = 1;				//最后一个元素的游标(有效元素)
}

//根据游标获取下标(这里没用到)
int getIndex(int Cur)
{
	for (i = 0; i < MAXSIZE; i++)
	{
		if (L[i].cur == Cur)		//找到对应游标的元素
			return i;					//返回下标
	}
	return -1;							//没有找到
}

//获取长度
int getLength()
{
	int num = 0;
	i = L[MAXSIZE - 1].cur;
	if (L[0].cur == L[MAXSIZE - 1].cur)			//没有元素
		return 0;

	while (i < L[0].cur)
	{
		i = L[i].cur;
		num++;
	}
	return num;
}

//插入链表
void Insert(int index, int num)
{
	int pre = MAXSIZE - 1;
	if (index <= 0 || index > L[0].cur)			//插入范围无效(第1个和最后一个元素不存放数据)
	{
		printf("%d Insert Error!\n", num);
		return;
	}
	//移动到前一个元素(有效元素)
	for (i = 1; i < index; i++)
		pre = L[pre].cur;							

	L[0].cur = L[index].cur;				//链表首元素游标指向备用链表首元素
	L[index].data = num;
	L[0].cur = L[index].cur;				//更新备用链表
}

//删除链表(把需要删除的元素降级,变为备用链表的首元素)
void Delete(int index)
{
	int pre = MAXSIZE - 1, j;
	if (index < 1 || index >= L[0].cur)
	{
		printf("要删除的下标无效!\n");
		return;
	}

	for (i = 1; i <= index - 1; i++)
		pre = L[pre].cur;						//移动到需要删除的前一个位置(有元素位)

	L[index].cur = L[0].cur;					//1、游标指向原备用元素(被删除的元素作为备用元素的首)
	L[0].cur = index;							//2、更新备用链表首元素(被删除的元素)
	L[pre].cur = index;						//3、连接前一个元素和后一个元素(游标)
}

//遍历链表
void Traverse()
{
	int cur;
	cur = L[MAXSIZE - 1].cur;			//指向有效链表的首元素
	for (i = 1; i < L[0].cur; i++)			//从第一个开始,到最后一个结束(备用链表的首元素就是最后一个元素的下一个元素)
	{
		printf("%d\n", L[cur].data);
		cur = L[cur].cur;						//有效链表中继续后移
	}
}

int main()
{
	List_Init();						//链表初始化

	printf("插入前元素总数:%d\n", getLength());

	Insert(1, 6);					//插入链表
	Insert(2, 8);
	Insert(3, 10);

	printf("删除前元素总数:%d\n", getLength());

	Delete(3);
	Delete(2);						//删除链表
	Insert(2, 8);

	printf("删除后元素总数:%d\n", getLength());

	Traverse();

	return 0;
}

三、循环链表

循环链表顾名思义,尾连接头,实现循环。

存储结构和链表类似,就多一个头尾相接,完成循环。

 总代码:

//循环链表
//用了两个指针,一个指向头,一个指向尾,尾连接上头,完成循环链表
#include <stdio.h>
#include <malloc.h>

int i = 0;

//结构体
typedef struct Node
{
	int data;
	struct Node* next;
}Node;
Node* head;			//头结点
Node* tail;				//尾结点
Node* t;					//遍历结点

//链表初始化
void List_Init()
{
	head = (Node*)malloc(sizeof(Node));			//分配地址
	tail = (Node*)malloc(sizeof(Node));			//分配地址
	head->next = tail;
	tail->next = head;
}

//获取链表长度
int getLength() {
	int num = 0;
	Node* L = head;
	while (L->next != tail)
	{
		L = L->next;
		num++;
	}
	return num;
}

//插入链表
void Insert(int index, int num)
{
	if (index<0 || index>getLength())
	{
		printf("%d 序号不在范围内\n", num);
		return;
	}
	Node* p = (Node*)malloc(sizeof(Node)), * L;
	p->next = NULL;
	L = head;							//指向头
	while (L->next != tail)		//移动到尾结点前面(最后一个有效结点)
		L = L->next;
	L->next = p;
	p->data = num;
	tail = p->next;					//更新尾结点
}

//删除链表
void Delete(int index)
{
	if (index < 0 || index >= getLength())
	{
		printf("无该序号\n");
		return;
	}
	Node* L = head, * t;
	for (i = 0; i < index; i++)
		L = L->next;

	t = L->next;
	L->next = L->next->next;
	free(t);
}

//遍历链表
void Traverse()
{
	Node* L = head;
	while (L->next != tail)				//头尾循环
	{
		L = L->next;
		printf("%d\n", L->data);
	}
}

int main()
{
	List_Init();					//链表初始化

	Insert(0, 0);				//插入链表
	Insert(1, 1);
	Insert(2, 2);

	Delete(2);					//删除链表
	Delete(1);					//第二次删除位置改变

	Traverse();						//遍历链表

	return 0;
}

四、双向循环链表

双向循环链表优势:可以完成双向的遍历。

1、存储方式:

双向:*prior指针和*next指针分别指向前后两个元素。

循环:头结点的*prior指向尾结点,尾结点的*next指向头结点。

//双向循环链表
typedef struct Node
{
	int data;
	struct Node* prior;
	struct Node* next;
}Node;

2、插入和删除

插入:一个遍历链表,另一个进行插入(一开始不要进入链表,散置,不然会一直循环)。
删除:也要同时考虑next和prior指针。

3、正向遍历与反向遍历

正向遍历:从头结点head出发,向尾部遍历,直到遇到tail结束遍历。

反向遍历:从尾结点tail出发,向头部遍历,直到遇到head结束遍历。

//正向遍历链表
void Traverse()
{
	Node* p = head;
	printf("正向遍历:\n");
	while (p->next != tail)
	{
		p = p->next;
		printf("%d\n", p->data);
	}
}

//反向遍历链表
void AntiTraverse()
{
	Node* p = tail;
	printf("反向遍历:\n");
	while (p->prior != head)
	{
		p = p->prior;
		printf("%d\n", p->data);
	}
}

 总代码:

//双向循环链表
//前指针和后指针分别连接前面和后面的元素
//插入:一个遍历链表,另一个进行插入(一开始不要进入链表,散置,不然会一直循环)
//删除:也要同时考虑next和prior指针
#include <stdio.h>
#include <malloc.h>

//双向循环链表
typedef struct Node
{
	int data;
	struct Node* prior;
	struct Node* next;
}Node;

Node* head;			//头
Node* tail;				//尾
int i = 0;

//链表初始化
void Init_List()
{
	head = (Node*)malloc(sizeof(Node));
	tail = (Node*)malloc(sizeof(Node));
	head->prior = tail;
	head->next = tail;
	tail->next = head;
	tail->prior = head;
	//赋一个值,便于调试
	tail->data = -2;
	head->data = -1;
}

//获取元素数量
int getLength()
{
	int num = 0;
	Node* p = head;
	while (p->next != tail)
	{
		p = p->next;
		num++;
	}
	return num;
}

//链表插入
void Insert(int index, int num)
{
	if (index<0 || index>getLength())
	{
		printf("插入下标不正确!\n");
		return;
	}
	Node* t = (Node*)malloc(sizeof(Node)), * p;
	p = head;
	for (i = 0; i < index; i++)
	{
		p = p->next;					//移动到前一个元素
	}
	t->prior = p;
	t->next = p->next;
	p->next->prior = t;				//这一步要在前面
	p->next = t;							//这一步要在后面,负责p->next已经改变,前面的没有意义了
	t->data = num;
}

//删除链表
void Delete(int index)
{
	if (index < 0 || index >= getLength())
	{
		printf("删除下标不正确!\n");
		return;
	}
	Node* p = head, * s;
	for (i = 0; i < index; i++)
		p = p->next;
	s = p->next;
	p->next->next->prior = p;
	p->next = p->next->next;
	free(s);
}

//正向遍历链表
void Traverse()
{
	Node* p = head;
	printf("正向遍历:\n");
	while (p->next != tail)
	{
		p = p->next;
		printf("%d\n", p->data);
	}
}

//反向遍历链表
void AntiTraverse()
{
	Node* p = tail;
	printf("反向遍历:\n");
	while (p->prior != head)
	{
		p = p->prior;
		printf("%d\n", p->data);
	}
}

int main()
{
	Init_List();						//链表初始化

	Insert(0, 0);					//插入元素
	Insert(1, 1);
	Insert(2, 2);

	Delete(2);					//删除元素

	Traverse();						//正向遍历
	AntiTraverse();					//反向遍历

	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_(*^▽^*)_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值