queue队列
队列的特点是先进先出。
用一个排队系统来举例:假设你进去一家餐厅,先叫号的先去吃饭。
如果用数组的方法来表示,那就是Array.prototype.push和Array.prototype.shift
Stack(栈)
栈的特点是先进后出。
打个比喻:电梯就类似于Stack的结构,先进入电梯的后出电梯
对应数组的方法来表示,那就是Array.prototype.push和Array.prototype.pop
对于栈来说,一个典型的栗子就是递归函数
function digui() {
let a = 0
return a + digui2()
}
function digui2() {
let b = 1
return b + digui3()
}
function digui3() {
let c = 2
return c
}
digui()
整个压栈弹栈的过程如下:
一张图表示两者的关系
链表
let arr=[1,2,3]
arr.__proto__ ===Array.prototype
Array.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
从这个角度看,原型链也是一种链表
链表的图文形式
创建一个链表
以上面的图文表示,假设需要创建一个链表,我需要创建第一个节点。
//创建一个节点
let createNode=(value)=>{
return {
data:value,
next:null}
}
let list = createNode(10) //这是第一个节点,同时让把地址指向list,此时链表就创建完成了
增加节点
如果要增加节点,我们只需要将list.next设置为新的node即可。为了区分,我们将增加的节点value设置成20,可以写以下的函数:
let appendNode =(value,whichList)=>{
const node =createNode(value)
whichList.next = node //whichList代表需要新增的list
return node //返回增加的节点
}
appendNode(20,list) //增加了第二个节点
此时内存图为:
上面这段代码有误,因为增加第三个节点时,会覆盖
list.next也就是第二个节点,我们应该设置当next:null时,将当前的节点下的next设置成第三个节点。
如何找到next:null的节点呢?因为不知道循环几次所以for循环不行,可以使用while循环来查找。
- 我们可以设置变量x = list,此时x保存了list的地址。
- 如果x.next不是null,就让x.next的地址赋给x,这样就可以顺着地址找到
next:null的那个节点
//增加节点
let appendNode = (value, whatList) => {
let node = createNode(value)
let x = whatList
while (x.next != null) {
x = x.next
}
x.next = node
return node //返回增加的节点
}
let node2=appendNode(20,list) //返回节点2
let node3=appendNode(30,list) //返回节点3
let node4=appendNode(40,list) //返回节点4
附加说明:return node可以让我们知道哪个节点,打印一下每个节点看看,它可以帮助我们删除想要删除的节点
删除节点
如何删除节点呢?我们只需要将要删除节点的上一个节点的next设置为要删除的节点的下一个节点就可以了,假设我现在要删除node2节点。
先打印一下node2节点看看。
我们需要将next的地址指向第一个节点的next,就可以实现将第二个节点删除。
let removeNode =(whichList,rmNode)=>{
//rmNode表示要删除的节点
//此时whichList可以代表第一个节点,如果相等就表示要删除第一个节点
if(whichList===rmNode){
whichList =rmNode.next}
//如果要删除第二个节点,就让第一个节点的next指向第二个节点的next
else if(whichList.next === rmNode){
whichList.next =rmNode.next
//如果要删除第三个节点,就让第二个节点的next指向第三个节点的next
}else if(whichList.next.next === rmNode){
whichList.next.next =rmNode.next
//后面同理
}
上述代码展示基本的原理,我们可以通过找出与要要删除的节点相同的节点,来实现。
跟新增节点一样,我们先设置变量x,如果x不是要删除的节点,那么就让x=x.next来查找出要删除的节点的地址。
//修改后的代码
let removeNode =(whichList,rmNode)=>{
let x =whichList
//此时遇到一个问题,我不知道上一个节点是哪个,那么可以设置变量p,来保存上一个地址
let p =null
while(x !== rmNode){
p = x //保存了要删除的节点的上一个地址
x = x.next //当循环结束时会找到需要删除的节点的地址
}
p.next = x.next
}
上面的代码有bug,我们很容易就可以发现,当x=rmNode也就是要删除第一个节点的时候,实际上应该是p=x.next,所以我们应该做修改
//最终实现删除节点的代码
let removeNode = (whichList, rmNode) => {
let x = whichList
//此时遇到一个问题,我不知道上一个节点是哪个,那么可以设置变量p,来保存上一个地址
let p = rmNode
while (x !== rmNode && x !== null) {
p = x //通过循环不断保存要删除的节点的上一个地址
x = x.next //当循环结束时x会保存需要删除的节点的地址
}
console.log(p)
console.log(x)
if (whichList === null) {
console.log(`你传进来的${whichList}是空,叫我删个p啊?`)
return false
} else if (whichList === rmNode) { //说明要修改的是第一个
whichList = whichList.next //注意:如果对传进来的复杂类型整体修改,则对原内存无效
console.log(`修改完毕`); //不信看log打印后的内容
console.log(whichList) //看我看我,我保存的已经不是传进来的list的地址啦
return whichList //所以我们必须return出去,然后外部接收才行噢
} else {
p.next = x.next //此时修改掉地址指向
}
}
查节点的每个data值
我们知道链表的最后一个next指向的是null,所以我们可以通过这个条件来遍历链表,但是如果条件为next=null,我们就无法将最后一个节点的信息打印出来,所以我们可以修改这个条件。以data值为栗子
const travelList = (whichList, fn) => {
let x = whichList
while (x !== null) {
fn(x)
x = x.next
}
}
travelList(list, function (x) {
console.log(x.data)
})
链表核心总结:
1、当我们不知道某一个地址时,可以使用【循环方法+设置第三方变量+x = x.next】的方法来找到符合条件的地址。
2、经常画内存图或者链表图有助于理清思路。
树Tree
树的结构跟链表有点类似,链表是一环接着一环。而树则是长出多个节点。
创建树
const createTree(value)=>{
const tree={
data:value,
parent:null,
children:null
}
return tree
}
增加节点
const addChild(node,value){
const newNode={
data:value,
parent:node,
childen:null
}
node.children=node.children || []//看注释1
node.children.push(newNode)
return newNode
}
注释1:这句话的意思是如果node.children是null或者其他falsy值,那么就让它变成一个数组。使用数组包含多个节点。
回顾一下falsy值:分别是null、undefined、0、-0、""(空)、NaN、false
遍历节点
const travel=(tree,fn)=>{
fn(tree)
if(tree.children){
for(let i=0;i<tree.children.length;i++){
fn(tree.children[i],fn)
}
}else{
return null}
}
删除节点
const removeNode(rmNode)=>{
let siblings=rmNode.parent.children
let index=null
for(let i=0;i<siblings.length;i++){
if(siblings[i]===rmNode){
index=i
break
}
}
siblings.splice(index,1)
}
参考资料:github源码下载