拿捏javascript数据结构与算法(上)

169 阅读11分钟

下一篇:
拿捏javascript数据结构与算法(中)
知识点:

第一章:javascript简介(略)
第二章:ECMAscript和TypeScript概述
第三章:数组
第四章:栈
第五章:队列和双端对列
第六章:链表

第一章js基础比较简单,在这里我就不浪费时间重复了,直接从第二章开始

第二章:ECMAscript和TypeScript概述

文章的重点我放在数据结构和算法部分,所以这部分就是大概介绍一下。详细部分我放了文章链接

2.1 ECMAscript和JavaScript关系

javascript语言每年都在进化,2015年后几乎每年都有一个新版本发布,我们称之为ECMAScript

ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会,英文名称是European Computer Manufacturers Association)通过ECMA-262标准化的脚本程序设计语言。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,所以它可以理解为是JavaScript的一个标准,但实际上后两者是ECMA-262标准的实现和扩展。

ECMAScript和javascript两者关系:
前者是后者的规范 ,后者是前者的一种实现(ECMAScript也叫做jscript和Actionsrcipt)

ECMAScript简称 es 我们平时说的es5 es6指的就是ECMAScript 5 和ECMAScript 6
由于

2.2 ECMAScript2015+(es6)功能

2011年发布ECMAScript5.1
2015年6月发布ES6的第一个版本,因此ECMAScript2015+可以看做是es6

es6主要增加了如下功能

使用了let和const功能
模板字符串
函数的参数默认值
箭头函数 =>

模块
下面我们就来介绍这个几个功能

2.2.1 let和const功能和特点

在es6之前,是没有块级作用域的;只要这个变量的作用域范围内都能被访问到;声明一个变量,js引擎会把变量的声明提升。

es6新增了let和const声明变量
let声明的变量只在块级作用域内有效,外部无效,函数执行完成后销毁,(var是全局变量)
const和let一样声明的变量在块级作用域内有效。注const只能声明常量,声明后不能被改变。

let和const的几个特点:

1、 let和const属于局部变量,只在对应的作用域内有效
2、不存在变量提升:声明的变量只能在声明后使用,否则会报错
3、暂时性死区:声明变量从一开始就形成一个封闭的作用域,如果在声明之前使用这写变量,就会报错
4、在同一个作用域内不允许重复声明,否则会报错

2.2.2 模板字符串

模板字符串用一对 **‘ ’**包裹,要插入变量的值,值需要把变量放在${ }里面就可以了

const name = '小普'
const age = 21
//传统字符串拼接
console.info('大家好,我叫' + name + ',今年' + age + '岁了')
// 模板字符串用法
console.info(`大家好,我叫${name},今年${age}岁了`)

2.2.3 函数的参数默认值

javascript中有一个内置的对象,叫做arguments对象,是一个数组,包含函数被调用时传入的参数,即使不知道参数的名称,也可以动态的获取并使用这些参数

ES6函数扩展(函数参数默认值)

2.2.4 箭头函数 =>

ES6箭头函数语法定义函数,将原函数的“function”关键字和函数名都删掉,并使用“=>”连接参数列表和函数体。
通常函数写法:

var fn1 = function(a, b) {
return a + b
}
function fn2(a, b) {
return a + b
}

箭头函数写法:

var fn1 = (a, b) => {
    return a + b
}
(a, b) => {
    return a + b
}

2.2.6类

在传统的javascript中,没有类的概念,es6中新增 了类的概念
es6中class类的全方面理解

2.2.7 模块

ES6中使用了:import、export 取代了 require、module.exports 用来引入和导出模块
深入理解 ES6 模块机制

2.3 介绍TypcScript

TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。

TypeScript 提供最新的和不断发展的 JavaScript 特性,包括那些来自 2015 年的 ECMAScript 和未来的提案中的特性,比如异步功能和 Decorators,以帮助建立健壮的组件。下图显示了 TypeScript 与 ES5、ES2015 和 ES2016 之间的关系:
在这里插入图片描述
在这里插入图片描述

