JS实现双链表
前要: 卑微的只是想把刚掌握的知识点,记录下来,好在方便查询和回顾自己所学的知识点,如果有什么错误的,希望能高抬贵手,指出其中错误。
已经有两三个月没有写掘金博文了,前面也写了一点关于数据结构的知识,也很皮毛,今天就分享下链表中的双链表,前面也分享过很粗糙的单链表的知识点,可快速查看。
**双链表:**是链表中的一种,是一种拥有前指针和后指针,分别指向前驱节点和后继节点,主要是解决了前一个节点问题,在实际用途中,当我们实际上想用到上一个节点的数据,对于单链表来说,是需要重新遍历的。
双链表的结构
双链表实现
本文将实现双链表的几个主要方法,分别为:
-
append添加到尾部 -
indexOf返回某个元素的位置 -
indexEle返回某个位置的元素 -
insert插入某个位置 -
removeAt移除某个位置 -
removeEle移除某个元素 -
remove移除最后一个元素 -
getSize返回双链表的长度 -
isEmpty返回双链表是否是空的 -
getHead返回双链表的头部 -
getTail返回双链表的尾部 -
string返回字符串 -
getCurrentAndPrev返回[当前元素,prev节点]
定义Node节点
class Node {
constructor(element) {
this.element = element;
this.next = null;
this.prev = null; // 比单链表多出了一个pre,前指针来记录上一个节点。
}
}
定义双链表
- 由于我们需要记录
head和tail节点,故较单链表我们需要新增tail节点来记录最后一个节点。
class DoubleLinked {
constructor() {
this.head = null;
this.tail = null;
this.count = 0;
}
}
append添加尾部元素
- 判断条件:若
head为null,需要将当前新增的节点赋值为head - 若
head不为null,则需要遍历到最后一个元素current,将next赋值,还需要修改prev指针指向current - 每次新增都需要将
count+1
append(element) {
const node = new Node(element);
let current = this.head;
if (!current) {
this.head = node;
this.tail = node;
} else {
while (current.next) {
current = current.next
}
current.next = node;
node.prev = current;
this.tail = node; // 也可以直接使用this.tail.next = node;
}
this.count++;
}
insert:向某个位置插入元素
- 判断,考虑越界情况
- 当
position = 0需要判断this.head存在与否 - 当
position > 0 && position < this.count找出上一个值preNode,将尾指针指向node,node的prev指向preNode,node的尾指针指向nextNode,nextNode的prev指针指向node,count+1 - 当
position = this.count, 可直接采用append方法,tail指向该添加的node position > this.count; 返回false
/**
* insert 在某个位置插入某个值
* params {
* element:插入的元素
* position: 某个位置
* }
* 判断
* 1。 需要判断越界情况,
* position = 0 需要判断this.head存在与否
* position > 0 && position < this.count 找出上一个值preNode,将尾指针指向node,node的prev指向preNode,node的尾指针指向nextNode,nextNode的prev指针指向node
* position = this.count, 直接采用append方法
* position > this.count; 返回false
*/
insert(element, position) {
let current = this.head;
const node = new Node(element);
let previous = null;
// position 为 0,需要考虑到head是否为null
if(position === 0) {
// 若head为null
if(!current) {
this.head = node // 直接将head赋值为node
this.tail = node // 同时也需要将tail赋值为node
}else {
node.next = current; // node替换current,node的next指针指向current
current.prev = node;
this.head = node;
// 这里不用this.tail指向,是因为我们只是替换head,tail还是一样指向的是最后一个元素;
}
this.count++;
}else if (position > 0 && position <= this.count){
// 找出position所在的元素
let index = 0
while(index < position) {
previous = current;
current = current.next;
index++;
}
// 如果是最后一个元素length,则current为null,node作为最后一个元素
if(index !== this.count) {
node.next = current;
current.prev = node;
}else {
this.tail = node;
}
previous.next = node
node.prev = previous;
this.count++;
}else {
return false
}
}
indexOf:找出当前元素的索引值
- 判断考虑边界问题,超出没找到则返回
-1 - 遍历查找,找到返回
index索引,这里都是默认找出第一个元素,后面有相同元素暂时不考虑
// 找出当前的位置的元素
indexOf(element) {
// 根据current = current.next,求出当前位置
let index = 0;
let current = this.head;
while(current && index < this.count){
if(current.element === element) {
return index
}
current = current.next;
index++;
}
return -1;
}
indexEle: 找出当前位置所在的元素
- 判断考虑边界问题,超出没找到则返回
false,找到则返回当前元素
indexEle(position) {
let ele = null;
if(position >= 0 && position < this.count){
let current = this.head;
while(position > 0) {
current = current.next;
position--;
}
ele = current;
}
return ele
}
removeAt: 删除第几个元素
- 判断,考虑边界溢出问题。
- 当
position===0 && head时,需要将head赋值为ele.next,同时将ele.prev设置为null - 当
position > 0 && head && postion < count - 1时,将previous.next = current.next;current.next.prev = previous - 当
position === count - 1,则只需要将previous.next = null;this.tail = previous; - 其他情况,
!head || position > count-1都返回fasle
/**
* removeAt 删除第几个元素
*
*/
removeAt(position) {
// 找到当前位置的元素
let ele = this.indexEle(position);
// 这里已经是判断了越界情况了。
if(ele) {
// 需要考虑poistion === 0时,将prev指针指向null
if(position === 0) {
this.head = ele.next;
ele.prev = null;
}else {
// 找出前一个指针元素
let previous = this.indexEle(position - 1);
// 同样情况,面对边界情况next=null需要考虑
if(position === this.count - 1) {
previous.next = null;
this.tail = previous;
// console.log({'tail': this.tail})
}else {
previous.next = ele.next;
ele.next.prev = previous
}
}
// 数量减一
this.count--;
return true
}
return false
}
removeEle: 删除元素
- 考虑元素重复问题,需要将所有元素相等的都删除
/**
* removeEle 删除元素
* 考虑元素重复问题,需要将所有element相等的都删除
*/
removeEle(element){
let current = this.head;
let previous = null;
let num = 0
if(current.element === element) {
// 需要去掉是否是第一个元素,因为后面是从current.next开始的,所以当head存在ele时是删除不掉的
this.head = current.next;
current.next.prev = null;
this.count--;
num++
}
previous = current;
current = current.next;
while(current) {
if(current.element === element) {
previous.next = current.next;
// 若不是尾指针,需要执行前指针指向
if(current.next){
current.next.prev = previous;
}else {
this.tail = previous
}
this.count--; //长度-1
num++
}else {
previous = current;
}
current = current.next;
}
return num === 0 ? -1 : num;
}
remove删除最后一个元素
remove() {
let current = this.head;
// 删除时head为null
if(this.count === 0) {
return false
}else if (this.count === 1) { // 删除时只剩下head
this.head = null;
this.tail = null;
this.count--;
return current.element
}else {
// 删除时,删除最后一个,需要将tail重新赋值为previous
let previous = null
while(current.next) {
previous = current;
current = current.next;
}
previous.next = null;
this.tail = previous;
this.count--;
return current.element;
}
}
stirng: 转成字符串
string() {
let str = '';
let current = this.head;
while (current) {
str += current.element;
current = current.next;
}
return str;
}
getHead: 获取head节点
getHead() {
return this.head && this.head.element || null;
}
getTail:获取最后一个节点
getTail() {
return this.tail && this.tail.element || null;
}
getSize:获取长度大小
getSize() {
return this.count;
}
isEmpty: 判断是否是空的
isEmpty() {
return this.length === 0;
}
getCurrentAndPrev: 获取当前元素和上一个节点
getCurrentAndPrev() {
const arr = [];
let current = this.head;
while (current) {
arr.push([current.element, current.prev])
current = current.next;
};
return arr
}
测试数据
const doubleLinked = new DoubleLinked()
// console.log(doubleLinked.string());
doubleLinked.append(2);
// console.log({'head1': doubleLinked.getHead()})
// console.log({'tail1': doubleLinked.getTail()})
doubleLinked.append(3);
console.log(doubleLinked.remove())
console.log(doubleLinked.remove())
// console.log({'head2': doubleLinked.getHead()})
// console.log({'tail2': doubleLinked.getTail()})
return;
doubleLinked.append(4);
doubleLinked.append('A');
doubleLinked.append('C');
doubleLinked.append('A');
// console.log({'head3': doubleLinked.getHead()})
// console.log({'tail3': doubleLinked.getTail()})
doubleLinked.insert('A', 0)
// console.log(doubleLinked.getSize())
doubleLinked.insert('B', 4)
doubleLinked.insert('C', 3)
console.log(doubleLinked.string());
// doubleLinked.removeAt(5)
// console.log(doubleLinked.indexEle(3))
// console.log(doubleLinked.indexOf('C'))
// console.log(doubleLinked.insert('D', 10)) // false
// console.log({'tail3': doubleLinked.getTail()})
console.log(doubleLinked.removeEle('A'))
console.log({'tail3': doubleLinked.getTail()})
console.log(doubleLinked.getSize())
console.log(doubleLinked.string());
console.log(doubleLinked.getCurrentAndPrev())