数据结构之链表

95 阅读5分钟

链表

是什么?

概念: 用任意一组存储单元存储线性表的数据元素(这组存储单元可以是连续的也可以是不连续的)。因此,存储单元不仅要存储自身数据还需要存储后继存储单元的地址,这两个部分组成了节点,存储信息的是数据域,存储后记节点位置信息的是指针域。n个节点链成一个链表

根据概念不难得出,链表是由节点组成,节点是由数据和下一节点位置组成。

节点示意图如下:

image.png

链表示意图如下:

image-1.png

在链表中一般会用一个只存储下一节点位置的节点当作链表的开始节点,我们一般称为头指针。

Javascript实现思路

  1. 首先定义节点LNode,LNode由两部分组成,LNode 数据域(data),LNode 指针域(next);
  2. 其次定义头指针LHNode,LHNode由指针域组成(next);
/** 节点定义 */
const LNode = function () {
  /**
   * 节点数据
   */
  this.data = null
  /**
   * 下一节点位置信息
   */
  this.next = null
  /**
   * 设置节点数据
   * @param {*} data 节点数据
   */
  this.setData = (data) => {
    this.data = data
  }
  /**
   * 设置后继节点信息
   * @param {LNode} next 后继点位置信息
   */
  this.setNext = (next) => {
    this.next = next
  }
}
const LHNode = function () {
  /**
  * 节点数据
  */
  this.data = null
  /**
   * 下一节点位置信息
   */
  this.next = null
  /**
   * 设置后继节点信息
   * @param {LNode} next 后继点位置信息
   */
  this.setNext = (next) => {
    this.next = next
  }
}
/**
 * 链表增加节点(尾插法)
 * @param {LHNode} LLists 头节点
 * @param {LNode} LNode 插入节点
 */
const AddNode = (LLists, LNode) => {
  let current = LLists
  while (current.next) {
    current = current.next
  }
  current.next = LNode
}
const LLists = new LHNode()
const NewNode = new LNode()
NewNode.setData("hello")
AddNode(LLists, NewNode)
const NewNode1 = new LNode()
NewNode1.data = " word";
AddNode(LLists, NewNode1)

const NewNode2 = new LNode()
NewNode2.setData(" !");

AddNode(LLists, NewNode2)

console.log(LLists)

删除方法

分析:
假设有链表存在5个元素
[data:null,next:002,adress:001],
[data:"1",next:003,adress:002],
[data:"2",next:004,adress:003],
[data:"3",next:005,adress:004],
[data:"4",next:006,adress:005],
[data:"5",next:null,adress:006]

示意图如下:

image-2.png

删除第3个节点(不包含头节点)

首先取出节点2和节点3,然后将节点2的next设置为节点3的next,将节点3回收设置为null。
示意图如下:

image-3.png

image-4.png 代码实现:

/** 节点定义 */
const LNode = function () {
/**
 * 节点数据
 */
this.data = null
/**
 * 下一节点位置信息
 */
this.next = null
/**
 * 设置节点数据
 * @param {*} data 节点数据
 */
this.setData = (data) => {
  this.data = data
}
/**
 * 设置后继节点信息
 * @param {LNode} next 后继点位置信息
 */
this.setNext = (next) => {
  this.next = next
}
}
const LHNode = function () {
/**
* 节点数据
*/
this.data = null
/**
 * 下一节点位置信息
 */
this.next = null
/**
 * 设置后继节点信息
 * @param {LNode} next 后继点位置信息
 */
this.setNext = (next) => {
  this.next = next
}
}

/**
* 链表增加节点(尾插法)
* @param {LHNode} LLists 头节点
* @param {LNode} LNode 插入节点
*/
const AddNode = (LLists, LNode) => {
let current = LLists
while (current.next) {
  current = current.next
}
current.next = LNode
}
/**
* 删除指定节点
* @param {LHNode} LLists 
* @param {number} delIndex 删除节点的位置
* @returns 
*/
const DeleteNode = (LLists, delIndex) => { 
let i = 0;
/**不可删除头节点 */
if(delIndex === 0) return
let current = LLists;
while (i !== delIndex-1 && current.next) { 
  i++;
  current = current.next
}
/**delIndex大于节点总数 */
if (i !== delIndex - 1) return 
// 删除节点上一个节点为current
/**取出要删除的节点 */
let delNode = current.next
/**取出要删除节点的后继节点 */
const delNextNode = delNode.next
/**将当前节点的后继节点设置为delNextNode */
current.setNext(delNextNode)
/**回收删除节点 */
delNode = null
}