第三章:数组

主要学习数组的一些方法(增删改查)

3.1 为什么用数组

数组是最简单的内存数据结构
数组存储一系列同一种数据类型的值(javascript数组可以保存不同类型的值,但是尽量避免这么做)
数组就是为了记录简化记录多个数据和多维数组

3.2 创建和初始化数组

创建数组有两种方法:

1.使用Array构造函数
let shuzu=new Array();
注意:括号里面参数可以有参数,若为一个数字,表示该数组的长度,如果为多个数字或者一个(多个)非数字表示的是传递数组中应该包含的值。

2.使用数组字面量
let shuzu=[];

3.3 添加元素

1、在数组末尾插入元素:

使用push方法
numbers.push(11);

原生的方法:
numbers[numbers.length] = 10;
在Javascript中,数组是可以修改的对象,如果要添加元素,会动态生长,所以直接赋值给数组最后一个空位即可

2.在数组开头插入元素:

使用unshift方法
numbers.unshift(10);

原生的方法:
创建一个新数组,遍历原来的数组,把原数组依次赋值给后一位,然后复制给新数组,把要插入的数字赋值给新数组的第一个位置,最后返回新数组、

3.4 删除数组

1、从数组末尾删除元素:

使用pop方法;
numbers.pop();

2、删除数组开头元素:

使用shif方法
number.shif();

3、在任意位置添加或者删除元素

使用splice方法:
splice接受多个参数
number.splice(4.,0,6,4)
第一个参数标识要删除或者插入的元素索引值
第二个参数是删除元素的个数,这个例子我们要添加元素所以第二个删除元素的个数为0.
第三个参数往后,就是我们 要添加到数组里面的值

3.5 二维和多维数组

二维数组本质上是以数组作为数组元素的数组,即“数组的数组”,
类型说明符 数组名[常量表达式][常量表达式]。二维数组又称为矩阵,
行列数相等的矩阵称为方阵。对称矩阵a[i][j] = a[j][i],
对角矩阵:n阶方阵主对角线外都是零元素

var arr = [[1,2],['a','b']];
console.log(arr[1][0]); //a 第2列第1行所在的元素

同理多维数组就是在二维数组的基本上添加象限

3.6 javascript数组参考方法

1、concat(),连接两个或者更多的数组,并返回一个新的数组
2、ES6:copyWithin(),从数组的指定位置复制元素到数组的指定位置。语法:array.copyWithin(target, start, end)
3、ES6:entries(),返回数组的迭代对象
4、every(),检测数组所有元素是否都符合指定条件,接收一个函数作为参数,用于检测数组中的元素
5、ES6:fill(),将一个固定的值替换数组里面的元素。语法:array.fill(value, start, end)
6、filter(),返回一个数组,里面包含符合条件的元素
7、find(),返回数组中符合条件的第一个元素
8、findIndex(),返回数组中符合条件的第一个元素的所在位置
9、forEach(),数组每个元素都执行一次回调函数
10、ES6:from(),将伪数组转换为真正的数组
11、ES6:includes(),判断数组中是否函数指定的值,如有返回true,否则返回false
12、indexOf(),返回数组中指定元素的位置,如果数组中没有指定的元素则返回-1
13、isArray(),判断一个对象是否为数组,是 返回true,不是 返回false
15、ES6:keys(),从数组创建一个包含数组键的可迭代对象
16、lastIndexOf(),返回指定的元素在数组中最后出现的位置,在数组的后面开始搜索
17、map(),返回一个新数组,数组中的元素是原始数组的元素调用函数之后处理的值
18、pop(),删除数组的最后一个元素,并返回删除的元素
19、push(),在数组后面添加新元素,并返回数组新的长度

