JavaScript数据结构之双向链表,C语言也适用!

双向链表又称“双向循环链表”,相较于单链表,双向链表增加了一个指向前面一个结点的指针,链表内的结点不会存在NULL结点,文章有点长,但是干货满满,希望你能看完。

目录

一、初识双向链表

二、双向链表初始化

三、添加结点

3.1头插法

 3.2尾插法

3.3指定位置插入

四、删除结点

4.1删除指定位置结点

4.2删除整表

五、获取结点 

附:完整源码:


一、初识双向链表

 在双向链表中,每个结点都有两个指针域(prior和next)和一个数据域(data),第一个结点称”头结点“,头结点不存储数据,但它存储了指向下一个结点的地址和最后一个结点的地址,从而实现了双向和循环。反观单链表,只有一个next,每个节点只能访问自己的下一个结点,对于CRUD等操作都没有双向链表灵活。

二、双向链表初始化

 来看上面这张图,对于一个空的双向链表只有一个头结点,上面说过了头结点不带有数据,因此空的双向链表的next指针存储的就是我自己,prior(前指针)也指向我自己,这就相当于一个空的循环链表,所以通过代码实现就是上图的:this.prior=this.next=this;

我们这一次用DoubleLinkList来描述一个双向链表,使用构造函数就是

function DoubleLinkList()
{
    //初始化头结点
	this.prior=this.next=this;
}

对,就是这么简简单单的一句话,每次实例化一个DoubleLinkList类的对象的时候都会做一遍这个初始化,自动生成一个空双向链表。

但是貌似还缺少什么?眼力见好的小伙伴可能已经发现了:每个节点不是说都有两个指针域和一个数据域么?对的,这也属于初始化工作的一部分,但是这部分的初始化我们用一个内部类去封装,就叫做结点类DoubleNode

//相当于内部类,保存数据域和两个指针域
function DoubleNode(data){
	this.data=data;
	this.prior=null;
	this.next=null;
}

至此,初始化工作全部结束,接下来就可以写我们的业务逻辑:CRUD

三、添加结点

首先来简单讲一下增加结点的核心逻辑:

 如图,在双向链表的任何一个位置插入结点的代码都是完全一样的,假设我们实例化得到了一个新结点node,我们想在p结点1和p.next结点3的位置中间把这个新结点node4插入进去,就需要破坏原有的1和3结点的next和prior,如何破坏呢?首先将node的prior指向p,再将node.next指向p.next,此时完成了node结点的prior和next指针的赋值,但是还没有破坏p和p.next的两个指针,再来将p.next.prior指向node,此时就破坏了上图的蓝色箭头(画了X的),最后将p.next指向node,就破坏了上图绿色箭头,至此新结点node就插进去了。四个步骤不能乱,否则某个结点的地址就会丢失

3.1头插法

 OK,理解了上面的插入,接下来头插法就好理解了

//头插法
DoubleLinkList.prototype.insertDouListByHead=function(data){
	const node = new DoubleNode(data);
	let p=this;
    
    //核心代码
	node.prior = p;
	node.next = p.next;
	p.next.prior = node;
	p.next = node;
}

 3.2尾插法

尾插法类似,只是他需要寻找到最后一个结点,于是判断条件就是p.next!==this

DoubleLinkList.prototype.insertDouListByTail=function(data){
	const node=new DoubleNode(data);

	let p=this.next;
	while(p.next!==this){
		p=p.next;
	}

	 //核心代码
	node.prior = p;
	node.next = p.next;
	p.next.prior = node;
	p.next = node;
}

3.3指定位置插入

指定位置插入,需要用户传入一个Pos位置,原理类似

//任意位置插入结点
DoubleLinkList.prototype.insertDouListByPos=function(data,Pos){
	const node=new DoubleNode(data);

	let p=this,i=0;
	while(i<Pos-1 && p.next!==this){
		i++;
		p=p.next;
	}

	 //核心代码
	node.prior = p;
	node.next = p.next;
	p.next.prior = node;
	p.next = node;
}

四、删除结点

再来看删除结点的核心逻辑:

 相对于添加,删除似乎简单许多,主要是要破坏p结点的前后指针域,根据上图所画相信大家一目了然,哦~不过如此嘛!只不过不仅要破坏还要重新将p的前后两个结点连起来,就是上图红色的两个箭头,要不然就是删除包括p在内的后面的所有结点了!

4.1删除指定位置结点

