一、双向链表的特点:
双向链表不同于单向链表,它既可以从头遍历到尾,又可以从尾遍历到头;
链表一般有head、tail、length等属性,head指向头节点,length表示链表节点的个数,tail指向尾部节点;
链表无法通过下标直接访问元素,需要从head指向的头节点或tail指向的尾节点开始一个一个的访问,直到找到对应的元素;
二、这里我实现了:
增:
- append,向链尾添加节点
-
insert,向特定位置插入新节点
删:
- removeAt,指定位置删除节点,成功返回删除节点的数据,失败返回null
- remove,从列表中移除指定节点,成功返回删除节点的数据,失败返回null
改:
- update,修改某个位置的节点
查:
- get,获取对应位置的元素
- indexOf,返回队应元素的索引,没有则返回-1,有多个以数组形式返回
其他:
- isEmpty,是否为空
- size,包含元素个数
-
backwardString,从前向后遍历链表,以字符串形式返回
-
forwardString,从后向前遍历链表,以字符串形式返回
- toString,重写tosTring()方法,以数组形式返回
三、问题或不足
- 我一般是自己先实现一下各种方法,然后再去对比b站大学里的视频;
- 这次就犯了好几个错,在这里记录一下,大伙也可以参考参考:
- 第一个就是写inser和removeAtt时忽略了”length==0“这种情况;
- 第二个就是代码看起来杂乱,if...else if..else 用的有点多,重复逻辑没抽取;
- 第三个就是有多余代码,如:
if (!position) {
this.head = current.next;
this.head.previous = null;
// 最后一行代码可不写,Gc会清除它的
current.next = null;
}
这里最后一行可以不用写,GC会清除它的,因为没有对象指向它
四、难的点就说一下insert和removeAt吧:
这里得考虑四种情况,最好可以自己画个图:
当length==0时;
当length>0,删除的位置是head时;
当length>0,删除的位置是tail时;
当length>0,删除的位置是中间时,这里可以做判断,当position < this.length / 2用正向遍历,反之反向遍历;
五、源码
function DoublyLinkedList() {
// 内部类
function Node(data) {
this.data = data;
this.next = null;
this.previous = null;
}
// 属性
this.head = null;
this.tail = null;
this.length = 0;
// 方法
// 方法1:append
DoublyLinkedList.prototype.append = function (data) {
let newNode = new Node(data);
// 判断是否是添加的第一个节点
if (!this.length) {
this.head = newNode;
this.tail = newNode;
} else {
newNode.previous = this.tail;
this.tail.next = newNode;
this.tail = newNode;
}
this.length++;
};
// 方法2:insert
DoublyLinkedList.prototype.insert = function (position, element) {
if (position < 0 || position > this.length) return false;
let newNode = new Node(element);
// length==0?
if (this.length === 0) {
this.head = newNode;
this.tail = newNode;
} else {
// head?
if (position === 0) {
this.head.previous = newNode;
newNode.next = this.head;
this.head = newNode;
} else {
//tail?
if (position === this.length) {
newNode.previous = this.tail;
this.tail.next = newNode;
this.tail = newNode;
} else {
// middle?
let currentEl = this.head;
let index = 0;
while (index++ < position) {
currentEl = currentEl.next;
}
currentEl.previous.next = newNode;
newNode.previous = currentEl.previous;
newNode.next = currentEl;
currentEl.previous = newNode;
}
}
}
this.length++;
return true;
};
// 方法3:removeAt
DoublyLinkedList.prototype.removeAt = function (position) {
if (position < 0 || position >= this.length) return null;
let current = this.head;
if (this.length === 1) {
this.head = null;
this.tail = null;
} else {
// head? 我没考略只有一种的情况
if (!position) {
this.head = current.next;
this.head.previous = null;
// 最后一行代码可不写,Gc会清除它的
current.next = null;
}
// tail?
else if (position === this.length - 1) {
current = this.tail;
this.tail = current.previous;
this.tail.next = null;
current.previous = null;
}
// middle?
else if (position < this.length / 2) {
let index = 0;
while (index++ < position) {
current = current.next;
}
current.previous.next = current.next;
current.next.previous = current.previous;
current.next = null;
current.previous = null;
} else {
current = this.tail;
let index = 0;
while (index++ < this.length - 1 - position) {
current = current.previous;
}
current.previous.next = current.next;
current.next.previous = current.previous;
current.next = null;
current.previous = null;
}
}
this.length--;
return current.data;
};
// 方法4:remove
DoublyLinkedList.prototype.remove = function (element) {
let result = this.indexOf(element);
if (result == null) return null;
if (!Array.isArray(result)) {
this.length--;
return this.removeAt(result);
} else {
for (const key in result) {
this.length--;
}
return this.removeAt(key);
}
};
// 方法5:update
DoublyLinkedList.prototype.update = function (position, element) {
if (position < 0 || position >= this.length) return false;
let current = null;
if (position < this.length / 2) {
current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
current.data = element;
return true;
} else {
current = this.tail;
let index = 0;
while (index++ < this.length - 1 - position) {
current = current.previous;
}
current.data = element;
return true;
}
};
// 方法6:get
DoublyLinkedList.prototype.get = function (position) {
if (position < 0 || position >= this.length) return null;
let current = null;
if (position < this.length / 2) {
current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
return current.data;
} else {
current = this.tail;
let index = 0;
while (index++ < this.length - 1 - position) {
current = current.previous;
}
return current.data;
}
};
// 方法7:indexOf
DoublyLinkedList.prototype.indexOf = function (element) {
// 我有一个想法,从两端同时排查
let current = this.tail;
let index = this.length - 1;
let indexArr = [];
while (current) {
if (current.data == element) {
indexArr.push(index);
}
current = current.previous;
index--;
}
if (indexArr.length === 0) {
return -1;
}
if (indexArr.length === 1) {
return indexArr[0];
}
return indexArr;
};
// 方法8:toString
DoublyLinkedList.prototype.toString = function () {
let dataArr = [];
let current = this.head;
while (current) {
dataArr.push(current.data);
current = current.next;
}
return dataArr;
};
// 方法9:forwardString
DoublyLinkedList.prototype.forwardString = function () {
let current = this.tail;
let resultString = "";
while (current) {
resultString += current.data + " ";
current = current.previous;
}
return resultString;
};
// 方法10:backwardString
DoublyLinkedList.prototype.backwardString = function () {
let current = this.head;
let resultString = "";
// 这里要区分current 和 current.next;这里只能是current,另一个最后一个dada无法拿到
while (current) {
resultString += current.data + " ";
current = current.next;
}
return resultString;
};
// 方法11:isEmpty
DoublyLinkedList.prototype.isEmpty = function () {
if (!this.length) return true;
return false;
};
// 方法12:size
DoublyLinkedList.prototype.size = function () {
return this.length;
};
}