20、reduce(),将元素的值计算为一个值,从左到右
21、reduceRight(),将元素的值计算为一个值,从右到左
22、reverse(),反转数组元素的排列顺序
23、shift(),删除并返回数组第一个元素
24、unshift(),向数组的最前面添加新元素,并返回新的数组长度
25、slice(),返回指定的数组元素,第一个参数是开始的位置,第二个是结束位置(不包含结束位置的元素)

26、splice(),添加和删除数组中的元素,第一个参数是要删除的元素的开始位置,第二个参数是要删除的元素的个数。第三个以及以后的参数都是添加到数组中的新元素
27、some(),检测数组中是否含有指定的元素,有的话就返回true,没有就返回false
28、sort(),对数组进行排序,可以接收一个比较函数
29、toString(),将数组转为字符串,并返回结果
30、valueOf(),返回数组对象的原始值

常用的我已经加粗了!!!

3.8 类型数组

由于javascript与c和Java等语言不同,javascript数组不是强类型。可以存储任意类型数据
那么如何让javascript的数组也存储单一的数据类型呢?
这个就用到类型数组。
具体语法:

let myArray = new TypedArray(length)
//在实际使用中  把TypedArray换成下面列表中需要的类型

在这里插入图片描述

3.9 typescript中的数组

Typescript中最简单的方法是使用「类型 + 方括号」来表示数组

let fibonacci: number[] = [1, 1, 2, 3, 5];

数组的项中不允许出现其他的类型:
在使用数组方法时,对应增删改查的元素类型必须一致,否则会报错。

详解文章:
TypeScript 数组Array操作

第四章:栈 stack

本章主要学习怎么在javascript中利用数组和对象创建一个栈,以及栈的一些方法

4.1 栈的概述

栈是一种遵从先进后出(LIFO)原则的有序集合。
新添加或待删除的元素都保存在栈的末尾,称作栈顶,另一端就叫栈底。
在栈里,新元素都靠近栈顶,旧元素都接近栈底

用日常生活中的例子:
栈就像我们的衣服口袋,先放进去的东西后取出来,后放进去的东西能先取出来
在这里插入图片描述

栈被用在编程语言的编译器和内存中用于保存变量,方法调用等等,也被用于浏览器历史记录(浏览器的返回按钮)

4.2 基于javascript数组创建一个栈

在javascript中用数组创建一个栈一共7个步骤:

1、先声明一个stack类,用于存放栈
2、向栈中使用push添加元素(只能从栈顶添加)
3、从栈中使用pop方法移除元素(从栈顶移除)
4.查看栈顶元素(peek方法)
5.检查栈是否为空 方法为isEmpty 如果栈为空返回true 否则返回false
6.使用clear方法清空栈元素
7.可以开始使用stack类
经过上面的六步,stack已近具有了栈的特点。

代码实现:

//创建一个基于数组单位栈
class stack{
	constructor{
		this.items = [];
	}
}
//向栈中使用push方法添加元素
push () {
	this.items.push();
}
// 使用pop方法从栈顶移除元素
pop(){
	reyurn this.items.pop();
}
//经过上面这两个步骤,基本实现了栈的先进后出的原则
//查看栈顶元素 (length -1)
peek{
	return this.items{this.items.length - 1};
}
//使用isEmpty检查栈是否为空
isEmpty(){
	return this.items.length === 0;
}
//完成上面的步骤后,使用clear方法清除栈里面的所有元素
clear(){
	return this.items.length;
}
//至此。栈已经完成了
就可以使用栈的方法来对栈进行操作了

4.3 基于javascript对象创建一个栈

前面我们已经学会使用数组来创建栈了,但在实际的应用中,处理大量数据的时候,需要评估如何使用数据是最高效的,使用数组的时间复杂度为0(n),n代表数组的长度,因为数组是一个有序集合,为了保证元素排序,会占用更多的内存我们需要迭代整个数组直到寻找到目标元素。

