【数据结构复习—线性表1.1】

系列文章目录

第一章:线性表的定义和基本操作
第二章:双链表



一、双链表的定义

在上一篇的单链表中,我们知道,单链表结点中只有一个指向其后继的指针,使得单链表只能从头结点开始一次地向后遍历。要访问某个结点的前驱结点(插入、删除操作时),只能从头开始遍历,访问后继结点的时间复杂度为O(1),访问前驱结点的时间复杂度为O(n)。
而双链表就不一样了,双链表结点中有两个指针prior和next,分别指向其前驱指针和后继指针。如图所示:
双链表
双链表中结点类型如下:

typedef int ElemType;
typedef struct DNode {
	ElemType data;
	struct DNode* prior;  //前驱指针
	struct DNode* next;  //后驱指针
}DNode,*DLinkList;

二、双链表的基本操作

在双链表中,按序号查找值和按值查找结点数跟单链表的操作一样,但是在插入和删除上面却是有所不同,在双链表中,需要同时修改两个方向上的指针,在插入的时候需要修改四个指针,在删除的时候需要修改两个指针。两者的时间复杂度为O(n)。

(1)在第i个位置插入元素
跟单链表相比多了一个前驱指针prior,所以在插入上有所不同,在第i个位置插入某元素时需要修改四个指针。图片形式如下:
在其中,①和②的顺序必须是在④之前,否则* p 的后继结点的指针就会丢掉。
插入

代码如下:

//在第i个位置插入元素
bool DListFrontInster(DLinkList DL, int i, ElemType e)
{
	DLinkList p = GetELem(DL, i - 1);
	if (p == NULL)
	{
		return false;
	}
	DLinkList s = (DLinkList)malloc(sizeof(DNode));
	s->data = e;  //如上图第1步
	s->next = p->next;   //如上图第2步
	s->next->prior = p;   //如上图第3步
	p->next = s;   //如上图第4步
	return true;
}

(2)头插法建链表
头插法的话跟上面的在第i个位置插入元素是一个道理,只是说它是在头部插入元素,所以需要写上一个判断条件,去掉判断条件的话则跟上述的插入一样。

代码如下:

//头插法建表
DLinkList HeadInster(DLinkList &DL)
{
	DNode* s; int x;
	DL = (DLinkList)malloc(sizeof(DNode));
	DL->next = NULL;
	DL->prior = NULL;
	scanf_s("%d", &x);
	while (x!=99)
	{
		s = (DLinkList)malloc(sizeof(DNode));
		s->data = x;
		s->next = DL->next;
		if (DL->next != NULL)  //若插入第一个结点,则不执行该语句
		{
			DL->next->prior = s;
		}
		s->prior = DL;
		DL->next = s;
		scanf_s("%d", &x);
	}
	return DL;
}

(3)尾插法建链表
尾插法的话跟单链表的一样,还是增加一个尾指针r,使其始终指向当前链表的尾结点。

代码如下:

//尾插法建表
DLinkList FrontInster(DLinkList& DL)
{
	int x;
	DL = (DLinkList)malloc(sizeof(DNode));
	DNode* s, * r = DL;   //定义尾指针的时候,先执行上面的语句,即先申请空间
	DL->prior = NULL;
	scanf_s("%d", &x);
	while (x!=99)
	{
		s = (DLinkList)malloc(sizeof(DNode));
		s->data = x;
		r->next = s;
		s->prior = r;
		r = s;
		scanf_s("%d", &x);
	}
	r->next = NULL;
	return DL;
}

(4)删除元素
在大体上还是跟单链表的删除操作一样,只是多了一个对前驱指针的处理。
图片形式如下:
删除

代码如下:

//删除元素
bool DeleteDList(DLinkList DL, int i)
{
	DLinkList p = GetELem(DL, i - 1);
	if (p == NULL)
	{
		return false;
	}
	DLinkList q = p->next;
	if (q == NULL)
	{
		return false;
	}
	p->next = q->next;
	if (q->next != NULL)
	{
		q->next->prior = p;
	}
	free(q);
	q = NULL;
	return true;
}

三、循环链表

(1)循环单链表
循环链表是另一种形式的链式存储结构。其特点是表中最后一个结点的指针域不是NULL,而是指向头结点,整个链表形成一个环,因此,从表中任一结点出发均可找到表中的其他结点。
如图所示:
循环单链表
在单链表中只能从表头或者表尾结点按顺序往后遍历,而循环单链表可以从表中的任意一个结点开始遍历整个链表。如果在存在头指针的情况下,对表尾操作的话时间复杂度为O(n),而只有尾指针 r 的话,r->next即为头指针,对表头和对表尾进行操作时间复杂度为O(1),这样就能使得操作效率更高。

(2)循环双链表
循环双链表的定义与循环单链表的大同小异,不同的是在前驱结点上,头结点的prior指针还要指向表尾结点。如图所示:
循环双链表
在循环双链表中,某结点* p为尾结点时,p->next==L; 当循环双链表为空表时,其头结点的prior域和next域都等于L。

四、静态链表

静态链表借助数组来描述线性表的链式存储结构,结点也有数据域data和指针域next,与链表中的指针不同的是,这里的指针是结点的相对地址(数组下标),又称“游标”,和顺序表一样,静态链表也要预先分配一块连续的内存空间。

静态链表结构类型如下:

#define MaxSize 50
typedef struct{
	ElemType data;
	int next;  //下一个元素的数组下标
}SLinkList{MaxSize};

静态链表以next==1作为其结束的标志。静态链表的插入、删除操作与动态链表的相同,只需要修改指针,而不需要移动元素,

五、单链表和双链表的区别

(1)存取(读写)方式
顺序表可以顺序存取,也可以随即存取,链表只能从表头顺序存取元素,例如在第 i 个位置上执行存取的操作,顺序表仅需一次访问,而链表则需从表头开始一次访问 i 次。
(2)逻辑结构与物理结构
采用顺序存储时,逻辑上相邻的元素,对于的物理存储位置也相邻。而采用链式存储时,逻辑上相邻的元素,在物理存储位置上并不一定相邻,对于的逻辑关系是通过指针链接来表示的。
(3)查找、插入和删除操作
对于按值查找,顺序表无序时,两者的时间复杂度均为O(n),顺序表有序时,可采用折半查找,此时的时间复杂度为O(log2n)。
对于按序号查找,顺序表支持随即访问,时间复杂度为O(1),而链表的平均时间复杂的为O(n)。顺序表的插入、操作和删除操作,平均需要移动半个表长的元素,链表的则只需要修改相关结点的指针域即可。由于链表的每个结点都带有指针域,故存储密度不大。
(4)空间分配
顺序存储在静态存储分配的情形下,一旦存储空间装满就不能扩充,若在加入新元素,则会使得现内存溢出,因为需要预先分配足够大的存储空间。预先分配过大,可能会导致顺序表后部大量闲置;预先分配过小,又会造成溢出。动态存储分配虽然存储空间可以扩充,但是需要移动大量元素,导致操作效率降低,而且若内存中没有更大的连续存储空间,则会导致分配失败。链式存储的结点只在需要的时候申请分配,需要内存有空间就可以分配。

总结

这就是线性表这一节的几个知识点,重点是单链表和双链表,因为顺序表的话使用的都不算多,因为在一些操作上没有链表的使用方便,循环链表和静态链表只做了解就行了。
而在链表这一个知识点上,需要明白指向关系,结点之间的关系,执行的先后关系,在使用一个新的结点的时候记得要先申请空间。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值