数据结构————线性表(下)

读写方式顺序表可以随机存取,也可以顺序存取;链表只能顺序存储。插入/删除时移动元素的个数顺序表平均需要移动近一半元素;链表不需要移动元素,只需要修改指针。存储结构的方式顺序表相邻的元素在存储时也是相邻的,链表相邻的节点存储时是不相邻的存储密度的比较(存储密度=结点值域所占的存储量/结点结构所占的存储总量)顺序表的存储密度=1,链表的存储密度
摘要由CSDN通过智能技术生成

目录

知识框架

​编辑

循环链表

循环链表的概念

仅带头循环链表

仅带尾循环链表

头节点的作用

循环链表的优点

双向链表

双向链表的概念

 双向循环链表的初始化

双向循环链表的插入

双向循环链表的删除

双向循环链表的查找

双向循环链表的修改

双向循环链表的打印

双向循环链表的销毁

 运行结果比对

 总结

顺序表和链表的对比


知识框架

 在上一节中,我们介绍了线性表的概念,普通顺序表和单链表的增删查改,在这一章中我们将进一步的学习链表,详细介绍上节课中单链表中存在的一些问题和解决方法。

常见链表的类型:

1.单向不带头不循环链表     2.单向不带头循环链表   3.单向带头循环链表    4.单向带头不循环链表

5.双向不带头不循环链表     6.双向不带头循环链表    7.双向带头循环链表    8.双向带头不循环链表

循环链表

循环链表的概念

 循环链表是另一种形式的链式存储结构。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。

循环链表的结点包括两个部分:数据域和指针域。

①数据域(data),用于存储该结点的数据元素,数据元素类型由应用问题决定。
②指针域(link),用于存放一个指针,该指针指向下一个结点的开始存储地址。

仅带头循环链表

如图所示空带头循环链表,当循环链表为空时其指针域存储的是其本身,当链表不为空时,最后一个节点的指针域指向头节点。

注意:带头结点的头指针就指向头结点.如果当链表为空的时候,头结点的指针域的数值为NULL.

仅带尾循环链表

对于上述带头的循环链表,我们想要访问它的第一个节点是非常快的,时间复杂度为O(1),但是我们想要去访问最后一个节点的数据就要付出很大的代价,遍历链表,时间复杂度为O(N),那么我们就想出了一种解决方法,带尾循环链表。

 我们可以取其所长将上面两个链表合并后得到 

头节点的作用

1、防止单链表是空的而设的.当链表为空的时候,带头结点的头指针就指向头结点.如果当链表为空的时候,头结点的指针域的数值为NULL.

2、是为了方便单链表的特殊操作,插入在表头或者删除第一个结点.这样就保持了单链表操作的统一性!

3、单链表加上头结点之后,无论单链表是否为空,头指针始终指向头结点,因此空表和非空表的处理也统一了,方便了单链表的操作,也减少了程序的复杂性和出现bug的机会  。

循环链表的优点

1.循环链表中没有NULL指针;

2.循环链表无须增加存储量。循环链表中没有NULL指针优点在于仅对表的链接方式稍作改变,即可使得表处理更加方便灵活。涉及遍历操作时,其终止条件就不再是像非循环链表那样判别p或p->next是否为空,而是判别它们是否等于某一指定指针。

双向链表

双向链表的概念

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表

双向循环链表:双向循环链表将双向链表的头结点和尾结点链接起来也能构成循环链表,其称为双向循环链表

 

typedef int SLTDaTaType;//更方便以后我们更换需要的数据类型

typedef struct ListNode
{

	SLTDaTaType data;
	struct SListNode* next;//指向下一个结构体的地址
	struct SListNode* prev;
}ListNode;

 双向循环链表的初始化

初始化双向循环链表,创建一个节点,不储存数据,方便判断链表的一次遍历结束,

ListNode* BuyListNode1()
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	node->next = NULL;
	node->prev = NULL;
	return node;
}

ListNode* ListInit()
{
	ListNode* phead = BuyListNode1();
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

双向循环链表的插入

void ListpushBack(ListNode* phead, SLTDaTaType x)
{
	assert(phead);

	ListNode* tail = phead->prev;//先把phead指向的前一个指针保存下来
	ListNode* newnode = BuyListNode(x);

	tail->next = newnode;
	newnode->next = phead;
	newnode->prev = tail;
	phead->prev = newnode;
}

void ListpushFront(ListNode* phead, SLTDaTaType x)
{
	assert(phead);
	ListNode* newnode = BuyListNode(x);
	newnode->next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
}

双向循环链表的删除

void ListpopFront(ListNode* phead)
{
	assert(phead);
	assert(phead != phead->next);
	ListNode* pop = phead->next;
	phead->next = pop->next;
	free(pop);	
}
void ListpopBack(ListNode* phead)
{
	assert(phead);
	assert(phead != phead->next);
	ListNode* pop = phead->prev;
	ListNode* popp = pop->prev;
	popp->next = phead;
	phead->prev = popp;
	free(pop);
}

双向循环链表的查找

ListNode* ListFind(ListNode* phead, SLTDaTaType x)
{
	ListNode* cur = phead->next;
	while (cur!=phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
		return NULL;
}

双向循环链表的修改

//指定位置插入
void ListInsret(ListNode* p, SLTDaTaType x)
{
	ListNode* sert = BuyListNode(x);
	ListNode *pprev = p->prev;
	sert->next = p;
	sert->prev = p->prev;
	p->prev = sert;
	pprev->next = sert;
}
//删除当前位置
void ListEarse(ListNode* p)
{
	assert(p);
	ListNode* pprev = p->prev;
	ListNode* pnext = p->next;
	
	pprev->next = pnext;
	pnext->prev = pprev;
	free(p);
}

双向循环链表的打印

void Listprint(ListNode* phead)
{
	ListNode* cur = phead->next;
	while (cur!=phead)
	{
		printf("%d  ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

双向循环链表的销毁

void ListDestory(ListNode* phead)
{
	ListNode* cur = phead->next;
	while (cur!=phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}

}

 运行结果比对

#include "List.h"

void test()
{
	ListNode* plist = ListInit();
	ListpushBack(plist, 1);
	ListpushBack(plist, 2);
	ListpushBack(plist, 3);
	ListpushBack(plist, 4);
	ListpushBack(plist, 5);
	ListpushFront(plist, 0);
	ListpushFront(plist, -1);

	Listprint(plist);

	ListpopBack(plist);
	ListpopFront(plist);

	Listprint(plist);

	ListNode*p=ListFind(plist, 3);
	if (p)
	{
		ListInsret(p, 20);
	}
	Listprint(plist);

	ListEarse(p);
	Listprint(plist);

	printf("%d\n", ListSize(plist));


	ListDestory(plist);
	plist = NULL;


}
int main()
{
	test();

	return 0;
}

 总结

顺序表和链表的对比

读写方式

顺序表可以随机存取,也可以顺序存取;链表只能顺序存储。

 插入/删除时移动元素的个数

顺序表平均需要移动近一半元素;链表不需要移动元素,只需要修改指针。

存储结构的方式

顺序表相邻的元素在存储时也是相邻的,链表相邻的节点存储时是不相邻的

存储密度的比较

(存储密度=结点值域所占的存储量/结点结构所占的存储总量)
顺序表的存储密度=1,链表的存储密度<1.

  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 32
    评论
评论 32
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

善始善终!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值