【数据结构四】链表

链表:

要存储多个元素,数组可能是最常用的数据结构。
数组结构的优点是通过下标值存取元素效率非常高。
数组结构的缺点:

  1. 数组的创建通常需要申请一段连续的内存空间(一整块的内存)。
  2. 数组的大小是固定的,如果当前数组不能满足容量需求,那么久需要进行扩容(虽然 JS 中数组的大小不是固定的,但是大多数编程语言都是固定的)。
    扩容:一般情况是申请一个更大的数组,然后将当前数组中的元素复制过去。
  3. 在一个数组中进行插入和删除元素的成本很高,需要进行大量元素的位移。(比如:一个数组中有 10000 个元素,想要在第 100 位插入一个元素,那么原先在第 100 位及之后的元素都需要向后挪动一位)。

要存储多个元素,另一个选择就是链表。

链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的指针组成。

链表很类似于火车:有一个火车头,火车头会连接第一节车厢;每节车厢就是一个元素,车厢中有乘客(类似于元素本身的数据),还会连接到下一个车厢(指向下一个元素)。
请添加图片描述

链表的优点和缺点:

链表的优点:

  1. 内存空间不必是连续的,因此可以充分利用计算机的内存,实现灵活的内存动态管理。
  2. 链表不必在创建时就确定大小,并且大小可以无限地延伸下去。
  3. 链表在插入和删除元素时,时间复杂度可以达到O(1),相比数组来说效率高很多。

链表的缺点:链表无法通过下标值直接访问元素,访问任何一个位置的元素,都需要从头开始一个个访问,直到找到对应的元素。

链表的实现:

// 封装节点类
function Node(element, next) {
  // 链表中的节点需要包含两个内容:一个是元素本身的数据,另一个是指向下一个节点的指针
  this.element = element
  this.next = null
}

