【JavaScript数据结构系列】07-循环链表CircleLinkedList

码路工人 CoderMonkey

转载请注明作者与出处

1. 认识循环链表

首节点与尾节点相连的,就构成循环链表。其中,
单向链表首尾相连构成单向循环链表,
双向链表首尾相连构成双向循环链表。

循环链表,可以无限迭代,
迭代过程即是链表头不断移动的过程。
所以迭代过程中链表头尾节点是不断变化的。

1.1 单向循环链表 CircleLinkedList

与前文讲过的单向链表的区别是,
尾节点的后继不再指向 null,而是头节点。

1.2 双向循环链表 CircleDoublyLinkedList

与前文讲过的双向链表的区别是,
头节点的前驱不再指向 null,而是尾节点,
尾节点的后继不再指向 null,而是头节点。

请看码路工人画的循环链表结构示意图:

2. 常用方法

这里,我们以【单向循环链表】为例,

实现以下常用方法:

方法 描述
append(data) 向链表添加元素
insert(position, data) 向指定位置插入元素
remove(data) 删除元素
removeAt(position) 删除指定位置元素
update(position, data) 更新指定位置元素
findAt(position) 查找指定位置元素
indexOf(data) 获取元素位置
traverse(cb, reversal) 指定方向遍历
getNext() 迭代下一个节点
head() 获取首元素数据
tail() 获取尾元素数据
size() 获取链表大小
isEmpty() 判断链表是否为空
clear() 清空链表
toString() 字符串化

单向循环链表应是继承自链表,
有些方法没有区别使用继承即可,
这里为了阅读方便就在完整代码里写全了。

在添加、插入或删除节点时,
要注意相应地改变head/tail地指向。
在遍历时,因循环链表尾节点仍有后继,
要避免无限循环,使用index<count判断。

3. 代码实现

若要查看 data-struct-js 的完整代码实现:

git clone https://github.com/CoderMonkie/data-struct-js.git
# 或
git clone https://gitee.com/coder-monkey/data-struct-js.git

若要使用 data-struct-js 的 npm 包:

npm install data-struct-js

封装单向循环链表类

/**
* 单向循环链表
*/
function CircleLinkedList() {
this.__head = null
this.__tail = null
this.__count = 0 // 用Node表示链表内部元素
function Node(data) {
this.data = data
this.next = null Node.prototype.toString = function () {
return this.data.toString()
}
}
}

3.1 append(data)

添加节点方法

实现分析:

  • 如果为空链表,头节点与尾节点的指向新添加的节点
  • 然后尾节点的 next 指向头节点
  • 如果是非空链表,将尾节点的 next 指向新添加的节点
  • 再将尾节点指向 新添加的节点
  • 再将新的尾节点的 next 指向头节点
  • 添加完成,计数加1

与普通链表不同就在于要维护尾节点的 next 指向,
保持指向头节点。

// 添加节点
CircleLinkedList.prototype.append = function (data) { // 1. 创建新元素
let newNode = new Node(data) // 2.1 链表为空时,直接添加到末尾
if (this.isEmpty()) {
this.__head = newNode
this.__tail = newNode
}
// 2.2 链表非空时,末尾添加新元素
else {
this.__tail.next = newNode
this.__tail = newNode
}
// 2.3 将新的尾节点的后继指向头节点
this.__tail.next = this.__head // 3. 内部计数加1
this.__count += 1 return true
}

3.2 insert(position, data)

插入节点方法

实现分析:

  • 检查插入位置,范围 0~count-1
  • 创建新节点,插入位置分三种情况:
  • 1.位置0:修改head指向和tail的next指向
  • 2.位置count-1,同append方法
  • 3.其它位置:不涉及首尾节点的指向关系,按普通插入即可

// 插入节点
CircleLinkedList.prototype.insert = function (position, data) {
// 1.边界检查(插入位置)
if (position < 0 || position > this.__count) return false // 2. 创建新元素
var newNode = new Node(data) // 3.1插入到链表头部
if (position === 0) {
newNode.next = this.__head
this.__head = newNode
this.__tail.next = this.__head // 内部计数加1
this.__count += 1
}
// 3.2插入到链表尾部
else if (position === this.__count) {
this.append(data)
}
// 3.3以外
else {
let previous = null
let current = this.__head
let index = 0
while (index < position) {
previous = current
current = current.next
index++
}
previous.next = newNode
newNode.next = current // 内部计数加1
this.__count += 1
} return true
}