const lists = new LHNode()
for (let index = 0; index < 5; index++) {
const newNode = new LNode()
newNode.setData(index+1)
AddNode(lists,newNode)
}
console.log("origin list: ",lists)
DeleteNode(lists, 3)
console.log("deleted lists", lists)

插入方法

分析:
假设有链表存在4个元素
[data:null,next:002,adress:001],
[data:"1",next:003,adress:002],
[data:"2",next:004,adress:003],
[data:"4",next:006,adress:005],
[data:"5",next:null,adress:006]

示意图如下: image-4.png

将新的节点插入在第3个节点(不包含头节点)

首先取出第二个节点和第三个节点,然后将要插入节点的next设置为取出的第三个节点,最后将取出的第二个节点的next设置为要插入节点 示意图如下:

image-5.png

image-6.png 代码实现:

/** 节点定义 */
const LNode = function () {
/**
 * 节点数据
 */
this.data = null
/**
 * 下一节点位置信息
 */
this.next = null
/**
 * 设置节点数据
 * @param {*} data 节点数据
 */
this.setData = (data) => {
  this.data = data
}
/**
 * 设置后继节点信息
 * @param {LNode} next 后继点位置信息
 */
this.setNext = (next) => {
  this.next = next
}
}
const LHNode = function () {
/**
* 节点数据
*/
this.data = null
/**
 * 下一节点位置信息
 */
this.next = null
/**
 * 设置后继节点信息
 * @param {LNode} next 后继点位置信息
 */
this.setNext = (next) => {
  this.next = next
}
}

![image-4.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/544c2b41472e4c0fbef628155ac4f0f7~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1350&h=442&s=38339&e=png&b=1e1e1e)
/**
* 链表增加节点(尾插法)
* @param {LHNode} LLists 头节点
* @param {LNode} LNode 插入节点
*/
const addNode = (LLists, LNode) => {
let current = LLists
while (current.next) {
  current = current.next
}
current.next = LNode
}
/**
* 删除指定节点
* @param {LHNode} LLists 
* @param {number} delIndex 删除节点的位置
*/
const deleteNode = (LLists, delIndex) => { 
let i = 0;
/**不可删除头节点 */
if(delIndex === 0) return
let current = LLists;
while (i !== delIndex-1 && current.next) { 
  i++;
  current = current.next
}
/**delIndex大于节点总数 */
if (i !== delIndex - 1) return 
// 删除节点上一个节点为current
/**取出要删除的节点 */
let delNode = current.next
/**取出要删除节点的后继节点 */
const delNextNode = delNode.next
/**将当前节点的后继节点设置为delNextNode */
current.setNext(delNextNode)
/**回收删除节点 */
delNode = null
}
/**
* 在指定节点插入元素
* @param {LHNode} LLists 
* @param {number} insertIndex 插入节点的位置
* @param {LNode} newNode 插入节点
*/
const insertNode = (LHNode, insertIndex, newNode) => { 
//禁止修改头节点,和非法位置
if (insertIndex <= 0) return 
let current = LHNode
let i = 0;
while (current.next && i !== insertIndex - 1) {
  i++;
  current = current.next;
}
//insertIndex 大于节点总数加1
if (i !== insertIndex-1) return
//当前元素为current
//取出当前元素的后继元素
const nextNode = current.next
//将待插入元素的后继元素设置为当前元素的后继元素(nextNode)
newNode.setNext(nextNode)
//最后将当前元素的后继元素设置为插入元素(newNode)
current.setNext(newNode)
}
const lists = new LHNode()
for (let index = 0; index < 5; index++) {
const newNode = new LNode()
newNode.setData(index+1)
addNode(lists,newNode)
}
console.log("origin list: ",lists)
deleteNode(lists, 3)
console.log("deleted lists", lists)
const newNode3 = new LNode()
newNode3.setData(3)
insertNode(lists, 3, newNode3)
console.log("inserted lists: ",lists)

循环链表

就是链表的做后一个节点的next设置为头指针的地址

双向链表

除了头节点,其余节点会多出一个指针(pre)域指向此节点的上一个节点的地址,类似于循环应用

双向循环链表

双向链表的基础上,头指针的pre指向最后一个节点的地址,最后一个节点next指针指向头节点的位置