// 封装链表
function LinkedList() {
  // head 指针指向第一个元素
  this.head = null
  // 记录链表的长度
  this.length = 0
}
// 链表中的操作
// 向链表的尾部添加一个新的项
LinkedList.prototype.append = function(element) {
  // 1. 创建节点
  var node =  new Node(element)

  // 2. 如果添加的是第一个节点,让 head 指针指向它即可
  if (this.length === 0) {
    this.head = node
  } else {
    // 3. 如果添加的不是第一个节点,从头开始找

    // 首先,获取到 head 指向的第一个节点
    var current = this.head
    // 然后,判断当前节点的 next 的指向:如果 next 的指向为空,则说明是最后一个节点;否则就不是最后一个节点,获取到下一个节点接着判断
    while (current.next) {
      current = current.next
    }
    // 一直判断到节点的 next 指向为空,找到了最后一个节点,让它的 next 指向要添加的新的项
    current.next = node
  }

  // 4. 将链表的长度加 1
  this.length++
}
// 向链表的特定位置插入一个新的项
LinkedList.prototype.insert = function(element, position) {
  // 1. 对 position 进行越界判断
  if (position < 0 || position > this.length) return false

  // 2. 创建节点
  var node =  new Node(element)

  // 3. 如果插入的位置是第一个,那么需要 head 指向新插入的节点,新插入节点的 next 指向原来的第一个节点
  if (position === 0) {
    node.next = this.head
    this.head = node
  } else {
    // 4. 如果插入的位置不是第一个

    // 遍历循环一直到 position 位置:保存前一个节点为 previous;当前节点为 current
    var previous = null
    var current = null
    for (var i = 0; i <= position; i++) {
      if (i === 0) {
        previous = null
        current = this.head
      } else {
        // 此处变量值中的 current 是上一次循环所得的结果,也就是上一个节点
        previous = current
        current = current.next
      }
    }

    // 将前一个节点的 next 指向新插入的节点;新插入节点的 next 指向原先在 position 位置的节点
    previous.next = node
    node.next = current
  }

  // 5. 将链表的长度加 1
  this.length++

  return true
}
// 获取链表对应位置的节点的数据
LinkedList.prototype.get = function(position) {
  // 1. 对 position 进行越界判断
  if (position < 0 || position >= this.length) return null
	
  // 2. 遍历循环一直到 position 位置,获取 position 位置的节点
  var current = null
  for (var i = 0; i <= position; i++) {
    if (i === 0) {
      current = this.head
    } else {
      // 此处 变量值 current.next 中的 current 是上一次循环所得的结果,也就是上一个节点
      current = current.next
    }
  }

  return current.element
}
// 返回元素在链表中的索引,如果链表中没有该元素则返回-1
LinkedList.prototype.indexOf = function(element) {
  var index = 0
  var current = this.head

  // 开始查找:如果 current 不为空,则进行值的比较
  while(current) {
    if (current.element === element) {
      return index
    }

    current = current.next
    index++
  }

  // 如果 current 为空,则返回 -1
  return -1
}
// 修改某个位置的元素
LinkedList.prototype.update = function(element, position) {
	// 1. 对 position 进行越界判断
  if (position < 0 || position >= this.length) return false
	
  // 2. 遍历循环一直到 position 位置,获取 position 位置的节点
  var current = null
  for (var i = 0; i <= position; i++) {
    if (i === 0) {
      current = this.head
    } else {
      // 此处 变量值 current.next 中的 current 是上一次循环所得的结果,也就是上一个节点
      current = current.next
    }
  }

  // 3. 将 position 位置的节点的 element 修改为新的 element
  current.element = element
}
// 从链表的特定位置移除一项,并返回被移除的项
LinkedList.prototype.removeAt = function(position) {
	// 1. 对 position 进行越界判断
  if (position < 0 || position >= this.length) return null

  // 2. 如果删除的是第一个节点,则直接让 head 指向第一个节点的下一个节点
  var current = null
  if (position === 0) {
    current = this.head
    this.head = this.head.next
  } else {
    // 3. 如果删除的不是第一个:遍历循环一直到 position 位置:保存前一个节点为 previous;当前节点为 current
    var previous = null
    for (var i = 0; i <= position; i++) {
      if (i === 0) {
        previous = null
        current = this.head
      } else {
        // 此处变量值中的 current 是上一次循环所得的结果,也就是上一个节点
        previous = current
        current = current.next
      }
    }

    // 让前一个节点的 next 指向原先在 position 位置节点的下一个节点
    previous.next = current.next
  }

  // 5. 将链表的长度减 1
  this.length--

  // 6. 返回被删除的项
  return current.element
}
// 从链表中移除一项
LinkedList.prototype.remove = function(element) {
	// 1. 获取 element 在链表中的位置
  var position = this.indexOf(element)

  // 2. 根据位置删除节点
  return this.removeAt(position)
}
// 如果链表中没有任何元素就返回 true,否则返回 false
LinkedList.prototype.isEmpty = function() {
	return this.length === 0
}
// 返回链表中的元素个数
LinkedList.prototype.size = function() {
	return this.length
}
// 将链表中元素的内容以字符串形式返回
LinkedList.prototype.toString = function() {
  // 1. 获取第一个节点
  var current = this.head
  var resultStr = ''

  // 2. 循环获取每一个节点:如果 current 不为空,则将当前节点的内容拼接到 resultStr 中,让 current 指向下一个节点
  while (current) {
    resultStr += current.element + ' '
    current = current.next
  }

  return resultStr
}
// 调用链表
var linkedList = new LinkedList()
linkedList.append('Mary')
linkedList.append('Lily')
linkedList.append('Tom')
linkedList.append('Bob')
linkedList.insert('Lee', 2)
console.log(linkedList.toString()) // Mary Lily Lee Tom Bob 
console.log(linkedList.get(2)) // Lee
console.log(linkedList.indexOf('Lee')) // 2
linkedList.update('Peny', 2) // Mary Lily Peny Tom Bob 
console.log(linkedList.get(2)) // Peny
console.log(linkedList.removeAt(2)) // Peny

双向链表:

单向链表:链表相连的过程是单向的,只能是从头遍历到尾或者从尾遍历到头(一般是从头遍历到尾),实现的原理的是上一个链表节点中有一个指向下一个节点的指针。
单向链表有一个明显的缺点:单向链表可以轻松地到达下一个节点,但是想要回到上一个节点却很难。

双向链表:链表相连的过程是双向的,既可以从头遍历到尾,也可以从尾遍历到头,实现的原理是一个节点既有向前链接的指针,也有向后连接的指针。

请添加图片描述

双向链表的优缺点:

双向链表的优点:可以有效地解决单向链表难以回到上一个节点的问题。

双向链表的缺点:

  1. 相对于单向链表,占用内存空间更大一些。
  2. 每次在插入或删除某个节点时,需要处理 4 个引用,而不是单向链表的 2 个引用,实现起来要相对困难。

