链表

概念

链表是存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素有一个存储元素本身的节点和一个指向下一个元素的引用(指针)组成

An image

看上图我们将链表的尾元素指向了 null 节点,表示链接结束的位置,链表的起始位置比较麻烦,我们添加一个header的特殊节点表示头节点,如下:

An image

插入节点

确定插入节点的位置,修改前驱节点指向插入的新节点,新节点指向原来前驱节点指向的节点,如下图

An image

删除节点

待删除的前驱节点指向待删除节点的下一节点,如下图

An image

链表和数组对比

数组和链表都可以实现存储多个元素,两者各有自己的优劣。

  • 数组可以通过元素的下表,直接访问任何位置的任何元素;而想要访问链表中间的一个元素,需要从起点(表头)开始迭代链表,直到找到所需的元素
  • 链表对比数组的一个好处是,添加或移除元素的时候不需要移动其他元素

链表的设计

写两个类,一个Node类表示节点,一个LinkedList类提供插入、删除节点等一些操作

Node类

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

LinkedList类

// 链表类
function LinkedList () {
  this.head = new Node( 'head' );     // 头节点
  this.find = find;                   // 查找节点
  this.insert = insert;               // 插入节点
  this.remove = remove;               // 删除节点
  this.findPrev = findPrev;           // 查找前一个节点
  this.display = display;             // 显示链表
}
1
2
3
4
5
6
7
8
9

head节点的next属性初始化为 null ,当有新元素插入时,next会指向新的元素

insert方法:插入一个节点

// 插入节点
function insert ( newElement , item ) {
  var newNode = new Node( newElement );
  var currNode = this.find( item );
  newNode.next = currNode.next;
  currNode.next = newNode;
}
1
2
3
4
5
6
7

find方法:查找给定节点

思路

  1. 创建一个新节点,将链表的头节点赋给这个新创建的节点
  2. 然后在链表上循环,如果当前节点的 element 属性和我们要找的信息不符,就将当前节点移动到下一个节点
  3. 如果查找成功,该方法返回包含该数据的节点;否则,就会返回null
// 查找给定节点
function find ( item ) {
  var currNode = this.head;
  while ( currNode.element != item ){
    currNode = currNode.next;
  }
  return currNode;
}
1
2
3
4
5
6
7
8

dispaly方法:打印链表

// 显示链表元素
function display () {
  var currNode = this.head;
  while ( !(currNode.next == null) ){
      console.log( currNode.next.element );
      currNode = currNode.next;
  }
}
1
2
3
4
5
6
7
8

remove方法:删除一个节点

思路

  1. 找到待删除节点的前一个节点
  2. 修改它的 next 属性,使其不再指向待删除的节点,而是待删除节点的下一个节点
  3. 我们定义一个 findPrevious 方法遍历链表,查找待删除节点的前一个节点
// 查找带删除节点的前一个节点
function findPrev( item ) {
  var currNode = this.head;
  while ( !( currNode.next == null) && ( currNode.next.element != item )){
      currNode = currNode.next;
  }
  return currNode;
}

// 删除节点
function remove ( item ) {
  var prevNode = this.findPrev( item );
  if( !( prevNode.next == null ) ){
    prevNode.next = prevNode.next.next;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

示例

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

// 链表类
function LinkedList () {
  this.head = new Node( 'head' );     // 头节点
  this.find = find;                   // 查找节点
  this.insert = insert;               // 插入节点
  this.remove = remove;               // 删除节点
  this.findPrev = findPrev;           // 查找前一个节点
  this.display = display;             // 显示链表
}

// 插入节点
function insert ( newElement , item ) {
  var newNode = new Node( newElement );
  var currNode = this.find( item );
  newNode.next = currNode.next;
  currNode.next = newNode;
}

// 查找给定节点
function find ( item ) {
  var currNode = this.head;
  while ( currNode.element != item ){
    currNode = currNode.next;
  }
  return currNode;
}

// 显示链表元素
function display () {
  var currNode = this.head;
  while ( !(currNode.next == null) ){
      console.log( currNode.next.element );
      currNode = currNode.next;
  }
}

// 查找带删除节点的前一个节点
function findPrev( item ) {
  var currNode = this.head;
  while ( !( currNode.next == null) && ( currNode.next.element != item )){
      currNode = currNode.next;
  }
  return currNode;
}

// 删除节点
function remove ( item ) {
  var prevNode = this.findPrev( item );
  if( !( prevNode.next == null ) ){
    prevNode.next = prevNode.next.next;
  }
}

// 示例
var nameList = new LinkedList()
nameList.insert('木子', 'head')
nameList.insert('小明', '木子')
nameList.insert('凯阳', '小明')
nameList.display() // 木子 小明 凯阳

nameList.insert('林夕', '木子')
nameList.display() // 木子 林夕 小明 凯阳

nameList.remove('木子')
nameList.display() // 林夕 小明 凯阳
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

第二种设计

var LinkedList = function() {
  const Node = function(element) {
    this.element = element
    this.next = null
  }

  let head = null
  let current
  let length = 0

  // 在链表末尾加入元素
  this.append = function(element) {
    const node = new Node(element)
    if (head === null) {       // 插入第一个链表
      head = node
    } else {
      current = head
      while (current.next) {     // 找到最后一个节点
        current = current.next
      }
      current.next = node
    }
    length++
  }

  // 移除指定位置元素
  this.removeAt = function(position) {
    if (position > -1 && position < length) {
      let previous
      let index = 0
      if (position === 0) {         // 如果是第一个链表的话, 特殊对待
        head = head.next
      } else {
        current = head
        while (index < position) {  // 循环找到当前要删除元素的位置
          previous = current
          current = current.next
          index++
        }
        previous.next = current.next
      }
      length--
    }
  }

  // 在指定位置加入元素
  this.insert = function(position, element) {
    const node = new Node(element)
    let index = 0
    let current, previous
    if (position > -1 && position < length + 1) {
      if (position === 0) { // 在链表最前插入元素
        current = head
        head = node
        head.next = current
      } else {
        current = head
        while (index < position) { // 同 removeAt 逻辑, 找到目标位置
          previous = current
          current = current.next
          index++
        }
        previous.next = node       // 在目标位置插入相应元素
        node.next = current
      }
      length++
    }
  }

  // 链表中是否含有某个元素, 如果有的话返回相应位置, 无的话返回 -1
  this.indexOf = function(element) {
    let index = 0
    current = head
    while (index < length) {
      if (current.element === element) {
        return index
      }
      current = current.next
      index++
    }
    return -1
  }

  // 移除某元素
  this.remove = function(element) {
    const position = this.indexOf(element)
    this.removeAt(position)
  }

  // 获取大小
  this.size = function() {
    return length
  }

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

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

  // 打印链表元素
  this.log = function() {
    current = head
    let str = current.element
    while (current.next) {
      current = current.next
      str = str + ' ' + current.element
    }
    return str
  }
}


// 示例
var animals = new LinkedList()
animals.append('马')
animals.append('羊')
animals.append('牛')
animals.append('猪')
animals.log() // 马 羊 牛 猪

animals.remove('牛')
animals.log() // 马 羊 猪
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
115
116
117
118
119
120
121
122
123
124
125
126
127

双向链表

TODO

待补充

循环链表

TODO

待补充