数据结构(一)

295 阅读5分钟

queue队列

队列的特点是先进先出。

用一个排队系统来举例:假设你进去一家餐厅,先叫号的先去吃饭。

如果用数组的方法来表示,那就是Array.prototype.pushArray.prototype.shift

Stack(栈)

栈的特点是先进后出。

打个比喻:电梯就类似于Stack的结构,先进入电梯的后出电梯

对应数组的方法来表示,那就是Array.prototype.pushArray.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源码下载