双向链表的实现:

  1. 可以使用一个 head 和一个 tail 分别指向头部和尾部的节点;
  2. 每个节点都由三部分组成:指向前一个节点的指针(pre)、元素的数据(item)、指向后一个节点的指针(next);
  3. 双向链表的第一个节点的 pre 是 null;
  4. 双向链表的最后一个节点的 next 是 null;
// 封装节点类
function Node(element, prev, next) {
  // 双向链表中的节点需要包含两个内容:一个是元素本身的数据,一个是指向上一个节点的指针,另一个是指向下一个节点的指针
  this.element = element
  this.prev = null
  this.next = null
}

// 封装双向链表
function DoubleLinkedList() { 
  // head 指针指向第一个元素
  this.head = null
  // tail 指向最后一个节点
  this.tail = null
  // 记录双向链表的长度
  this.length = 0
}
// 双向链表中的操作
// 向双向链表的尾部添加一个新的项
DoubleLinkedList.prototype.append = function(element) {
  // 1. 创建节点
  var node =  new Node(element)

  // 2. 如果添加的是第一个节点,让 head 指针和 tail 指针都指向它即可
  if (this.length === 0) {
    this.head = node
    this.tail = node
  } else {
    // 3. 如果添加的不是第一个节点

    // this.tail 指向的是最后一个节点,让最后一个节点的 next 指向要添加的新的项,新添加的项的 prev 指向最后一个节点,this.tail 指向新添加的项
    this.tail.next = node
    node.prev = this.tail
    this.tail = node
  }

  // 4. 将链表的长度加 1
  this.length++
}
// 向双向链表的特定位置插入一个新的项
DoubleLinkedList.prototype.insert = function(element, position) {
  // 1. 对 position 进行越界判断
  if (position < 0 || position > this.length) return false

  // 2. 创建节点
  var node =  new Node(element)

  // 3. 如果双向链表为空,且插入的位置是第一个
  if (this.length === 0) {
    this.head = node
    this.tail = node
  } else {
    // 4. 如果双向链表不为空

    // 5. 插入的位置是第一个:让原先第一个节点的 prev 指向新插入的节点;新插入节点的 next 指向原先第一个节点;head 指向新插入的节点
    if (position === 0) {
      this.head.prev = node
      node.next = this.head
      this.head = node
    } else if (position === this.length) {
      // 6. 插入的位置是最后一个
      node.prev = this.tail
      this.tail.next = node
      this.tail = node
    } else {
      // 7. 插入的位置是中间

      // 遍历循环一直到 position 位置:保存当前节点为 current
      var current = null
      for (var i = 0; i <= position; i++) {
        if (i === 0) {
          current = this.head
        } else {
          // 此处变量值中的 current 是上一次循环所得的结果,也就是上一个节点
          current = current.next
        }
      }

      // 将新插入节点的 prev 指向原先在 position 位置前的节点; 将新插入节点的 next 指向原先在 position 位置的节点;将原先在 position 位置前的节点的 next 指向新插入的节点;将原先在 position 位置节点的 prev 指向新插入的节点
      node.prev = current.prev
      node.next = current
      current.prev.next = node
      current.prev = node
    }
  }

  // 5. 将链表的长度加 1
  this.length++

  return true
}
// 获取双向链表对应位置的节点的数据
DoubleLinkedList.prototype.get = function(position) {
  // 1. 对 position 进行越界判断
  if (position < 0 || position >= this.length) return null
	
  // 2. 遍历循环一直到 position 位置,获取 position 位置的节点
  var current = null
  for (var i = 0; i <= position; i++) {
    if (i === 0) {
      current = this.head
    } else {
      // 此处 变量值 current.next 中的 current 是上一次循环所得的结果,也就是上一个节点
      current = current.next
    }
  }

  return current.element
}
// 返回元素在双向链表中的索引,如果双向链表中没有该元素则返回-1
DoubleLinkedList.prototype.indexOf = function(element) {
  var index = 0
  var current = this.head

  // 开始查找:如果 current 不为空,则进行值的比较
  while(current) {
    if (current.element === element) {
      return index
    }

    current = current.next
    index++
  }

  // 如果 current 为空,则返回 -1
  return -1
}
// 修改某个位置的元素
DoubleLinkedList.prototype.update = function(element, position) {
	// 1. 对 position 进行越界判断
  if (position < 0 || position >= this.length) return false
	
  // 2. 遍历循环一直到 position 位置,获取 position 位置的节点
  var current = null
  for (var i = 0; i <= position; i++) {
    if (i === 0) {
      current = this.head
    } else {
      // 此处 变量值 current.next 中的 current 是上一次循环所得的结果,也就是上一个节点
      current = current.next
    }
  }

  // 3. 将 position 位置的节点的 element 修改为新的 element
  current.element = element
}
// 从双向链表的特定位置移除一项,并返回被移除的项
DoubleLinkedList.prototype.removeAt = function(position) {
	// 1. 对 position 进行越界判断
  if (position < 0 || position >= this.length) return null

  var current = null
  // 2. 如果双向链表只有一个节点
  if (this.length === 1) {
    current = this.head
    this.head = null
    this.tail = null
  } else {
    // 3. 如果双向链表不是只有一个节点

    // 4. 要删除的是第一个节点:将第二个节点的 prev 指向 null;head 指向第二个节点
    if (position === 0) {
      current = this.head
      this.head.next.prev = null
      this.head = this.head.next
    } else if (position === this.length - 1) {
      // 5. 要删除的是最后一个节点:将倒数第二个节点的 next 指向 null;tail 指向倒数第二个节点
      current = this.tail
      this.tail.prev.next = null
      this.tail = this.tail.prev
    } else {
      // 6. 要删除的是中间的节点:遍历循环一直到 position 位置,保存当前节点为 current
      for (var i = 0; i <= position; i++) {
        if (i === 0) {
          current = this.head
        } else {
          // 此处变量值中的 current 是上一次循环所得的结果,也就是上一个节点
          current = current.next
        }
      }

      // 让原先在 position 位置节点的前一个节点的 next 指向原先在 position 位置节点的下一个节点;原先在 position 位置节点的下一个节点的 prev 指向原先在 position 位置节点的前一个节点
      current.prev.next = current.next
      current.next.prev = current.prev
    }
  }

  // 5. 将链表的长度减 1
  this.length--

  // 6. 返回被删除的项
  return current.element
}
// 从双向链表中移除一项
DoubleLinkedList.prototype.remove = function(element) {
	// 1. 获取 element 在链表中的位置
  var position = this.indexOf(element)

  // 2. 根据位置删除节点
  return this.removeAt(position)
}
// 如果双向链表中没有任何元素就返回 true,否则返回 false
DoubleLinkedList.prototype.isEmpty = function() {
	return this.length === 0
}
// 返回双向链表中的元素个数
DoubleLinkedList.prototype.size = function() {
	return this.length
}
// 将双向链表中元素的内容以字符串形式返回
DoubleLinkedList.prototype.toString = function() {
  return this.forwardString()
}
// 返回正向遍历的元素内容的字符串形式
DoubleLinkedList.prototype.forwardString = function() {
  // 1. 获取第一个节点
  var current = this.head
  var resultStr = ''

  // 2. 循环获取每一个节点:如果 current 不为空,则将当前节点的内容拼接到 resultStr 中,让 current 指向下一个节点
  while (current) {
    resultStr += current.element + ' '
    current = current.next
  }

  return resultStr
}
// 返回反向遍历的元素内容的字符串形式
DoubleLinkedList.prototype.backworddString = function() {
  // 1. 获取最后一个节点
  var current = this.tail
  var resultStr = ''

  // 2. 循环获取每一个节点:如果 current 不为空,则将当前节点的内容拼接到 resultStr 中,让 current 指向上一个节点
  while (current) {
    resultStr += current.element + ' '
    current = current.prev
  }

  return resultStr
}
// 调用双向链表
var doubleLinkedList = new DoubleLinkedList()
doubleLinkedList.append('Mary')
doubleLinkedList.append('Lily')
doubleLinkedList.append('Tom')
doubleLinkedList.append('Bob')
doubleLinkedList.insert('Lee', 2)
console.log(doubleLinkedList.toString()) // Mary Lily Lee Tom Bob 
console.log(doubleLinkedList.get(2)) // Lee
console.log(doubleLinkedList.indexOf('Lee')) // 2
doubleLinkedList.update('Peny', 2) // Mary Lily Peny Tom Bob 
console.log(doubleLinkedList.get(2)) // Peny
console.log(doubleLinkedList.removeAt(2)) // Peny
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值