3.3 remove(data)

删除节点方法

这里调用了两个其它方法来实现。

  • 1.调用 indexOf 方法找到下标
  • 2.调用 removeAt 方法删除节点

// 删除节点
CircleLinkedList.prototype.remove = function (data) { const position = this.indexOf(data) if (position === -1) return false return this.removeAt(position)
}

3.4 removeAt(position)

删除指定位置节点

实现分析:

  • 1.参数的边界检查
  • 2.根据循环链表节点个数,分以下两种情况:
  • 2.1只有一个节点:首尾节点全部置空即可
  • 2.2多个节点的时候,分以下三种情况:
  • 2.2.1删除头节点
    • 将删除对象的前驱与后继相连
    • 更新头节点指向
    • 重新首尾相连(更新尾节点next指向)
  • 2.2.2删除尾节点
    • 将删除对象的前驱与后继相连
    • 更新尾节点指向
  • 2.2.3删除其它节点:
    • 将删除对象的前驱与后继相连
    • 三种情况下此处理相同

// 删除指定位置节点
CircleLinkedList.prototype.removeAt = function (position) {
// 1.边界检查
if (position < 0 || position >= this.__count) return false // 2.1.链表中只要一个元素的时候
if (this.__count === 1) {
// position 只能是 0
this.__head = this.__tail = null
}
// 2.2.链表中有多个元素的时候
else {
let index = 0
let previous = null
let current = this.__head // 2.2.1.找到指定位置元素
while (index++ < position) {
previous = current
current = current.next
}
// 2.2.2.使当前元素不再被引用(当删除的不是头节点)
previous && (previous.next = current.next) // A. 如果删除的是头节点
if (position === 0) {
// 更新 head 的指针
this.__head = current.next
// 重新连接首尾
this.__tail.next = this.__head
}
// B. 如果删除的是尾节点
else if (position === this.__count - 1) {
// 更新 tail 的指针
this.__tail = previous
}
} // 3.内部计数减1
this.__count -= 1 return true
}

3.5 update(position, data)

更新节点

实现分析:

  • 更新方法不涉及首尾节点指向关系
  • 与普通链表的更新处理相同

// 更新节点
// 因不涉及指向问题,更新方法与LinkedList相同
// 实际开发中使用继承自 CircleLinkedList 的 update 方法
CircleLinkedList.prototype.update = function (position, data) {
// 1.边界检查
if (position < 0 || position >= this.__count) return false var current = this.__head
var index = 0 // 2.找到指定位置元素
while (index++ < position) {
current = current.next
}
// 3.修改当前元素数据
current.data = data // 4.修改完成,返回 true
return true
}

3.6 findAt(position)

获取指定位置节点的数据


// 获取指定位置节点
CircleLinkedList.prototype.findAt = function (position) {
// 边界检查
if (position < 0 || position >= this.__count) return var index = 0
var current = this.__head while (index < position) {
current = current.next
index += 1
}
return current.data
}

3.7 indexOf(data)

获取下标


// 获取下标
CircleLinkedList.prototype.indexOf = function (data) {
var current = this.__head
var index = 0 // 根据指点数据查找节点元素,探查到尾节点后需停止
while (index < this.__count) {
if (current.data == data) {
return index
}
current = current.next
index += 1
} return -1
}

3.8 traverse(callback)

遍历函数


// 遍历链表
CircleLinkedList.prototype.traverse = function(callback) {
// 参数检查(回调函数)
if (!callback || toString.call(callback) !== '[object Function]') return // 计数
let index = 0
// 起始元素设为 head
let current = this.__head // 头部起始,向后遍历,到链表尾结束
while (index < this.__count) {
callback(current.data)
current = current.next
index += 1
}
}

3.9 getNext()

迭代函数