由此引出了对象创建栈
对象能直接获取元素,占用较少的内存空间,而且能够按照我们的要求排列数据。

操作步骤一共六步:

1、声明一个stack类
2、向栈中插入元素
3、验证栈是否为空和它的大小
4、从栈中弹出元素
5、查看栈值并将栈清空
6、创建tostring方法

代码实现:

//首先声明一个stack类
class  stack {
	constructor () {
		this.count = 0 ; //使用count来记录栈的大小,也能帮助我们删除和添加元素
		this.items = {};
	}
}
//向栈中加入元素
push(element) {
	this.items[this.count ] = element;
	this.count ++ ;
}
//在js中,对象是键值对的形式表现出来,我们使用count作为items对象的键名,插入的元素则是它的值,插入元素后,我们把count++,以便于下一个变量值插入
//count属性也表示栈的大小,我们可以返回count的size来计算栈的大小
size(){
	return this.count;
}
//验证栈是否为空
isEmpty(){
	return this.count === 0;
}
//查看栈顶的值并将栈清空
peek{
if (this.isEmpty()){
	return unerfined;
}
	return this.items{this.items.length - 1};
}
//清空栈把值变为构造函数的初始值就ok
clear (){
	this.items = {};
	this.count =  0;
}
//也可以使用另外一种方法:
while (!this.isEmpty()){
	this.pop();
}
//创建tostring方法
在前面的数组中没有提string方法,是因为数组内置就有tostring属性,使用对象的时候,就需要我们手动去穿件了
toString () {
	if  (this.isEmpty()){
		return '';
	}
	let objStru=ing = '${this.items[0]}';
	for  (let i = 1 ; i < this.count; i++ ){
		objString = '${objstring},${this.items[i]}';
	}
	return objstring ;
}
//如果栈是空的,返回空值就ok了
//如果不是空的。底部第一个元素作为字符串的初始值,然后迭代整个栈

至此,一个基于对象创建的栈就完成了

4.4 保护数据结构内部元素

在我们创建一个栈后,别的同事也可能要使用,我们想保护内部的元素,只允许改变我们暴露的元素,这个时候就提及到了保护数据结构内部元素:
主要有三种方法:

1、下划线命名约定

下划线命名定义
一部分开发者喜欢在javascript中使用下划线命名约定来标记一个属性为私有属性
下划线命名约定实在属性名称之间加一个下划线-,不过这种方法只是一种约定,不能保护数据,看不懂的程序员就gg了

代码实现:

class stack {
	constructor () {
		this._count = 0;
		this._items = {};
	}
}

2、symbol实现类

使用symbol实现类
在es6中规定,symbol是不可变的,可以用作对象的属性

代码实现:

cosnt_items = symbol('stacItems');
class  stack {
	constoructor () {
		this[_items] = [];
	}
	//栈的方法
}

3、使用weakMap实现类

使用weakMap实现类
weakMap就能确保属性私有,weakMap是键值对的存在,键是对象,值是属性。
这点在第八章会重点讲解

4.5 javascript中用栈解决实际问题

使用栈实现阶乘的递归

使用栈来模拟5!的过程,首先将数字5到1压入栈,然后使用一个循环将数字挨个弹出并连乘
代码实现:

 1 function fact(num) {
 2     var stack=new Stack;
 3     while(num>0){
 4         stack.push(num--);
 5     }
 6     var sum=1;
 7     while(stack.length>0){
 8         sum*=stack.pop;
 9     }
10     return sum;
11 }
12 
13 console.log(fact(5)) //120

合法括号

下面的字符串中包含小括号,请编写一个函数判断字符串中的括号是否合法,所谓合法,就是括号成对出现

