很多现在语言都比较灵活,提供了栈、队列等数据结构,这些实质上都是基于数组工作的。数组属于基本数据结构,属于顺序线性表。 它的优势在于数据的随机访问,但在长度的扩展和数据的随机插入、删除时则比较麻烦,这里我们介绍一个特殊的数据结构-链表
。
单向链表
链表是一组结点组成的集合,每个结点都包含两部分:存储数据元素的数据域和存储下一个结点的指针域。在物理存储上非连续,因此在插入和删除时效率更高。
数据结构 | 查找 | 插入/删除 | |
---|---|---|---|
顺序线性表(数组) | O(1) | O(n) | |
链表 | O(logn) | O(logn) |
优势:
- 链表结构可以充分利用存储空间,不需要预先知道数据的大小。
- 插入和删除结点效率高
劣势:
- 相比数组,无法进行随机读取
- 除了数据,还需要存储指针域,空间开销大
单向链表的操作
链表是一组结点的集合,每个结点都是用一个引用指向后一个结点,这个引用就是链,整个结构就是链表。
链表会包含一个特殊的头结点,用于整个链表的引用。链表的尾元素指向null结点,表示链表的结束。
向链表插入一个结点非常简单,就是将前结点的引用指向新结点,而将新结点指向原前结点的引用结点。
删除链表的一个结点,只需要将前结点的引用指向待删除结点的引用,而将待删除结点指向null即可。
实现一个单向链表(JS版)
// 一个结点类
class Node {
constructor(data) {
this.data = data;
this.next = null;
}
}
// 一个链表
class List {
constructor() {
this.head = new Node('head');
}
// 展示链表中所有的值
display() {
let cur = this.head;
while (cur.next !== null) {
console.log(cur.next.data);
cur = cur.next;
}
}
// 查找结点在链表中的位置
find(value) {
let cur = this.head;
while (cur.data !== value) {
if (cur.next === null) {
return null;
}
cur = cur.next;
}
return cur;
}
// 查找结点在链表中的前一个结点
findPrev(value) {
let cur = this.head;
while (cur.next !== null) {
if (cur.next.data === value) {
return cur;
}
cur = cur.next;
}
return null;
}
// 在指定结点后插入一个结点
insert(newValue, value) {
let node = new Node(newValue);
let cur = this.find(value);
if (cur === null) {
return false;
}
node.next = cur.next;
cur.next = node;
}
// 删除一个结点
remove(value) {
let prev = this.findPrev(value);
if (prev !== null) {
if (prev.next.next === null) {
prev.next = null;
} else {
prev.next = prev.next.next;
}
}
}
}
// 单向链表的使用
let list = new List();
list.insert('a', 'head');
list.insert('b', 'a');
list.display(); // a, b
list.insert('c', 'b');
list.remove('b');
list.display(); // a, c
复制代码
双向链表
双向链表与单向链表的区别就是除了有next结点的引用,还提供prev结点的引用,这样能够解决单向列表难以逆序遍历。
实现一个双向链表(JS版)
// 一个结点类
class Node {
constructor(data) {
this.data = data;
this.next = null;
this.prev = null;
}
}
// 一个双向链表(double direction list)
class ddList {
constructor() {
this.head = new Node('head');
}
// 展示链表中所有的值
display() {
let cur = this.head;
while (cur.next !== null) {
console.log(cur.next.data);
cur = cur.next;
}
}
// 反向展示链表中的所有值
displayReverse() {
let cur = this.findLast();
while (cur.prev !== null) {
console.log(cur.data);
cur = cur.prev;
}
}
// 查找结点在链表中的位置
find(value) {
let cur = this.head;
while (cur.data !== value) {
if (cur.next === null) {
return null;
}
cur = cur.next;
}
return cur;
}
// 查找最后一个结点
findLast() {
let cur = this.head;
while (cur.next !== null) {
cur = cur.next;
}
return cur;
}
// 在指定结点后插入一个结点
insert(newValue, value) {
let node = new Node(newValue);
let cur = this.find(value);
if (cur === null) {
return false;
}
node.next = cur.next;
node.prev = cur;
cur.next = node;
}
// 删除一个结点
remove(value) {
let cur = this.find(value);
if (cur === null) {
return false;
}
if (cur.next === null) {
cur.prev.next = null;
} else {
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
}
cur.next = null;
cur.prev = null;
}
}
// 双向链表的使用
let list = new ddList();
list.insert('a', 'head');
list.insert('b', 'a');
list.insert('c', 'b');
list.display(); // a, b, c
list.displayReverse(); // c, b, a
list.remove('b');
list.display(); // a, c
复制代码
双向循环链表(JS版)
循环链表的含义很简单,就是头尾相连,实现的独特之处,就是头部结点在初始化时,后继结点指向自身,这样在每次插入时都会继承这个设定。
// 一个结点类
class Node {
constructor(data) {
this.data = data;
this.next = null;
this.prev = null;
}
}
// 一个双向循环链表(double direction round list)
class roundList {
constructor() {
this.head = new Node('head');
this.head.next = this.head;
this.head.prev = this.head;
}
// 展示链表中所有的值
display() {
let cur = this.head.next;
while (cur !== this.head) {
console.log(cur.data);
cur = cur.next;
}
}
// 反向展示链表中的所有值
displayReverse() {
let cur = this.head.prev;
while (cur !== this.head) {
console.log(cur.data);
cur = cur.prev;
}
}
// 查找结点在链表中的位置
find(value) {
let cur = this.head.next;
while (cur !== this.head && cur.data !== value) {
cur = cur.next;
}
if (cur === this.head) {
return null;
}
return cur;
}
// 在指定结点后插入一个结点, 如果第2个参数为空,则在尾部插入
insert(newValue, value) {
let node = new Node(newValue);
let cur = null;
if (value === undefined) {
cur = this.head.prev;
} else {
cur = this.find(value);
if (cur === null) {
return false;
}
}
node.next = cur.next;
cur.next.prev = node;
node.prev = cur;
cur.next = node;
}
// 删除一个结点
remove(value) {
let cur = this.find(value);
if (cur === null) {
return false;
}
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
cur.next = null;
cur.prev = null;
}
}
// 双向循环链表的使用
let list = new roundList();
list.insert('a');
list.insert('b');
list.insert('c');
list.display(); // a, b, c
list.insert('d', 'b');
list.display(); // a, b, d, c
list.displayReverse(); // c, d, b, a
list.remove('b');
list.display(); // a, d, c
复制代码
双向循环链表(PHP版)
<?php
class Node {
public $data = null;
public $prev = null;
public $next = null;
public function __construct($value) {
$this->data = $value;
}
}
class RoundList {
public $head = null;
public function __construct() {
$this->head = new Node('head');
$this->head->prev = $this->head;
$this->head->next = $this->head;
}
public function display() {
$cur = $this->head->next;
while ($cur !== $this->head) {
echo $cur->data . '<br/>';
$cur = $cur->next;
}
}
public function displayReverse() {
$cur = $this->head->prev;
while ($cur !== $this->head) {
echo $cur->data . '<br/>';
$cur = $cur->prev;
}
}
public function find($value) {
$cur = $this->head->next;
while ($cur !== $this->head) {
if ($cur->data === $value) {
return $cur;
}
$cur = $cur->next;
}
return false;
}
public function insert($value, $beforeValue = null) {
if ($beforeValue === null) {
$cur = $this->head->prev;
} else {
$cur = $this->find($beforeValue);
if ($cur === false) {
return false;
}
}
$node = new Node($value);
$node->next = $cur->next;
$cur->next->prev = $node;
$node->prev = $cur;
$cur->next = $node;
return $node;
}
public function remove($value) {
$cur = $this->find($value);
if ($cur === false) {
return false;
}
$cur->prev->next = $cur->next;
$cur->next->prev = $cur->prev;
$cur->prev = null;
$cur->next = null;
return $cur;
}
}
$list = new RoundList();
$list->insert('a');
$list->insert('b');
$list->insert('c');
$list->display(); // a, b, c
$list->insert('d', 'b');
$list->display(); // a, d, d, c
$list->displayReverse(); // c, d, b, a
$list->remove('b');
$list->display(); // a, d, c
return;
?>
复制代码
总结
链表的基础知识基本就这么多了,虽然我都已经理解,但在写上面示例代码的时候还是出现了那么几次bug
,所以自己亲自动手写写还是很有必要的,可以有效避免眼高手低的坏毛病。
参考资料
- JS中的算法与数据结构——链表(Linked-list):juejin.im/entry/59cb7…
- 链表:baike.baidu.com/item/链表