/**
* 迭代下一个节点
* 即链表头节点指针后移
*
* @returns 所在节点数据
* @memberof CircleLinkedList
*/
CircleLinkedList.prototype.getNext = function() {
if (this.isEmpty()) return undefined
let current = this.__head
if (this.__count > 1) {
this.__head = current.next
this.__tail = current
}
return current.data
}

3.10 其它方法

其它方法大都与链表方法一致(toString的循环条件不同)
一并放在完整代码里。

/**
* 链表:单向循环链表
*/
function CircleLinkedList() {
// 记录链表首个元素
this.__head = null
this.__tail = null
this.__count = 0 // 用Node表示链表内部元素
function Node(data) {
this.data = data
this.next = null Node.prototype.toString = function () {
return this.data.toString()
}
} // 添加节点
CircleLinkedList.prototype.append = function (data) { // 1. 创建新元素
let newNode = new Node(data) // 2.1 链表为空时,直接添加到末尾
if (this.isEmpty()) {
this.__head = newNode
this.__tail = newNode
}
// 2.2 链表非空时,末尾添加新元素
else {
this.__tail.next = newNode
this.__tail = newNode
}
// 2.3 将新的尾节点的后继指向头节点
this.__tail.next = this.__head // 3. 内部计数加1
this.__count += 1 return true
} // 插入节点
CircleLinkedList.prototype.insert = function (position, data) {
// 1.边界检查(插入位置)
if (position < 0 || position > this.__count) return false // 2. 创建新元素
var newNode = new Node(data) // 3.1插入到链表头部
if (position === 0) {
newNode.next = this.__head
this.__head = newNode
this.__tail.next = this.__head // 内部计数加1
this.__count += 1
}
// 3.2插入到链表尾部
else if (position === this.__count) {
this.append(data)
}
// 3.3以外
else {
let previous = null
let current = this.__head
let index = 0
while (index < position) {
previous = current
current = current.next
index++
}
previous.next = newNode
newNode.next = current // 内部计数加1
this.__count += 1
} return true
} // 删除节点
CircleLinkedList.prototype.remove = function (data) { const position = this.indexOf(data) if (position === -1) return false return this.removeAt(position)
} // 删除指定位置节点
CircleLinkedList.prototype.removeAt = function (position) {
// 1.边界检查
if (position < 0 || position >= this.__count) return false // 2.1.链表中只要一个元素的时候
if (this.__count === 1) {
// position 只能是 0
this.__head = this.__tail = null
}
// 2.2.链表中有多个元素的时候
else {
let index = 0
let previous = null
let current = this.__head // 2.2.1.找到指定位置元素
while (index++ < position) {
previous = current
current = current.next
}
// 2.2.2.使当前元素不再被引用(当删除的不是头节点)
previous && (previous.next = current.next) // A. 如果删除的是头节点
if (position === 0) {
// 更新 head 的指针
this.__head = current.next
// 重新连接首尾
this.__tail.next = this.__head
}
// B. 如果删除的是尾节点
else if (position === this.__count - 1) {
// 更新 tail 的指针
this.__tail = previous
}
} // 3.内部计数减1
this.__count -= 1 return true
} // 更新节点
// 因不涉及指向问题,更新方法与LinkedList相同
// 实际开发中使用继承自 CircleLinkedList 的 update 方法
CircleLinkedList.prototype.update = function (position, data) {
// 1.边界检查
if (position < 0 || position >= this.__count) return false var current = this.__head
var index = 0 // 2.找到指定位置元素
while (index++ < position) {
current = current.next
}
// 3.修改当前元素数据
current.data = data // 4.修改完成,返回 true
return true
} // 获取指定位置节点
CircleLinkedList.prototype.findAt = function (position) {
// 边界检查
if (position < 0 || position >= this.__count) return var index = 0
var current = this.__head while (index < position) {
current = current.next
index += 1
}
return current.data
} // 获取下标
CircleLinkedList.prototype.indexOf = function (data) {
var current = this.__head
var index = 0 // 根据指点数据查找节点元素,探查到尾节点后需停止
while (index < this.__count) {
if (current.data == data) {
return index
}
current = current.next
index += 1
} return -1
} // 遍历链表
CircleLinkedList.prototype.traverse = function(callback) {
// 参数检查(回调函数)
if (!callback || toString.call(callback) !== '[object Function]') return // 计数
let index = 0
// 起始元素设为 head
let current = this.__head // 头部起始,向后遍历,到链表尾结束
while (index < this.__count) {
callback(current.data)
current = current.next
index += 1
}
} /**
* 迭代下一个节点
* 即链表头节点指针后移
*
* @returns 所在节点数据
* @memberof CircleLinkedList
*/
CircleLinkedList.prototype.getNext = function() {
if (this.isEmpty()) return undefined
let current = this.__head
if (this.__count > 1) {
this.__head = current.next
this.__tail = current
}
return current.data
} // 获取节点个数
CircleLinkedList.prototype.size = function () {
return this.__count
} // 是否空链表
CircleLinkedList.prototype.isEmpty = function () {
return this.__count === 0
} // 清空链表
CircleLinkedList.prototype.clear = function () {
this.__head = null
this.__count = 0
} // 获取字符串
CircleLinkedList.prototype.toString = function () {
let str = '[ '
let index = 0
let current = this.__head
while (index < this.__count) {
str += current + ' -> '
current = current.next
index += 1
}
str += ` | Count: ${this.__count} ]`
return str
}
}