sdf(ds(ew(we)re)rwqw)qwrwq		合法
(sd(qwqe)sd(sd))		合法
()()sd()(sd()dw))(		不合法

思路分析
括号存在嵌套关系,也存在并列关系,如果使用数组来存储这些括号,然后再想办法一对一的抵消掉,似乎可行。但是我们无法判断一个左括号对应的是哪一个右括号。在数组的角度思考这个问题,就有些困难。
现在,我们使用栈来解决这个问题
遇到左括号,就把做括号压入栈中
遇到右括号,判断栈是否为空,如果为空则说明没有左括号与之相对应,字符串括号不合法。如果栈不为空,则把栈顶元素移除,这对括号就抵消了。
当for循环结束,如果栈是空的,说明所有的左右括号都抵消了,如果栈力还有元素,则说明缺少右括号,字符串括号不合法。

代码实现:

function is_leagl_brackets(string){
	var stack = new Stack();
	for (var i = 0;i<string.length;i++) {
		var item = string[i];
		// 遇到做括号入栈
		if(item == '('){
			stack.push(item)
		}else if (item == ')'){
		// 遇到右括号,判断栈是否为空
			if(stack.isEmpty()){
				return false
			}else {
				stack.pop() // 弹出左括号
			}
		}
	}
	//  如果栈为空,说明字符串括号合法
	return stack.isEmpty()
}
console.log(is_leagl_brackets('sdf(ds(ew(we)re)rwqw)qwrwq')) // true
console.log(is_leagl_brackets('(sd(qwqe)sd(sd))')) // true
console.log(is_leagl_brackets('()()sd()(sd()dw))(')) // false

实现一个有min方法的栈

供一个min方法,返回栈里的最小的元素,且时间复杂度为O(1)

function MinStack() {
	var data_stack = new Stack();
	var min_stack = new Stack();
	// 用min_stack 记录每次 push 进来的元素之后,栈中的最小值
	this.push = function (item) {
		data_stack.push(item);
		if(min_stack.isEmpty() || item < min_stack.top()){
			min_stack.push(item)
		}else {
			min_stack.push(min_stack.top())
		}
	};
	// 这样,每次pop之后,min_stack 也会将上次栈中的最小值弹出
	this.pop() = function () {
		data_stack.pop()
		min_stack.pop()
	}
	this.min = function () {
		return min_stack.top()
	}
}

实际应用中还有许多,这里我就不挨个列举 了。

第五章:队列和双端对列

5.1 队列数据结构

1、队列的概念:
队列遵循的是先进先出原则的一组有序的列。队列在尾部添加元素,顶部移除元素
联系到日常生活中排队付款,买菜,后面加人排队,前面的人付款先走。
2、队列的创建:

1、使用一个类来创建队列
class Queue { constructor() { this.count = 0; this.lowestCount = 0;//追踪队列的第一个元素 this.items = {}; }

2.使用enqueue方法向队列中添加元素(添加在队列末尾)
enqueue(element) { this.items[this.count] = element; this.count++; }

3、使用dequeue方法从队列中删除元素(从队列顶部移除)
size() { return this.count - this.lowestCount; };isEmpty() { return this.size() === 0; };

4、查看队列最前面的项(使用peek方法)
peek() { if (this.isEmpty()) { return undefined; } return this.items[this.lowestCount]; }

5、使用isEmpty方法检查队列是否为空和获取他的长度
6.清空队列
clear() { this.items = {}; this.count = 0; this.lowestCount = 0; }

7.创建tostring方法
toString() { if (this.isEmpty()) { return ‘’; } let objstring = ${this.items[this.lowestCount]}; for (let i = this.lowestCount + 1; i < this.count; i++) { objstring = ${objString},${this.items[i]}; } return objString; }

创建方法根据队列的原理和前面讲到得栈相似,这里我就不重复了

5.2 双端队列数据结构(deque)

1、双端队列的概念:
双端队列是一种允许我们同时从队列的前端和后端添加和移除元素的特殊队列

在计算机中双端队列常见的应用是储存一系列的撤销操作,用户在撤销后,该操作会被存储在一个双端队列中,可以点击撤销和烦撤销,
由于双端队列同时遵守了先进先出和后进先出的原则,可以说他是把队列和栈结合的一种数据结构。

2、双端队列的创建:

1、 创建双端队列
class Deque { constructor() { this.count = 0; this.lowestCount = 0; this.items = {}; }

2、队首添加元素
addFront(element) { if (this.isEmpty()) {//空队列 this.addBack(element); } else if (this.lowestCount > 0) {//之前被删除,重新添加 this.lowestCount–; this.items[this.lowestCount] = element; } else { for (let i = this.count; i > 0; i–) { this.items[i] = this.items[i - 1]; } this.count++; this.items[0] = element; } }

3、 队尾添加元素
addBack(element) { this.items[this.count] = element; this.count++; }

4、队首删除元素
removeFront() { if (this.isEmpty()) { return undefined; } const result = this.items[this.lowestCount]; delete this.items[this.lowestCount]; this.lowestCount++; return result; }

5、队尾删除元素
removeBack() { if (this.isEmpty()) { return undefined; } this.count–; const result = this.items[this.count]; delete this.items[this.count]; return result; }

6、返回队首元素
peekFront() { if (this.isEmpty()) { return undefined; } return this.items[this.lowestCount]; }

7、返回队尾元素
peekBack() { if (this.isEmpty()) { return undefined; } return this.items[this.count - 1]; }

5.3队列和双端队里的应用

模拟击鼓传花游戏

情景:孩子们围城一圈,把花传递给身边的人,某一时刻停止,花在谁手上,谁就推出。重复这个操作,剩下的最后一个人就是胜利者。
代码实现:

function hotPotato(elementsList, num) {  const queue = new Queue();  const elimitatedList = [];
  for (let i = 0; i < elementsList.length; i++) {    queue.enqueue(elementsList[i]);  }
  while (queue.size() > 1) {    for (let i = 0; i < num; i++) {      queue.enqueue(queue.dequeue());    }    elimitatedList.push(queue.dequeue());  }
  return {    eliminated: elimitatedList,    winner: queue.dequeue()  };}

回文检查器

检查一个词组挥着字符串是否为回文。
代码实现:

function palindromeChecker(aString) {  if (    aString === undefined    || aString === null    || (aString !== null && aString.length === 0)  ) {    return false;  }  const deque = new Deque();  const lowerString = aString.toLocaleLowerCase().split(' ').join('');  let firstChar;  let lastChar;
  for (let i = 0; i < lowerString.length; i++) {    deque.addBack(lowerString.charAt(i));  }
  while (deque.size() > 1) {    firstChar = deque.removeFront();    lastChar = deque.removeBack();    if (firstChar !== lastChar) {      return false;    }  }
  return true;};

第六章:链表

6.1 链表概述(LinkedList)

链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称为指针或链接)组成。下图讲解:
在这里插入图片描述
相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不需要移动其他元素。然而,链表需要使用指针,因此实现链表的时候需要注意。数组的另一个细节是可以直接访问任何位置元素,而要想访问链表中间的一个元素,需要从起点(表头)开始送达列表直到找到所需要元素。

现实实例就是火车,火车有多节车厢相互衔接,通过接轨来链接火车。车厢就是链表的元素,而接轨就是指针。
在这里插入图片描述

6.2 创建链表

function LinkedList() {
    var Node = function (val) {       //新元素构造
        this.val = val;
        this.next = null;
    };
    var length = 0;
    var head = null;
 
    this.append = function (val) {
        var node = new Node(val);       //构造新的元素节点
        var current;
        if (head === null) {        //头节点为空时  当前结点作为头节点
            head = node;
        } else {
            current = head;              
            while (current.next) {     //遍历,直到节点的next为null时停止循环,当前节点为尾节点
                current = current.next;
            }
            current.next = node;      //将尾节点指向新的元素,新元素作为尾节点
        }           
        length++;              //更新链表长度
    };
    this.removeAt = function (position) {
        if (position > -1 && position < length) {
            var current = head;
            var index = 0;
            var previous;
            if (position == 0) {
                head = current.next;
            } else {
                while (index++ < position) {
                    previous = current;
                    current = current.next;
                }
                previous.next = current.next;
            }
            length--;
            return current.val;
        } else {
            return null;
        }
    };
    this.insert = function (position, val) {
        if (position > -1 && position <= length) {   //校验边界
            var node = new Node(val);        
            current = head;
            var index = 0;
            var previous;
            if (position == 0) {       //作为头节点,将新节点的next指向原有的头节点。
                node.next = current;
                head = node;         //新节点赋值给头节点
            } else {
                while (index++ < position) {
                    previous = current;
                    current = current.next;
                }               //遍历结束得到当前position所在的current节点,和上一个节点
                previous.next = node;    //上一个节点的next指向新节点  新节点指向当前结点,可以参照上图来看
                node.next = current;
            }
            length++;
            return true;
        } else {
            return false;
        }
    };
    this.toString = function () {
        var string = head.val;
        var current = head.next;        
        while (current) {
            string += ',' + current.val;
            current = current.next;
        }
        return string;
    };
    this.indexOf = function (val) {
        var current = head;
        var index = -1;
        while (current) {
            if (val === current.val) { //从头节点开始遍历
                return index;
            }
            index++;
            current = current.next;
        }
        return -1;
    };
    this.getLength = function () {
        return length;
    }
    this.getHead = function () {
        return head;
    }
}
 
// 创建链表
var li = new LinkedList();
li.append(1);
li.append(2);
li.append(4);
li.append(4);
li.append(5);
li.insert(2,3);
li.insert(2,3);
console.log(li.toString())  // 1,2,3,3,4,4,5
console.log(li.getHead())   // 1->2->3->3->4->4->5

参考链接:
【数据结构】如何在JS中创建一个链表

6.3 双向链表

双向列表和普通列表的区别在于:
在单向列表中一个节点只有链向下一个节点的链接,而在双向列表中,链接是双向的一个链接向下一个元素,另一个链向前一个元素。

双向链表:既可以从头遍历到尾,又可以从尾遍历到头。也就是说链表连接的过程是双向的,它的实现原理是:一个节点既有向前连接的引用,也有一个向后连接的引用。
双向链表的缺点:
每次在插入或删除某个节点时,都需要处理四个引用,而不是两个,实现起来会困难些;
相对于单向链表,所占内存空间更大一些;
但是,相对于双向链表的便利性而言,这些缺点微不足道。

如图所示:
在这里插入图片描述
代码实现:

//先创建双向链表类DoubleLinklist,并添加基本属性,再实现双向链表的常用方法:
 //封装双向链表类
    function DoubleLinklist(){
      //封装内部类:节点类
      function Node(data){
        this.data = data
        this.prev = null
        this.next = null
      }
 
      //属性
      this.head = null
      this.tail ==null
      this.length = 0
      }

6.4 循环链表

双向链表的每个结点需要连接前一个结点和后一个结点,所以需要定义两个指针域,分别指向前一个结点和后一个结点。

双向链表中头节点的prev指针指向尾节点,尾节点的next指针指向头节点。
在这里插入图片描述
循环列表的实现:
1、创建双向循环链表

function DoublyCircularLinkedList(){
	function Node(element){
		this.element=element
		this.next=null
		this.prev=null
	}
	let length=0
	let head=null
	let tail=null
}

2、尾部插入新节点

	this.append=function(element){
		let node = new Node(element)
		let current // 当前节点
		let previous // 前一个节点
		if(!head){
			head=node
			tail=node
			head.prev=tail
			tail.next=head
		}else{
			current=head
			while(current.next !== head){
				previous = current
				current = current.next
			}
			current.next=node
			node.next=head
			node.prev=current
		}
		length++
		return true
	}

3、任意位置插入节点

	// 在任意位置插入一个节点
	this.insert=function(position,element){
		if(position > 0 && position <= length){
			let node = new Node(element)
			let index = 0
			let current = head
			let previous
			if(position === 0){ // 头部插入
				if(!head){
					node.next=node
					node.prev=node
					head=node
					tail=node
				}else{
					current.prev=node
					node.next=current
				}
			}else if(position === length){ // 在尾部插入
				current=tail
				current.next=node
				node.prev=current
				tail=node
				node.next=head
			}else{
				while(index++ < position){
					previous=current
					current=current.next
				}
				current.prev=node
				node.next=current
				previous.next=node
				node.prev=previous
			}
			length++
			return true
		}else{
			return false
		}
	}

4、根据位置删除节点

this.removeAt = function(position){ 
	    if(position > -1 && position < length){   
	      let current = head
	      let index = 0
	      let previous; 
	      if(position === 0){   
	        current.next.previous = tail
	        head = current.next;   
	      }else if(position === length - 1){   
	        current = tail;  
	        current.prev.next = head
	        head.prev = current.prev
	        tail = current.prev
	      }else{ 
	        while(index++ < position){ 
	          previous = current
	          current = current.next
	        } 
	        previous.next = current.next
	        current.next.prev = previous
	      } 
	      length--
	      return true
	    }else{ 
	      return false
	    } 
	  }

5、根据节点值删除节点

	this.remove = function(element){ 
	  let current = head
	  let  previous
	  let  indexCheck = 0  
	  while(current && indexCheck < length){ 
		if(current.element === element){ 
		  if(indexCheck === 0){ 
			current.next.prev = tail; 
			head = current.next
		  }else{ 
			current.next.prev = previous
			previous.next = current.next
		  } 
		  length--
		  return true
		} 	    
		previous = current
		current = current.next
		indexCheck++
	  }    
	  return false
	}

6.5 有序链表

有序链表是指保持元素有序的链表结构,除了使用排序算法之外,我们还可以将元素插入到正确的位置来保证链表的有序性。
代码实现:

const Compare = {
    LESS_THAN:-1,
    BIGGER_THAN:1
};
function defaultCompare(a,b){
    if(a === b){
        return 0;
    }
    return a < b?Compare.LESS_THAN : Compare.BIGGER_THAN;
}
class SortedLinkedList extends LinkedList{//LinkedList这个类可以见https://www.cnblogs.com/MySweetheart/p/13212220.html
    constructor(equalsFn = defaultEquals, compareFn = defaultCompare){
        super(equalsFn);
        this.compareFn = compareFn;
    }
    insert(element,index = 0){
        if(this.isEmpty()){
            return super.insert(element,0);
        }
        const pos = this.getIndexNextSortedElement(element);
        return super.insert(element,pos);
    }
    getIndexNextSortedElement(element){
        let current = this.head;
        let i = 0;
        for(;i < this.size() && current; i++){
            const comp = this.compareFn(element,current.element);
            if(comp == Compare.LESS_THAN){
                return i;
            }
            current = current.next;
        }
        return i;
    }
}

6.6 用链表实现栈

这点书上讲的比较模糊,可以参考下面这篇文章:
JAVASCRIPT——通过链表实现栈功能

先写到第六章,后面的等我复习完在写出来,书里有很多地方写的我没有全部理解,因此在写这篇文章的时候引入了很多相关的博客,博客看起来更容易理解
码字不易,点个赞在走吧!!! 祝大家五一快乐!!!

下一篇:
拿捏javascript数据结构与算法(中)

文章参考:
《学习javascript数据结构与算法》
通俗易懂的 TypeScript 入门教程

JavaScript实现双向链表

JavaScript数据结构复习(篇5-4)双向循环链表

使用javaScript来实现一个有序链表