鏈表的介紹
鏈表存儲有序的元素集合,但不同於數組,鏈表中的元素在內存中並不是連續放置的。每個元素由一個存儲元素本身的節點和一個指向下一個元素的引用(也稱爲指針或鏈接)組成。下圖講解:
相對於傳統的數組,鏈表的一個好處在於,添加或移除元素的時候不需要移動其他元素。然而,鏈表需要使用指針,因此實現鏈表的時候需要注意。數組的另一個細節是可以直接訪問任何位置元素,而要想訪問鏈表中間的一個元素,需要從起點(表頭)開始送達列表直到找到所需要元素。
現實實例就是火車,火車有多節車廂相互銜接,通過接軌來鏈接火車。車廂就是鏈表的元素,而接軌就是指針。
鏈表算法實例
1.鏈表增刪改查。
let list = new LinkedList();
list.append(15);
list.append(10);
list.append(13);//15,10,13
list.indexOf(10);//1
list.removeAt(1)//15,13
list.insert(1,17);//15,17,13
list.remove(11);//17,13
ES6鏈表實現代碼:
let LinkedList = (function () {
class Node {//一個節點
constructor(element){
this.element = element;
this.next = null;
}
}
const length = new WeakMap();
const head = new WeakMap();
class LinkedList {
constructor () {
length.set(this, 0);
head.set(this, null);
}
append(element) {//向列表尾部添加一個新的項
let node = new Node(element),
current;
if (this.getHead() === null) {
head.set(this, node);
} else {
current = this.getHead();
while (current.next) {
current = current.next;
}
current.next = node;
}
let l = this.size();
l++;
length.set(this, l);
}
insert(position, element) {//向列表的特定位置插入一個新的項
if (position >= 0 && position <= this.size()) {
let node = new Node(element),
current = this.getHead(),
previous,
index = 0;
if (position === 0) {
node.next = current;
head.set(this, node);
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
node.next = current;
previous.next = node;
}
let l = this.size();
l++;
length.set(this, l);
return true;
} else {
return false;
}
}
removeAt(position) {//從列表的特定位置移除一項
if (position > -1 && position < this.size()) {
let current = this.getHead(),
previous,
index = 0;
if (position === 0) {
head.set(this, current.next);
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = current.next;
}
let l = this.size();
l--;
length.set(this, l);
return current.element;
} else {
return null;
}
}
remove(element) {//移除一項
let index = this.indexOf(element);
return this.removeAt(index);
}
indexOf(element) {//返回元素在列表中的索引,沒有返回-1
let current = this.getHead(),
index = 0;
while (current) {
if (element === current.element) {
return index;
}
index++;
current = current.next;
}
return -1;
}
isEmpty() {//鏈表不包含任何元素,返回true,如果長度大於0返回false
return this.size() === 0;
}
size() {//返回鏈表包含的元素個數
return length.get(this);
}
getHead() {//獲取鏈頭項
return head.get(this);
}
toString() {//已string輸出鏈表
let current = this.getHead(),
string = '';
while (current) {
string += current.element + (current.next ? ', ' : '');
current = current.next;
}
return string;
}
print() {//string格式打印到控制檯上。
console.log(this.toString());
}
}
return LinkedList;
})();
雙向鏈表
雙向鏈表和普通鏈表的區別在於。在鏈表中,一個節點只有鏈向下一個節點的鏈接,而在雙向鏈表中,鏈接是雙向的:一個鏈向下一個元素,另一個鏈向前一個元素。如下圖:
雙向鏈表提供了兩種送代列表的方法:從頭到尾,或者反過來。我們也可以訪問一個特定節點的下一個或前一個元素。在單向鏈表中,如果送代列表時錯過了要找的元素,就需要回到列表起點,重新開始送代。這是雙向鏈表的一個優點。
事例:
let list = new DoublyLinkedList();
list.append(15);
list.append(16);//15,16
list.print();//15,16
list.printInverse();//16, 15
list.insert(0,13);//13,15,16
list.removeAt(0);//15,16
ES6代碼實例:
let DoublyLinkedList = (function () {
class Node {
constructor(element) {
this.element = element;
this.next = null;
this.prev = null; //NEW
}
}
const length = new WeakMap();
const head = new WeakMap();
const tail = new WeakMap(); //NEW
class DoublyLinkedList {
constructor () {
length.set(this, 0);
head.set(this, null);
tail.set(this, null);
}
append(element) {
let node = new Node(element),
current, _tail;
if (this.getHead() === null) { //first node on list
head.set(this, node);
tail.set(this, node); //NEW
} else {
//attach to the tail node //NEW
_tail = this.getTail();
_tail.next = node;
node.prev = _tail;
tail.set(this, node);
}
//update size of list
let l = this.size();
l++;
length.set(this, l);
}
insert(position, element) {
//check for out-of-bounds values
if (position >= 0 && position <= this.size()) {
let node = new Node(element),
current = this.getHead(),
previous,
index = 0;
if (position === 0) { //add on first position
if (!this.getHead()) { //NEW
head.set(this, node);
tail.set(this, node);
} else {
node.next = current;
current.prev = node; //NEW {1}
head.set(this, node);
}
} else if (position === this.size()) { //last item //NEW
current = tail; // {2}
current.next = node;
node.prev = current;
tail.set(this, node);
} else {
while (index++ < position) { //{3}
previous = current;
current = current.next;
}
node.next = current;
previous.next = node;
current.prev = node; //NEW
node.prev = previous; //NEW
}
//update size of list
let l = this.size();
l++;
length.set(this, l);
return true;
} else {
return false;
}
}
removeAt(position) {
//check for out-of-bounds values
if (position > -1 && position < this.size()) {
let _head = this.getHead(),
_tail = this.getTail(),
current = _head,
previous,
index = 0;
//removing first item
if (position === 0) {
_head = current.next; // {1}
//if there is only one item, then we update tail as well //NEW
if (this.size() === 1) { // {2}
_tail = null;
} else {
_head.prev = null; // {3}
}
} else if (position === this.size() - 1) { //last item //NEW
current = _tail; // {4}
_tail = current.prev;
_tail.next = null;
} else {
while (index++ < position) { // {5}
previous = current;
current = current.next;
}
//link previous with current's next - skip it to remove
previous.next = current.next; // {6}
current.next.prev = previous; //NEW
}
head.set(this,_head);
tail.set(this,_tail);
//update size of list
let l = this.size();
l--;
length.set(this, l);
return current.element;
} else {
return null;
}
}
remove(element) {
let index = this.indexOf(element);
return this.removeAt(index);
}
indexOf(element) {
let current = this.getHead(),
index = -1;
//check first item
if (element == current.element) {
return 0;
}
index++;
//check in the middle of the list
while (current.next) {
if (element == current.element) {
return index;
}
current = current.next;
index++;
}
//check last item
if (element == current.element) {
return index;
}
return -1;
}
isEmpty() {
return this.size() === 0;
}
size() {
return length.get(this);
}
toString() {
let current = this.getHead(),
s = current ? current.element : '';
while (current && current.next) {
current = current.next;
s += ', ' + current.element;
}
return s;
}
inverseToString() {
let current = this.getTail(),
s = current ? current.element : '';
while (current && current.prev) {
current = current.prev;
s += ', ' + current.element;
}
return s;
}
print() {
console.log(this.toString());
}
printInverse() {
console.log(this.inverseToString());
}
getHead() {
return head.get(this);
}
getTail() {
return tail.get(this);
}
}
return DoublyLinkedList;
})();
循環鏈表
循環鏈表。就是最後一個元素指向不是null,而是指向第一個元素(head)。如下圖。
實例用法:
let circularLinkedList = new CircularLinkedList();
circularLinkedList.append(15);
circularLinkedList.append(16);//15,16
circularLinkedList.insert(0,14);//14,15,16
circularLinkedList.removeAt(0);//15,16
circularLinkedList.indexOf(16)//1
ES6循環鏈表實例:
let CircularLinkedList = (function () {
class Node {
constructor(element) {
this.element = element;
this.next = null;
}
}
const length = new WeakMap();
const head = new WeakMap();
class CircularLinkedList {
constructor () {
length.set(this, 0);
head.set(this, null);
}
append(element) {
let node = new Node(element),
current;
if (this.getHead() === null) { //first node on list
head.set(this, node);
} else {
current = this.getHead();
//loop the list until find last item
while (current.next !== this.getHead()) { //last element will be head instead of NULL
current = current.next;
}
//get last item and assign next to added item to make the link
current.next = node;
}
//set node.next to head - to have circular list
node.next = this.getHead();
//update size of list
let l = this.size();
l++;
length.set(this, l);
}
insert(position, element) {
//check for out-of-bounds values
if (position >= 0 && position <= this.size()) {
let node = new Node(element),
current = this.getHead(),
previous,
index = 0;
if (position === 0) { //add on first position
node.next = current;
//update last element
while (current.next !== this.getHead()) { //last element will be head instead of NULL
current = current.next;
}
head.set(this, node);
current.next = this.getHead();
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
node.next = current;
previous.next = node;
}
//update size of list
let l = this.size();
l++;
length.set(this, l);
return true;
} else {
return false;
}
}
removeAt(position) {
//check for out-of-bounds values
if (position > -1 && position < this.size()) {
let current = this.getHead(),
previous,
index = 0;
//removing first item
if (position === 0) {
while (current.next !== this.getHead()) { //needs to update last element first
current = current.next;
}
head.set(this, this.getHead().next);
current.next = this.getHead();
} else { //no need to update last element for circular list
while (index++ < position) {
previous = current;
current = current.next;
}
//link previous with current's next - skip it to remove
previous.next = current.next;
}
let l = this.size();
l--;
length.set(this, l);
return current.element;
} else {
return null;
}
}
remove(element) {
let index = indexOf(element);
return removeAt(index);
}
indexOf(element) {
let current = this.getHead(),
index = -1;
//check first item
if (element == current.element) {
return 0;
}
index++;
//check in the middle of the list
while (current.next !== this.getHead()) {
if (element == current.element) {
return index;
}
current = current.next;
index++;
}
//check last item
if (element == current.element) {
return index;
}
return -1;
}
isEmpty() {
return this.size() === 0;
}
size() {
return length.get(this);
}
getHead() {
return head.get(this);
}
toString() {
let current = this.getHead(),
s = current.element;
while (current.next !== this.getHead()) {
current = current.next;
s += ', ' + current.element;
}
return s.toString();
}
print() {
console.log(this.toString());
}
}
return CircularLinkedList;
})();