4. 使用

// ---------------------------------------------
// Test: CircleLinkedList
// ---------------------------------------------
console.log('----Test: CircleLinkedList----') var circle = new CircleLinkedList() circle.append("1.Plan")
circle.append("2.Do")
circle.append("3.Check")
circle.append("4.Act") for (let j = 0; j < 4; j++) {
const item = circle.getNext()
console.log(`${j} : ${item}`)
} circle.traverse(item=>{
console.log(`Traversing : ${item}`)
}) console.log('---------------------')
circle.remove('2.Do')
console.log(`After remove element 2.Do : ${circle}`, )
circle.removeAt(2)
console.log(`After removeAt(2): ${circle}`)
----Test: CircleLinkedList----
0 : 1.Plan
1 : 2.Do
2 : 3.Check
3 : 4.Act
Traversing : 1.Plan
Traversing : 2.Do
Traversing : 3.Check
Traversing : 4.Act
---------------------
After remove element 2 : [ 1.Plan -> 3.Check -> 4.Act -> | Count: 3 ]
After removeAt(2): [ 1.Plan -> 3.Check -> | Count: 2 ]

以上。


做了一份 npm 工具包 data-struct-js
基于 ES6 实现的 JavaScript 数据结构,
虽然这个小轮子很少会被使用,
也许对于初学者学习 JavaScript 会有点帮助。
只要简单 install 一下即可,感兴趣的话还可以去
GitHub / Gitee 看源码。(Star 表支持~)

npm install data-struct-js --save-dev

https://github.com/CoderMonkie/data-struct-js
https://gitee.com/coder-monkey/data-struct-js

最后,感谢您的阅读和支持~


-end-

