前言
面试暂时告一段落,也收到了自己满意的offer,但这只是人生路上的一个小考,还是要继续向前走的,那么今天一起来了解一下数据结构之链表
什么是链表
链表是物理存储单元上非连续的、非顺序的存储结构。链表的节点包含两部分:
- 存储数据元素的数据域
- 指向下一个节点的指针域
概念还是比较抽象的,看组数据:
// 这只是一个简单的demo
Node {
element: 1,
next: Node {
element: 2,
next: null
}
}
这就是一个简单的单链表,Node为节点,element为节点数据,next为指针,如果是双链表每个节点则还会有pre指向上个节点
链表&数组
我们通常会拿链表和数组做对比,它们均是线性结构,但优缺点却是完全相反的
- 存储
数组从栈中分配空间,系统自动申请空间,属于静态分配内存,占用内存少
链表从堆中分配空间,每个元素需手动分配空间,属于动态分配内存,占用内存多
- 查询
数组在物理内存上是连续存储的,随机查询时间复杂度为O(1),随机查询即访问arr[0]和arr[100]时使用的时间相同
链表通常认为是不连续的,每次查询都需要从第一个节点开始查起,所以访问list[0]和list[100]时list[100]用时更长,随机查询时间复杂度为O(n)
- 插入、删除
数组是连续,正是因为如此,一旦插入/删除一条数据,后续所有数据下标都会发生改变,时间复杂度为O(n)
链表只需要改变指针即可,不会对其他节点的位置有影响,时间复杂度为O(1)
总结:链表占用内存更大,插入、删除效率高于数组,但随机查询劣于数组
单链表
链表通常有一个头结点、创建节点的类以及增删查等方法,一起来实现一下,首先创建一个节点类:
class Node{
constructor(element){
this.element = element;
this.next = null;
}
}
接着创建链表及新增方法:
class LinkedList{
constructor(){
this.head = null;
this.length = 0;
}
append(element){
const node = new Node(element);
let current = null;
if(this.head === null){
this.head = node;
}else{
current = this.head;
while(current.next){
current = current.next;
}
current.next = node
}
this.length++
}
}
ok,我们先来新增一些数据看一下:
const linkedList = new LinkedList();
linkedList.append('a');
linkedList.append('b');
console.log('head',linkedList.head)
console.log('length',linkedList.length) // 2
// head Node {
// element: 'a',
// next: Node {
// element: 'b',
// next: null
// }
// }
这里不一一举例其他方法,直接看完整代码
class Node {
constructor(element) {
this.element = element;
this.next = null;
}
}
class LinkedList {
constructor() {
this.head = null;
this.length = 0;
}
// 尾部添加
append(element) {
const node = new Node(element);
let current = null;
if (this.head === null) {
this.head = node;
} else {
current = this.head;
while (current.next) {
current = current.next;
}
current.next = node
}
this.length++
}
// 插入
insert(position, element) {
if (position > -1 && position < this.length) {
const node = new Node(element)
let current = this.head;
let index = 0;
let pre = null;
if (position == 0) {
node.next = current;
this.head = node;
} else {
while (index++ < position) {
pre = current;
current = current.next;
}
node.next = current
pre.next = node;
}
this.length++
return true;
}
return false;
}
// 根据索引移除
remove(position) {
if (position > -1 && position < this.length) {
let current = this.head;
let index = 0;
let pre = null;
if (position == 0) {
current = current.next;
this.head = current;
} else {
while (index++ < position) {
pre = current;
current = current.next;
}
pre.next = current.next;
}
this.length--
return true;
} else {
console.log('移除元素不存在');
return false;
}
}
// 查询索引
findIndex(element) {
let index = 0;
let current = this.head;
while (current) {
if (current.element === element) {
return index;
}
index++;
current = current.next;
}
console.log('元素不存在');
return -1;
}
// 根据元素移除
removeElement(element) {
const position = this.findIndex(element);
this.remove(position)
}
}
双链表
单链表可以访问下一个节点,但无法直接访问上一个节点的,所以逆向访问链表是非常耗时,而双链表中不仅有指向下一个节点的指针,也会保存指向上一个节点的指针,看下边一个demo:
Node {
element: "a",
next: Node {
element: "b",
next: Node { element: "c", pre: Node, next: null },
pre: Node { element: "a", pre: null, next: Node }
},
pre: null
}
所以,我们创建节点的类就变成了:
class Node {
constructor(element) {
this.element = element;
this.prev = null;
this.next = null;
}
}
双链表实现:
class DoublyLinkedList {
constructor() {
this.head = null;
this.tail = null; // 存储新添加的节点
this.length = 0;
}
append(element) {
let node = new Node(element)
if (!this.head) {
this.head = node
this.tail = node
} else {
node.prev = this.tail
// 为最后一层数据添加指针指向
this.tail.next = node
// 将tail赋值为最新添加的数据
this.tail = node
}
this.length++
return true
}
}
还是先来尝试一下:
const doublyLinkedList = new DoublyLinkedList();
doublyLinkedList.append('a')
doublyLinkedList.append('b')
doublyLinkedList.append('c')
console.log('list', doublyLinkedList) // 打印结果符合预期,自己copy下来看一下
console.log('length', doublyLinkedList.length) // 3
ok,看一下双链表完整代码:
class Node {
constructor(element) {
this.element = element;
this.prev = null;
this.next = null;
}
}
class DoublyLinkedList {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
append(element) {
let node = new Node(element)
if (!this.head) {
this.head = node
this.tail = node
} else {
node.prev = this.tail
this.tail.next = node
this.tail = node
}
this.length++
return true
}
insert(position, element) {
if (position > -1 && position <= this.length) {
const node = new Node(element);
let current = this.head;
let index = 0;
let previous = null;
if (position === 0) {
if (this.head === null) {
this.head = node;
this.tail = node;
} else {
node.next = current;
current.prev = node;
this.head = node;
}
} else if (position == this.length) {
current = this.tail;
current.next = node;
node.prev = current;
this.tail = node;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
node.next = current
previous.next = node
current.prev = node
node.prev = previous
}
this.length++;
return true;
}
return false;
}
remove(position) {
if (position < 0 || position > this.length - 1) return false
if (position == 0) {
if (this.length == 1) {
this.head = null
this.tail = null
} else {
this.head = this.head.next
this.head.prev = null
}
} else if (position == this.length - 1) {
this.tail = this.tail.prev
this.tail.next = null
} else {
let current = ''
let index = 0
if (position < this.length / 2) {
current = this.head
index = 0
while (index++ < position - 1) {
current = current.next
}
} else {
index = this.length - 1
current = this.tail
while (index-- > position - 1) {
current = current.prev
}
}
current.next = current.next.next
current.next.prev = current
}
this.length--
return true
}
findIndex(element) {
let index = 0;
let current = this.head;
while (current) {
if (current.element === element) {
return index;
}
index++;
current = current.next;
}
console.log('元素不存在');
return -1;
}
removeElement(element) {
const position = this.findIndex(element);
this.remove(position)
}
}
循环链表
循环链表是在单链表的基础上,将尾节点的指针指向头节点,我们这里也做个简单实现:
class Node {
constructor(element) {
this.element = element;
this.next = null;
}
}
class LinkedList {
constructor() {
this.head = null;
this.length = 0;
}
append(element) {
const node = new Node(element);
let current = null;
if (this.head === null) {
this.head = node;
this.head.next = node
} else {
current = this.head;
while (current.next !== this.head) {
current = current.next;
}
current.next = node
node.next = this.head
}
this.length++
}
}
const linkedList = new LinkedList();
linkedList.append('a');
linkedList.append('b');
linkedList.append('c');
console.log('head', linkedList.head)
// 打印结果a->b->c->a->b-c....
反转链表
var reverseList = function (head) {
let prev = null
let curr = head
while (curr) {
[curr.next, prev, curr] = [prev, curr, curr.next]
}
return prev
};
合并两个有序链表
const mergeTwoLists = function (l1, l2) {
if (l1 === null) {
return l2;
}
if (l2 === null) {
return l1;
}
if (l1.element < l2.element) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
};
结语
今年过年不回家,后悔了:cry: