Iwen's blog Iwen's blog
首页
  • 前端文章

    • JavaScript
    • Vue
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《JavaScript高级程序设计》笔记
    • 《ES6 教程》笔记
    • 《Vue》笔记
    • 《TypeScript 从零实现 axios》
    • 小程序笔记
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • Linux
  • 学习
  • 面试
  • 心情杂货
  • 友情链接
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
复盘
关于

Iwen

不摸鱼的哥哥
首页
  • 前端文章

    • JavaScript
    • Vue
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《JavaScript高级程序设计》笔记
    • 《ES6 教程》笔记
    • 《Vue》笔记
    • 《TypeScript 从零实现 axios》
    • 小程序笔记
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • Linux
  • 学习
  • 面试
  • 心情杂货
  • 友情链接
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
复盘
关于
  • Vue

  • Vue进阶

  • CSS

  • ES6

  • Base

  • Core

  • Array

  • Object

  • String

  • Async

  • Browser

  • Http

  • 性能优化

  • 正则

  • 经典总结

  • 设计模式

  • 数据结构

    • 栈
    • 队列
    • 链表
      • 链表的定义
      • Node类
      • LinkedList类
      • 双向链表
      • 循环链表
    • 树
    • 集合
    • 字典
    • 图
    • 散列表
    • 堆
  • 算法

  • 手写

  • TypeScript

  • 复盘
  • 数据结构
Mr.w
2020-11-29

链表

# 链表(Linked-list)

如果你发现数组在实际使用时很慢,就可以考虑使用链表来替代它。除了对数据的随机访问,链表几乎可以用在任何可以使用一维数组的情况中。如果需要随机访问,数组仍然是更好的选择。

# 链表的定义

另一个例子是寻宝游戏。你有一条线索,这条线索就是指向寻找下一条线索的地点的指针。你顺着这条链接去下一个地点,得到另一条指向再下一处的线索。得到链表中间的线索的唯一办法,就是从起点(第一条线索)顺着链表寻找。

还有一个可能是用来说明链表的最流行的例子,那就是火车。一列火车是由一系列车厢(也称车皮)组成的。每节车厢或车皮都相互连接。你很容易分离一节车皮,改变它的位置、添加或移除它。下图演示了一列火车。每节车皮都是链表的元素,车皮间的连接就是指针。

# Node类

Node类包含连个属性: element 用来保存节点上的数据,next 用来保存指向下一个节点的链接,具体实现如下:

function Node(element) {
    this.element = element;     // 当前节点的元素
    this.next = null;           // 下一个节点链接
}
1
2
3
4

# LinkedList类

LinkedList类提供了对链表进行操作的方法,包括插入删除节点,查找给定的值等。值得注意的是,它只有一个 属性,那就是使用一个 Node 对象来保存该链表的头节点。

function linkedList() {

    let head = null,                // 第一个链表 默认标识
        length = 0,                 // 链表长度
        current;                    // 当前传入元素

    // 添加
    this.push = (el) => {
        const node = new Node(el)
        if (head === null) {   // 插入第一个链表
            head = node;
        } else {
            current = head;
            while (current.next) {  // 获取最后一个链表
                current = current.next
            }
            current.next = node     // 将最后一个链表的next赋值为新元素
        }
        length++    // 链表长度
    }

    // 移除
    this.removeAt = (index) => {
        if (index >= 0 && index < length) {     // 是否越界
            // 移除第一个链表,特殊对待
            if (index === 0) {
                head = head.next
            } else {
                let previous;
                current = head;
                for (let i = 0; i < index; i++) {
                    previous = current
                    current = current.next
                }
                // 移除当前索引的 current
                previous.next = current.next
            }
            length--
            return head
        }
        return undefined;
    }

    // 指定位置添加
    this.insert = (el, index) => {
        if (index >= 0 && index < length) {     // 是否越界
            const node = new Node(el);
            if (index === 0) {
                current = head
                node.next = current
                head = node
            } else {
                let previous;
                current = head;
                for (let i = 0; i < index; i++) {
                    previous = current
                    current = current.next
                }
                // 介于 previous & current 两者间插入
                previous.next = node
                node.next = current
            }
            length++
            return head
        }
        return false;
    }

    // 查找是否存在,有 => 索引,否 => -1
    this.indexOf = (el) => {
        current = head
        for (let i = 0; i < length; i++) {
            if (current.element === el) {
                return i
            }
            current = current.next
        }
        return -1
    }

    // 移除
    this.remove = (element) => {
        this.removeAt(this.indexOf(element))
    }

    // 是否为空
    this.isEmpty = () => {
        return length === 0
    }

    // 获取长度
    this.size = () => {
        return length
    }

    // 获取最开头的链表
    this.getHead = () => {
        return head
    }

    // 打印链表
    this.toString = () => {
        if (head === null) {
            return ''
        }
        current = head
        let str = current.element
        while (current.next) {
            current = current.next
            str += current.element
        }
        return str
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

# 双向链表

双向链表和普通链表的区别在于,在链表中,一个节点只有链向下一个节点的链接;

而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向上一个元素

function DoublyLinkedList() {
    let head = null,                // 第一个链表
        tail = null,                // 最后一个链表
        length = 0,                 // 链表长度
        current, previous;          // 当前传入元素
        
    // 指定位置添加
    this.insert = (el, index) => {
        if (index >= 0 && index < length + 1) {     // 是否越界
            const node = new DoublyNode(el);
            if (index === 0) {
                if (head == null) {         // ① 链表内元素为空
                    head = node
                    tail = node
                } else {                    // 链表内存在元素
                    current = head
                    current.prev = node
                    node.next = current
                    head = node
                }
            } else if (index === length) {  // ② 在末尾插入元素
                current = tail
                current.next = node
                node.prev = current
                tail = node
            } else {                        // ③ 在链表中插入元素
                current = head
                for (let i = 0; i < index; i++) {
                    previous = current
                    current = current.next
                }
                previous.next = node
                node.next = current
                current.prev = node
                node.prev = previous
            }
            length++
                console.log(head, tail)
            return true
        }
        return false;
    }

    // 指定位置移除
    this.removeAt = (index) => {
        if (index >= 0 && index < length) {     // 是否越界
            if (index === 0) {
                if (length === 1) {
                    head = null
                    tail = null
                } else {
                    current = head
                    head = current.next
                    head.prev = current.prev
                }
            } else if (index === length - 1) {
                current = tail
                tail = current.prev
                tail.next = current.next
            } else {
                current = head
                for (let i = 0; i < index; i++) {
                    previous = current
                    current = current.next
                }
                previous.next = current.next
                current.next.prev = previous
            }
            return true
        }
        return undefined
    }

    this.log = function() {
        current = head
        let str = current.element
        while (current.next) {
            current = current.next
            str = str + ' ' + current.element
        }
        return str
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

# 循环链表

循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用。循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针(tail.next)不是引用undefined,而是指向第一个元素(head)

队列
树

← 队列 树→

最近更新
01
flex布局页面自适应滚动条问题
12-28
02
前后端分离开发请求接口跨域如何携带cookie的问题
12-28
03
怎么实现div长宽比固定width-height4比3
12-28
更多文章>
Theme by Vdoing | Copyright © 2017-2022 Iwen | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式