-
Notifications
You must be signed in to change notification settings - Fork 725
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JavaScript 数据结构与算法之美 - 线性表(数组、栈、队列、链表) #34
Comments
老哥写的非常好,有个小错误要给老哥指出来,再单链表的insert方法中,条件判断不太正确应该用或语句,position<0||position>length,希望老哥改过来 |
@LeishenKOBE 老哥看得非常细心,谢谢提醒,的确是错了,已经修改正确。 |
嗨,双向和循环链表里越界的判断应该是用 || 吧? |
老哥看得非常细心,谢谢提醒,双向链表的是错了,已经修改过来了;循环链表里没有错哦。 |
楼主,循环列表这里 insert 方法在执行了一次 position === 0 的插入后,插入了 node 这时候尾结点指向了链表的第二个元素了吧 |
双向链表的 |
老哥,循环链表里面的insert方法中,当positon===0处似乎有错误,当postion===0时即向链表头部插入节点,链表的头部改变了则链表尾部的next也应该改变指向 |
循环链表的 if (position === 0) {
while (current.next !== head) {
current = current.next;
}
current.next = node;
node.next = head;
head = node;
} |
我觉得也是,对应的removeAt也是吧,如果删了最前面的节点,head变了,尾部为节点也要改它的next,指向这个新的hea的节点吧。 |
循环列表里为什么有两个 |
前言
栈、队列、链表、堆 是数据结构与算法中的基础知识,是程序员的地基。
笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 JavaScript ,旨在入门数据结构与算法和方便以后复习。
1. 线性表与非线性表
线性表(Linear List):就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。数组、链表、队列、栈 等就是线性表结构。
非线性表:数据之间并不是简单的前后关系。二叉树、堆、图 就是非线性表。
本文主要讲线性表,非线性表会在后面章节讲。
2. 数组
定义
特点
数组是用一组连续的内存空间来存储的。
所以数组支持 随机访问,根据下标随机访问的时间复杂度为 O(1)。
低效的插入和删除。
数组为了保持内存数据的连续性,会导致插入、删除这两个操作比较低效,因为底层通常是要进行大量的数据搬移来保持数据的连续性。
插入与删除的时间复杂度如下:
插入:从最好 O(1) ,最坏 O(n) ,平均 O(n)
删除:从最好 O(1) ,最坏 O(n) ,平均 O(n)
注意
但是因为 JavaScript 是弱类型的语言,弱类型则允许隐式类型转换。
隐式:是指源码中没有明显的类型转换代码。也就是说,一个变量,可以赋值字符串,也可以赋值数值。
你还可以直接让字符串类型的变量和数值类型的变量相加,虽然得出的最终结果未必是你想象的那样,但一定不会报错。
数组的每一项可以是不同的类型,比如:
定义的数组的大小是可变的,不像强类型语言,定义某个数组变量的时候就要定义该变量的大小。
实现
JavaScript 原生支持数组,而且提供了很多操作方法,这里不展开讲。
3. 栈
定义
栈
结构。栈顶
,另一端就叫栈底
。操作受限
的线性表,只允许在一端插入和删除数据。空栈
。栈也被用在编程语言的编译器和内存中保存变量、方法调用等,比如函数的调用栈。
实现
栈的方法:
测试:
栈的应用实例:JavaScript 数据结构与算法之美 - 实现一个前端路由,如何实现浏览器的前进与后退 ?
4. 队列
普通队列
定义
实现
队列里面有一些声明的辅助方法:
代码:
测试:
优先队列
定义
优先队列中元素的添加和移除是依赖
优先级
的。应用
优先队列分为两类
最小优先队列是把优先级的值最小的元素被放置到队列的最前面(代表最高的优先级)。
比如:有四个元素:"John", "Jack", "Camila", "Tom",他们的优先级值分别为 4,3,2,1。
那么最小优先队列排序应该为:"Tom","Camila","Jack","John"。
最大优先队列正好相反,把优先级值最大的元素放置在队列的最前面。
以上面的为例,最大优先队列排序应该为:"John", "Jack", "Camila", "Tom"。
实现
实现一个优先队列,有两种选项:
这里最小优先队列和最大优先队列我都采用第一种方式实现,大家可以尝试一下第二种。
下面只重写 enqueue() 方法和 print() 方法,其他方法和上面的普通队列完全相同。
实现最小优先队列
实现最小优先队列 enqueue() 方法和 print() 方法:
最小优先队列测试:
实现最大优先队列
最大优先队列测试:
循环队列
定义
循环队列,顾名思义,它长得像一个环。把它想像成一个圆的钟就对了。
关键是:确定好队空和队满的判定条件。
循环队列的一个例子就是击鼓传花游戏(Hot Potato)。在这个游戏中,孩子们围城一个圆圈,击鼓的时候把花尽快的传递给旁边的人。某一时刻击鼓停止,这时花在谁的手里,谁就退出圆圈直到游戏结束。重复这个过程,直到只剩一个孩子(胜者)。
下面我们在普通队列的基础上,实现一个模拟的击鼓传花游戏,下面只写击鼓传花的代码片段:
执行结果为:
队列小结
一些具有某些额外特性的队列,比如:循环队列、阻塞队列、并发队列。它们在很多偏底层系统、框架、中间件的开发中,起着关键性的作用。
以上队列的代码要感谢 leocoder351。
5. 链表
定义
简单的链接结构图:
其中,data 中保存着数据,next 保存着下一个链表的引用。
上图中,我们说 data2 跟在 data1 后面,而不是说 data2 是链表中的第二个元素。值得注意的是,我们将链表的尾元素指向了 null 节点,表示链接结束的位置。
特点
链表是通过指针将零散的内存块串连起来的。
所以链表不支持 随机访问,如果要找特定的项,只能从头开始遍历,直到找到某个项。
所以访问的时间复杂度为 O(n)。
高效的插入和删除。
链表中插入或者删除一个数据,我们并不需要为了保持内存的连续性而搬移结点,因为链表的存储空间本身就不是连续的,只需要考虑相邻结点的指针改变。
所以,在链表中插入和删除一个数据是非常快速的,时间复杂度为 O(1)。
三种最常见的链表结构,它们分别是:
单链表
定义
由于链表的起始点的确定比较麻烦,因此很多链表的实现都会在链表的最前面添加一个特殊的节点,称为 头节点,表示链表的头部。
经过改造,链表就成了如下的样子:
针对链表的插入和删除操作,我们只需要考虑相邻结点的指针改变,所以插入与删除的时间复杂度为 O(1)。
在 d2 节点后面插入 d4 节点:
删除 d4 节点:
实现
单向链表的八种常用操作:
具体代码:
测试:
整个链表数据在 JavaScript 里是怎样的呢 ?
为了看这个数据,特意写了个 list 函数:
重点上上面的最后一行代码: singlyLinked.list() ,打印的数据如下:
所以,在 JavaScript 中,单链表的真实数据有点类似于对象,实际上是 Node 类生成的实例。
双向链表
单向链表只有一个方向,结点只有一个后继指针 next 指向后面的结点。
而双向链表,它支持两个方向,每个结点不止有一个后继指针 next 指向后面的结点,还有一个前驱指针 prev 指向前面的结点。
单向链表与又向链表比较
所以,如果存储同样多的数据,双向链表要比单链表占用更多的内存空间。
虽然两个指针比较浪费存储空间,但可以支持双向遍历,这样也带来了双向链表操作的灵活性。
我们可以访问一个特定节点的下一个或前一个元素。
实现
具体代码:
测试:
整个链表数据在 JavaScript 里是怎样的呢 ?
调用 doublyLinked.list(); .
控制台输出如下:
链表代码实现的关键是弄清楚:前节点与后节点与边界。
循环链表
循环链表是一种特殊的单链表。
循环链表和单链表相似,节点类型都是一样。
唯一的区别是,在创建循环链表的时候,让其
头节点的 next 属性指向它本身
。即:
这种行为会导致链表中每个节点的 next 属性都指向链表的头节点,换句话说,也就是链表的尾节点指向了头节点,形成了一个循环链表。如下图所示:
循环链表:在单链表的基础上,将尾节点的指针指向头结点,就构成了一个循环链表。环形链表从任意一个节点开始,都可以遍历整个链表。
代码:
测试:
整个链表数据在 JavaScript 里是怎样的呢 ?
调用 circularLinked.list() 。
控制台输出如下:
你知道大家发现没有,为什么从 1 - 4 - 1 了,还有 next 节点,而且是还可以一直点 next ,重复的展开下去,这正是 循环 的原因。
链表总结
6. 文章输出计划
JavaScript 数据结构与算法之美 的系列文章,坚持 3 - 7 天左右更新一篇,暂定计划如下表。
7. 最后
文章中的代码已经全部放在了我的 github 上,如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
关注我的公众号,第一时间接收最新的精彩博文。
文章可以转载,但须注明作者及出处,需要转载到公众号的,喊我加下白名单就行了。
参考文章:
数组:为什么很多编程语言中数组都从 0 开始编号?
JS中的算法与数据结构——链表(Linked-list)
JavaScript数据结构 03 - 队列
链表(上):如何实现 LRU 缓存淘汰算法?
JavaScript数据结构——队列
The text was updated successfully, but these errors were encountered: