链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
使用链表结构可以克服数组结构需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
链表的特点:
- 插入、删除数据效率高 O(1) 级别(只需更改指针指向即可),随机访问效率低 O(n) 级别(需要从链头至链尾进行遍历)。
- 和数组相比,内存空间消耗更大,因为每个存储数据的节点都需要额外的空间存储后继指针。
5.1. 单链表
每个节点只包含一个指针,即后继指针。
class Node {
constructor(element) {
this.element = element
this.next = null
}
}
class LinkedList {
// #
constructor() {
this.count = 0; // 计数:有几个结点
this.head = null; // 链头
}
push(element) {
const node = new Node(element)
// hearder 是空
if(this.head === null) {
this.head = node
} else {
let current = this.head;
while(current.next !== null){
current = current.next
}
current.next = node
}
this.count++;
}
// 指定位置删除
removeAt(index){
if(index >=0 && index < this.count){
let current = this.head
if(index === 0){
this.head = this.head.next
} else {
// 让前一个 next = 当前的 next
// let previous
// for(let i=0;i<index;i++){
// previous = current
// current = current.next
// }
let previous = this.getNodeAt(index - 1)
current = previous.next
previous.next = current.next
}
this.count--
return current.element
}
return
}
getNodeAt(index){
if(index>=0 && index<this.count){
let node = this.head
for(let i=0; i<index; i++){
node = node.next
}
return node
}
return
}
// 根据数据 返回索引的方法
indexOf(element) {
let current = this.head
for(let i=0;i<this.count;i++) {
if(this.equalFn(element, current.element)){
return i
}
current = current.next
}
return -1
}
// 判断是否是同个元素
equalFn(a, b){
// 方式一:不适用于直接 push({a:1}) 的对象
// return a===b
// 方式二:比较复杂
// 1.只能做一层对比:判断是对象,对象1[key] === 对象2[key]
// 2.在1基础上,写一个递归函数,深度检查是否相等
// 方式三:简单暴力,转换成字符串的方式比较。
// 缺点:对于list.push({a:1,b:2}) list.push({b:2,a:1})不生效。
return JSON.stringify(a) === JSON.stringify(b)
// 方式四:imumutable(第三方js库)
}
remove(element){
const index = this.indexOf(element)
return this.removeAt(index)
}
insert(element, index) {
if(index>=0 && index<=this.count) {
const node = new Node(element)
if(index === 0) {
const current = this.head
node.next = current
this.head = node
} else {
const previous = this.getNodeAt(index - 1)
const current = previous.next
node.next = current
previous.next = node
}
this.count++
return true
}
return false
}
isEmpty(){
return this.size() === 0
}
size(){
return this.count
}
getHead(){
return this.head
}
}
var list = new LinkedList()
5.2. 单链表的应用
更松散,能解决的问题更多
- 在双端队列中的应用 - 回文检查
function test(str){
const lowstr = str.toLocaleLowerCase().split(" ").join("")
let dequeue = new LinkedList()
for(let i=0;i<lowstr.length;i++){
dequeue.push(lowstr[i])
}
let isEqual = true
while(dequeue.size()>1){
if(dequeue.removeAt(0) !== dequeue.removeAt(dequeue.size() - 1)){
isEqual = false
break;
}
}
return isEqual
}
test("D a d")
2. 在队列中的应用 - 击鼓传花
function game(list,num) {
let queue = new LinkedList()
for(let i=0;i<list.length;i++) {
queue.push(list[i])
}
while(queue.size()>1) {
for(let i=0;i<num;i++){
queue.push(queue.removeAt(0))
}
console.log(queue.removeAt(0),"淘汰了")
}
return queue.removeAt(0)
}
game(["user1","user2","user3","user4","user5"],7)
3. 在栈中的应用 - 进制转换
function convert(decNumber, base){
let remStack = new LinkedList()
let number = decNumber
let string = ""
let baseString = "0123456789ABCDEF"
while(number > 0) {
remStack.push(number%base)
number = Math.floor(number/base)
}
while(!(remStack.isEmpty())){
string += baseString[remStack.removeAt(remStack.size() - 1)]
}
return string
}
convert(50, 2) // 110010
convert(50, 8) // 62
convert(500, 16) // 1F4
5.3. 双向链表
节点除了存储数据外,还有两个指针分别指向前一个节点地址(前驱指针 prev)和下一个节点地址(后继指针 next)
class DoublyNode extends Node {
constructor(element) {
super(element)
this.prev = null
}
}
class DoublyLinkedList extends LinkedList{
// #
constructor() {
super()
this.tail = null // 尾巴
}
push(element) {
const node = new DoublyNode(element)
// prev next
// hearder 是空
if(this.head === null) {
this.head = node
this.tail = node
} else {
this.tail.next = node
node.prev = this.tail
this.tail = node
}
this.count++;
}
insert(element, index) {
if(index>=0 && index<=this.count) {
const node = new DoublyNode(element)
let current = this.head
if(index === 0) {
if(this.head === null) {
this.head = node;
this.tail = node;
} else {
node.next = this.head;
this.head.prev = node;
this.head = node
}
} else if(index === this.count){
current = this.tail
current.next = node
node.prev = current
this.tail = node
} else {
const previous = this.getNodeAt(index - 1)
current = previous.next
node.next = current
current.prev = node
previous.next = node
node.prev = previous
}
this.count++
return true
}
return false
}
// 指定位置删除
removeAt(index){
if(index >=0 && index < this.count){
let current = this.head
if(index === 0){
this.head = this.head.next
if(this.count === 1){
this.tail = null
}else{
this.head.prev = undefined
}
} else if(index === this.count-1){
current = this.tail;
this.tail = current.prev;
this.tail.next = undefined;
} else {
let previous = this.getNodeAt(index - 1)
current = previous.next
previous.next = current.next
current.next.prev = previous
}
this.count--
return current.element
}
return
}
getHead(){
return this.head
}
getTail(){
return this.tail
}
}
var list = new DoublyLinkedList()
5.4. 循环链表
循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针(tail.next)不是引用 undefined,而是指向第一个元素(head)
class CirularLinkedList extends LinkedList{
constructor() {
super()
}
push(element) {
const node = new Node(element)
let current;
// hearder 是空
if(this.head === null) {
this.head = node
} else {
current = this.getNodeAt(this.size() - 1)
current.next = node
}
node.next = this.head
this.count++;
}
insert(element, index) {
if(index>=0 && index<=this.count) {
const node = new Node(element)
let current = this.head
if(index === 0) {
if(this.head === null) {
this.head = node;
node.next = this.head
} else {
node.next = current
current = this.getNodeAt(this.size() - 1)
this.head = node
current.next = this.head
}
} else {
const previous = this.getNodeAt(index - 1)
node.next = previous.next
previous.next = node
}
this.count++
return true
}
return false
}
// 指定位置删除
removeAt(index){
if(index >=0 && index < this.count){
let current = this.head
if(index === 0){
if(this.size() === 1){
this.head = undefined
}else{
let last = this.getNodeAt(this.size() - 1)
this.head = this.head.next
last.next = this.head
}
} else {
const previous = this.getNodeAt(index -1)
current = previous.next
previous.next = current.next
}
this.count--
return current.element
}
return
}
}
var list = new CirularLinkedList()