【JavaScript数据结构系列】07-循环链表CircleLinkedList的更多相关文章

  1. JavaScript进阶系列07,鼠标事件

    鼠标事件有Keydown, Keyup, Keypress,但Keypress与Keydown和Keyup不同,如果按ctrl, shift, caps lock......等修饰键,不会触发Keyp ...

  2. 【JavaScript数据结构系列】03-队列Queue

    [JavaScript数据结构系列]03-队列Queue 码路工人 CoderMonkey 转载请注明作者与出处 1. 认识队列Queue结构 队列,跟我们的日常生活非常贴近,我们前面举例了食堂排队打 ...

  3. 【JavaScript数据结构系列】05-链表LinkedList

    [JavaScript数据结构系列]05-链表LinkedList 码路工人 CoderMonkey 转载请注明作者与出处 ## 1. 认识链表结构(单向链表) 链表也是线性结构, 节点相连构成链表 ...

  4. 【JavaScript数据结构系列】06-双向链表DoublyLinkedList

    [JavaScript数据结构系列]06-双向链表DoublyLinkedList 码路工人 CoderMonkey 转载请注明作者与出处 1. 认识双向链表 不同于普通链表/单向链表,双向链表最突出 ...

  5. 【JavaScript数据结构系列】04-优先队列PriorityQueue

    [JavaScript数据结构系列]04-优先队列PriorityQueue 码路工人 CoderMonkey 转载请注明作者与出处 ## 1. 认识优先级队列 经典的案例场景: 登机时经济舱的普通队 ...

  6. 【JavaScript数据结构系列】02-栈Stack

    [JavaScript数据结构系列]02-栈Stack 码路工人 CoderMonkey 转载请注明作者与出处 ## 1. 认识栈结构 栈是非常常用的一种数据结构,与数组同属线性数据结构,不同于数组的 ...

  7. 【JavaScript数据结构系列】01-数组Array

    [JavaScript数据结构系列]01-数组Array 码路工人 CoderMonkey 转载请注明作者与出处 # [JavaScript数据结构系列] # 01-数组Array 数组: 是有序的元 ...

  8. 【JavaScript数据结构系列】00-开篇

    [JavaScript数据结构系列]00-开篇 码路工人 CoderMonkey 转载请注明作者与出处 ## 0. 开篇[JavaScript数据结构与算法] 大的计划,写以下两部分: 1[JavaS ...

  9. JavaScript进阶系列06,事件委托

    在"JavaScript进阶系列05,事件的执行时机, 使用addEventListener为元素同时注册多个事件,事件参数"中已经有了一个跨浏览器的事件处理机制.现在需要使用这个 ...

随机推荐

  1. Ngxin 开启CDN 日志获取不了真实IP的解决办法。

    nginx配置里面在http{ 后加入如下两行代码即可: set_real_ip_from 0.0.0.0/0;real_ip_header X-Forwarded-For; 重启nginx生效. 注 ...

  2. codeforce 270C Magical Boxes

    C. Magical Boxes Emuskald is a well-known illusionist. One of his trademark tricks involves a set of ...

  3. 【BIM】BIMFACE中创建雾化效果

    背景 在BIM运维场景初始化的时候,一般都是首先将整个运维对象呈现在用户面前,例如一座大厦.一座桥梁.一个园区等等,以便于用户进行总览,总体把握运维对象,如果这个宏大的场景边界过于清晰,与背景融合也不 ...

  4. OSG程序设计之更新回调

    更新回调(Update Callback)涉及到一个类:osg::NodeCallback.这个类重载了函数调用操作符.当回调动作发生时,将会执行这一操作符的内容. 如果节点绑定了更新回调函数,那么在 ...

  5. ASR6505带MCU STM8L+SX1262内核LoRa芯片

    LoRa是Semtech公司采用和推广的一种基于扩频技术的超远距离无线传输方案.一种简单的能实现远距离.长电池寿命.大容量的系统,进而扩展传感网络,LoRaWAN,LinkWAN,MESH组网,自组网 ...

  6. 【FPGA篇章三】FPGA常用语句:Verilog基本语法要素

    欢迎大家关注我的微信公众账号,支持程序媛写出更多优秀的文章 Verilog中总共有十九种数据类型,我们先介绍四个最基本的数据类型,他们是: reg型.wire型.integer型.parameter型 ...

  7. 自定义IDOC

    目录 1需求场景    4 2配置发送端IDOC    4 2.1定义段(WE31)    4 2.2定义基本类型(WE30)    4 2.3定义消息类型(WE81)    5 2.4定义传输结构  ...

  8. 安卓集成Unity开发示例(一)

    本项目目的是在移动端的 Native App 中以库的形式集成已经写好的 Unity 工程,利用 Unity 游戏引擎便捷的开发手段进行跨平台开发. Unity官方文档 Unity as a Libr ...

  9. 多线程测试时的辅助类--CountDownLatch

    多线程时,很多时候由于mian线程与多线程结束时间不可控,造成无法测试 辅助测试类---CountDownLatch 我看的视频教程匿名内部类无法使用外部的变量,所以CountDownLatch定义为 ...

  10. [hdu5371 Hotaru's problem]最大回文半径

    题意:在一个字符串里面找最长的[A][B][A]子串,其中[A][B]是回文串,[A]和[B]的长度相等 思路:[A][B]是回文串,所以[B][A]也是回文串.先预处理出每个点的最大回文半径Ri,枚 ...