//指定位置删除
DoubleLinkList.prototype.deleteNode=function(Pos){
	if(this.prior===this && this.next===this){
		console.log(new Error('链表为空,无法删除'));
		return false;
	}

	let i = 0, p = this;
	while (i < Pos && p.next !== this) {
        i++;
	    p = p.next;
	}

	if (Pos > i || Pos < 1) {
		console.log(new Error('该位置不存在'));
		return false;
	}


	p.prior.next=p.next;
	p.next.prior = p.prior;
}

这段代码头尾部分应该不用多讲,中间部分就是在寻找需要被删除的那个结点

4.2删除整表

//整表删除
DoubleLinkList.prototype.clear=function(){
	if(this.prior===this && this.next===this){
		console.log(new Error('链表为空,无法清空链表'));
		return false;
	}
	let p=this.next;
	let i=1;
	while(p!==this){
		this.deleteNode(1);
		p=p.next;
	}
}

删除整表就是从头结点的下一个结点开始一个一个删除,这里直接调用了上面的删除结点方法,每次删除第一个,直至全部删除。

五、获取结点 

//获取指定位置的结点
DoubleLinkList.prototype.getNode=function(Pos){
	if(this.prior===this && this.next===this){
		console.log(new Error('链表为空,无法获取'));
		return false;
	}

	let i = 0, p = this;
	while (i < Pos && p.next !== this) {
        i++;
	    p = p.next;
	}

	if (Pos > i || Pos < 1) {
		console.log(new Error('该位置不存在'));
		return false;
	}

	return p.data;
}

这个和删除结点没有太大的区别,唯一的区别就是最后只需要将获取结点的数据域返回出去即可。

附:完整源码:

'use strict'

//双向链表
const DoubleLinkList=function(){
const wm=new WeakMap();
//双向链表构造函数
function DoubleLinkList(){
	//初始化头结点
	this.prior=this.next=this;

	//定义私有方法
	const privates=wm.get(this) || {};
	this.privateInsert=Symbol('private'); //私有方法
	this.privateFind=Symbol('private'); //私有方法
	privates[this.privateInsert]=privateInsert.bind(this); //插入
	privates[this.privateFind]=privateFind.bind(this);
	wm.set(this,privates);
}

//相当于内部类,保存数据域和两个指针域
function DoubleNode(data){
	this.data=data;
	this.prior=null;
	this.next=null;
}

//将头插法、尾插法相同部分的代码抽取出来,进一步封装
function privateInsert(node,p) {
	node.prior = p;
	node.next = p.next;
	p.next.prior = node;
	p.next = node;
}

//寻找结点的公共部分,封装
function privateFind(Pos){
	let i = 0, p = this;
	while (i < Pos && p.next !== this) {
		i++;
		p = p.next;
	}

	if (Pos > i || Pos < 1) {
		console.log(new Error('该位置不存在'));
		return false;
	}

	return p;
}

//头插法
DoubleLinkList.prototype.insertDouListByHead=function(data){
	const node = new DoubleNode(data);
	let p=this;

	wm.get(this)[this.privateInsert](node,p);
}

//尾插法
DoubleLinkList.prototype.insertDouListByTail=function(data){
	const node=new DoubleNode(data);

	let p=this.next;
	while(p.next!==this){
		p=p.next;
	}
	wm.get(this)[this.privateInsert](node,p);
}

//任意位置插入结点
DoubleLinkList.prototype.insertDouListByPos=function(data,Pos){
	const node=new DoubleNode(data);

	let p=this,i=0;
	while(i<Pos-1 && p.next!==this){
		i++;
		p=p.next;
	}
	wm.get(this)[this.privateInsert](node,p);
}

//指定位置删除
DoubleLinkList.prototype.deleteNode=function(Pos){
	if(this.prior===this && this.next===this){
		console.log(new Error('链表为空,无法删除'));
		return false;
	}

	let p=wm.get(this)[this.privateFind](Pos);

	p.prior.next=p.next;
	p.next.prior = p.prior;
}

//获取指定位置的结点
DoubleLinkList.prototype.getNode=function(Pos){
	if(this.prior===this && this.next===this){
		console.log(new Error('链表为空,无法获取'));
		return false;
	}

	let p=wm.get(this)[this.privateFind](Pos);

	return p.data;
}

//整表删除
DoubleLinkList.prototype.clear=function(){
	if(this.prior===this && this.next===this){
		console.log(new Error('链表为空,无法清空链表'));
		return false;
	}
	let p=this.next;
	let i=1;
	while(p!==this){
		this.deleteNode(1);
		p=p.next;
	}
}

return DoubleLinkList;
}();

双向链表的学习就到此结束了。

如果对你有用,别忘了点个收藏哦!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值