双链表的有关学习以及c语言代码实现基本操作

本文详细介绍了双链表的定义、结点类型、初始化、插入、删除、销毁及遍历操作。双链表通过在结点中设置前后驱指针,实现了更快的插入和删除操作,时间复杂度均为O(1)。文中提供了详细的代码示例,确保了在各种情况下操作的健壮性。此外,还讨论了双链表的遍历方式,包括后向和前向遍历,以及遍历的时间复杂度。
摘要由CSDN通过智能技术生成

双链表的定义

背景:由于单链表的结点中只有一个指向其后继的指针,使得单链表只能一次从头结点依次顺序向后遍历。其访问前驱结点的时间复杂度为 O ( n ) O(n) O(n),访问后继结点的时间复杂度 O ( O( O()。

为了克服单链表的问题,我们引入了双链表——一个双链表结点中含有两个指针:prior指针:前驱指针,next:后继指针,二者分别指向前驱结点和后继结点。

双链表的结点类型

//双链表的结点类型
typedf struct DNode{     //定义双链表结构类型
	ElemType data;        //每个节点存放一个数据元素
	struct DNode *prior, *next;    //前驱和后继指针
}DNode,*DLinkList;

双链表的初始化

(带头结点)

typedf struct DNode{     //定义双链表结构类型
	ElemType data;        //每个节点存放一个数据元素
	struct DNode *prior, *next;    //前驱和后继指针
}DNode,*DLinkList;

//DLinkList 等价于 DNode *

// 初始化双链表
bool InitDLinklist(DLinkList &L){
	L =  (DNode *) malloc (sizeof(DNode));  //申请一个头结点
	if(L==NULL)//空表,暂时还没有任何节点,可以防止脏数据;内存不足,分配失败
		return false;
	L->prior=NULL;//头结点的前驱指针永远指向NULL
	L->next =NULL;//初始化时,头结点之后还没有结点
	return ture;
}

void testDLinkList (){
	//初始化一个双链表
	DLinkList L; 
	InitDLinkList(L);
	//....后续代码。。。。。
}

双链表在插入和删除操作的实现上,与单链表的插入,删除操作有着很大的不同。
原因:"链"变化时不仅是对 ∗ n e x t *next next指针的修改,也要注意对 ∗ p r i o r *prior prior指针进行修改。关键在于修改的过程中不断链。
Note:由于双链表很容易找到其前驱结点,因此双链表的插入和删除操作的时间复杂度都是 O ( 1 ) O(1) O(1)

双链表的插入

思路代码

//函数功能:在p结点之后插入s结点
Bool InsertNextDnode(DNode *p,DNode *s){
	s->next =p->next;  //1——p结点的后继指针指向赋予s结点的后继指针
	p->next->prior=s;  //2-p结点的后继指针的前驱指针指向s
	s->prior=p;   //3-s结点的前向指针指向p 
	p->next=s;    //4-p结点的后继指针指向s
//1和2两步必须在4之前

效果过程图

在这里插入图片描述在这里插入图片人 述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
思考:如果p结点是最后一个结点,那么p结点的后继指针就为NULL,导致上述代码存在问题,不符合健壮性。
在这里插入图片描述

为了更严谨一些,来看下面这段代码

严谨代码

//函数功能:在p结点之后插入s结点
Bool InsertNextDnode(DNode *p,DNode *s){
	if(p==NULL || s==NULL)//非法参数,没有插入的必要
		return false;
	s->next =p->next;  //p结点的后继指针指向赋予s结点的后继指针
	if(p->next !=NULL){   //如果p结点有后继结点,就考虑把p结点的后继指针指向s,否则不读下面的代码,本身s的后继指针同样也是指向NULL。
		p->next->prior=s;  //p结点的后继指针的前驱指针指向s
	}
	s->prior=p;   //s结点的前向指针指向p 
	p->next=s;    //p结点的后继指针指向s
}

双链表的删除

逻辑代码

//函数功能:删除p结点的后继结点q
Bool DeleteNextDnode(DNode *p,DNode *s){
	p->next =q->next;  //后继结点——q结点的后继指针指向赋予p结点的后继指针
	q->next->prior=p;  //q结点的后继结点的前驱指针指向p
	free(q);//释放结点空间
}

效果过程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
思考:如果被删结点是尾结点怎么办?为了提高代码的健壮性,大家来看一下代码:

严谨代码

//函数功能:删除p结点的后继结点q
Bool DeleteNextDnode(DNode *p,DNode *s){
	if(p==NUll){  //若p是尾结点,那么它的后继指针指向为为NULL,没有删除的必要
		return false
	}
	DNode *q=p->next;  //先找到p的后继结点
	if(q==NULL){
		return false;   //q是尾结点, 说明p没有后继结点
	p->next =q->next;  //后继结点——q结点的后继指针指向赋予p结点的后继指针
	if(q->next!=NULL){   //q结点不是尾结点
		q->next->prior=p;  //q结点的后继结点的前驱指针指向p
	}
	free(q);//释放结点空间
}

双链表的销毁

利用双链表的删除函数

void DestoryList(DLinkList &L){
	//循环释放结点,删除结点
	while (L-next !=NULL){
		DeleteNextDnode(L);
	}
	free(L);  //释放头结点
	L=NULL//头指针指向NULL

双链表的遍历

后向遍历

while(p!=NULL){
	p=p->next;
}
//例如:对结点p作相应处理,打印

前向遍历

while(p!=NULL){
	p=p->prior;
}
//例如:对结点p作相应处理

注意到:双链表不可以随机存取,所以按位查找和按值查找都只能用遍历的方式实现。
时间复杂度为: O ( n ) O(n) O(n)

总结

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值