循环链表是一种特殊的单链表。
循环链表和单链表相似,节点类型都是一样。
唯一的区别是,在创建循环链表的时候,让其头节点的 next 属性指向它本身。
即:
这种行为会导致链表中每个节点的 next 属性都指向链表的头节点,换句话说,也就是链表的尾节点指向了头节点,形成了一个循环链表。如下图所示:
循环链表:在单链表的基础上,将尾节点的指针指向头结点,就构成了一个循环链表。环形链表从任意一个节点开始,都可以遍历整个链表。
代码:
// 循环链表 function CircularLinkedList() { // 节点 function Node(element) { this.element = element; // 当前节点的元素 this.next = null; // 下一个节点指针 } var length = 0, head = null; this.append = function(element) { var node = new Node(element), current; if (!head) { head = node; // 头的指针指向自己 node.next = head; } else { current = head; while (current.next !== head) { current = current.next; } current.next = node; // 最后一个节点指向头节点 node.next = head; } length++; return true; }; this.insert = function(position, element) { if (position > -1 && position < length) { var node = new Node(element), index = 0, current = head, previous; if (position === 0) { // 头节点指向自己 node.next = head; head = node; } else { while (index++ < position) { previous = current; current = current.next; } previous.next = node; node.next = current; } length++; return true; } else { return false; } }; this.removeAt = function(position) { if (position > -1 && position < length) { var current = head, previous, index = 0; if (position === 0) { head = current.next; } else { while (index++ < position) { previous = current; current = current.next; } previous.next = current.next; } length--; return current.element; } else { return false; } }; this.remove = function(element) { var current = head, previous, indexCheck = 0; while (current && indexCheck < length) { if (current.element === element) { if (indexCheck == 0) { head = current.next; length--; return true; } else { previous.next = current.next; length--; return true; } } else { previous = current; current = current.next; indexCheck++; } } return false; }; this.remove = function() { if (length === 0) { return false; } var current = head, previous, indexCheck = 0; if (length === 1) { head = null; length--; return current.element; } while (indexCheck++ < length) { previous = current; current = current.next; } previous.next = head; length--; return current.element; }; this.indexOf = function(element) { var current = head, index = 0; while (current && index < length) { if (current.element === element) { return index; } else { index++; current = current.next; } } return -1; }; this.isEmpty = function() { return length === 0; }; this.size = function() { return length; }; // 由于链表使用了 Node 类,就需要重写继承自 JavaScript 对象默认的 toString() 方法,让其只输出元素的值 this.toString = function() { var current = head, string = '', indexCheck = 0; while (current && indexCheck < length) { string += ',' + current.element; current = current.next; indexCheck++; } return string.slice(1); }; // 获取链表头部元素 this.getHead = function() { return head.element; }; // 打印链表数据 this.print = function() { console.log(this.toString()); }; // 获取整个链表 this.list = function() { console.log('head: ', head); return head; }; }测试:
// 创建单向链表实例 var circularLinked = new CircularLinkedList(); console.log(circularLinked.removeAt(0)); // false console.log(circularLinked.isEmpty()); // true circularLinked.append('Tom'); circularLinked.append('Peter'); circularLinked.append('Paul'); circularLinked.print(); // "Tom,Peter,Paul" circularLinked.insert(0, 'Susan'); circularLinked.print(); // "Susan,Tom,Peter,Paul" circularLinked.insert(1, 'Jack'); circularLinked.print(); // "Susan,Jack,Tom,Peter,Paul" console.log(circularLinked.getHead()); // "Susan" console.log(circularLinked.isEmpty()); // false console.log(circularLinked.indexOf('Peter')); // 3 console.log(circularLinked.indexOf('Cris')); // -1 circularLinked.remove('Tom'); circularLinked.removeAt(2); circularLinked.print(); // "Susan,Jack,Paul" circularLinked.list(); // 具体控制台整个链表数据在 JavaScript 里是怎样的呢 ?
// 获取整个链表 this.list = function() { console.log('head: ', head); return head; };调用 circularLinked.list() 。
控制台输出如下:
你知道大家发现没有,为什么从 1 - 4 - 1 了,还有 next 节点,而且是还可以一直点 next ,重复的展开下去,这正是 循环 的原因。
链表总结
写链表代码是最考验逻辑思维能力的,要熟练链表,只有 多写多练,没有捷径。
因为,链表代码到处都是指针的操作、边界条件的处理,稍有不慎就容易产生 Bug。
链表代码写得好坏,可以看出一个人写代码是否够细心,考虑问题是否全面,思维是否缜密。
所以,这也是很多面试官喜欢让人手写链表代码的原因。
一定要自己写代码实现一下,才有效果。
6. 文章输出计划JavaScript 数据结构与算法之美 系列文章,暂时写了如下的 11 篇文章,后续还有想写的内容,再补充。
所写的内容只是数据结构与算法内容的冰山一角,如果你还想学更多的内容,推荐学习王争老师的 数据结构与算法之美。
从时间和空间复杂度、基础数据结构到排序算法,文章的内容有一定的关联性,所以阅读时推荐按顺序来阅读,效果更佳。
1. JavaScript 数据结构与算法之美 - 时间和空间复杂度
2. JavaScript 数据结构与算法之美 - 线性表(数组、队列、栈、链表)
3. JavaScript 数据结构与算法之美 - 实现一个前端路由,如何实现浏览器的前进与后退 ?
4. JavaScript 数据结构与算法之美 - 栈内存与堆内存 、浅拷贝与深拷贝
5. JavaScript 数据结构与算法之美 - 递归
6. JavaScript 数据结构与算法之美 - 非线性表(树、堆)
7. JavaScript 数据结构与算法之美 - 冒泡排序、选择排序、插入排序
8. JavaScript 数据结构与算法之美 - 归并排序、快速排序、希尔排序、堆排序
9. JavaScript 数据结构与算法之美 - 计数排序、桶排序、基数排序
10. JavaScript 数据结构与算法之美 - 十大经典排序算法汇总
11. JavaScript 数据结构与算法之美 - 强烈推荐 GitHub 上值得前端学习的数据结构与算法项目
如果有错误或者不严谨的地方,请务必给予指正,以免误人子弟,十分感谢。
7. 最后文章中的代码已经全部放在了我的 github 上,如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
参考文章: