ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【JS】二叉树先序、中序、后序(递归和非递归)

2022-06-11 13:34:13  阅读:151  来源: 互联网

标签:node 结点 right cur 递归 中序 二叉树 root left


1二叉树的结构

定义一个简单的结点:

Node{
    value:number,
    left:Node,
    right:Node
}

2递归遍历

参考学习:https://blog.csdn.net/xin9910/article/details/73476773

2.1伪代码

先序、中序、后序中的“先、中、后”描述的是“当前、左、右”三个结点中,当前结点(根结点)的遍历先后情况。

先序,说明先遍历根结点。

中序,说明先遍历左,再遍历根,最后右。根结点的遍历优先级位于三者的中间。

后序,则是先左,再右,最后根结点。

根结点的优先级虽然分了上面三种,但是左和右的优先级并没有分两种,而是左始终优先于右。

由于对称性,只要掌握了这三种遍历,右优先于左的话,也是很容易调整代码进行修改的。


先序:先遍历根,再遍历左,再遍历右。

preOrder(node:Node):
	if(node){
		visit(node);
		preOrder(node.left)
		preOrder(node.right)
	}

中序:先遍历左,再遍历根,再遍历右。

midOrder(node:Node):
	if(node){
		midOrder(node.left)
		visit(node);
		midOrder(node.right)
	}

后序:先遍历左,再遍历右,最后遍历根。

postOrder(node:Node):
	if(node){
		postOrder(node.left)
		postOrder(node.right)
		visit(node);
	}

2.2JS代码测试

var node = {
    value:0,
    left:{
        value:1,
        left:{
            value:3,
        },
        right:{
            value:4
        }
    },
    right:{
        value:2,
        left:{
            value:5,
        },
        right:{
            value:6
        }
    }
}


function preOrder(node){
    if(node){
        console.log(node.value);
        preOrder(node.left)
        preOrder(node.right)
    }
}

preOrder(node)

console.log("-------");

function midOrder(node){
    if(node){
        midOrder(node.left)
        console.log(node.value);
        midOrder(node.right)
    }
}

midOrder(node)

console.log("-------");


function postOrder(node){
    if(node){
        postOrder(node.left)
        postOrder(node.right)
        console.log(node.value);
    }
}

postOrder(node)

console.log("-------");

3非递归遍历

参考学习:https://blog.csdn.net/weixin_42123213/article/details/112688508

3.1伪代码

由于树状结构,其遍历如果不递归,就需要用线性的容器将非线性的树转化为线性的,所以就可以用到数组、栈、队列。即使没有用到,也是需要用到线性化的方式。

假设使用了这样的容器,对于每个结点,放到容器时不代表立即访问,而是按照从容器中取出的顺序进行遍历。

3.1.1先序

先序是先根,再左,最后右。

分析根结点,如果使用队列,则根结点为了先从队列中出来,则需要第一个进入队列,然后左结点再进入,此时右结点如果立即进入了,那么就会排在左结点的子结点的前面,所以右结点不能进入。
右结点不进入的话,后面只能在根结点出来时,才能将右结点加入队列,因此当根结点出来时,左结点和它的孩子们必须全部已经遍历完毕了(a)。
左结点的孩子们也是一种根结点,其右孩子也是不能加入队列,所以只能先搞一个while,把根结点的左孩子的左孩子的...依次都加入到队列中。
但是这些左孩子们还会有自己的右孩子,由于最开始的根结点不能出列,所以无法回头将右孩子加入到队列中,所以
(a)就无法实现了,所以用队列恐不能实现无迭代的先序遍历。

(非严谨的个人推理,能说服自己了。)


然后换栈来分析一下。

首先,对于原始的根结点,一种是自己进栈前,右孩子先进栈,自己再进栈,自己出栈并访问,然后再左孩子进栈。
左孩子作为新的根结点进栈前,...(圆回来了)
但是问题在于,原始根结点的右孩子早早进栈了,那么它的右孩子将无法按上面的判断逻辑进栈,要么再去搞一些复杂的逻辑判断,要么就永远无法进栈。


另一种可以是自己进栈、出栈并访问,右孩子进栈,左孩子进栈....(发现左孩子的操作顺序和根结点的一样了,说明可以代码复用,找到规律了。同时,右孩子出栈时,也能接的上。)

push(root)
while(stack.size()>0):
	cur = pop()
	visit(cur)
	if cur.right
		push(cur.right)
	if cur.left
		push(cur.left)
	

初步结论:

  • 根据自己不太严谨的推理,无递归的先序,可以使用栈,无法纯使用队列。

3.1.2后序

前序是:根-左-右,后序是左-右-根。

首先由于对称性,可以没有难度地调整前序为:根-右-左,这时就发现它和后序的顺序是完全颠倒的,所以就可以按照前序的对称模式(右优先于左)访问,然后将访问结果颠倒一下。


根-右-左的“前序”:

push(root)
while(stack.size()>0):
	cur = pop()
	visit(cur)
	if cur.left
		push(cur.left)
	if cur.right
		push(cur.right)
	

颠倒成后序:可以在visit(cur)时将cur放到栈中,最后出栈顺序就是真正的访问顺序,也可以直接用arr.unshift从数组起始位置插入:

push(root)
var visitResult = []//表示后序访问的顺序
while(stack.size()>0):
	cur = pop()
	visitResult.unshift(cur)
	if cur.left
		push(cur.left)
	if cur.right
		push(cur.right)
	

3.1.3中序

中序是先左,再根,最后右。

首先分析一下,使用队列的可能性。

对于根结点,必须先把左孩子的支线全部走完,才遍历根,所以根早早地入队,很可能不行。


分析使用栈的情况。

一种想法是根结点入栈,左孩子不断入栈,直到左孩子为null,此时已经到叶子了,必须弹出一个访问。

访问完,也就访问完了左和根,就需要访问它的右孩子了,所以右孩子入栈。

然后需要把右孩子的左孩子支线都入栈,也就回到了最初的逻辑结构。

伪代码(错误的思考结果):

push(root)
while(stack.size()>0):
	while(root.left){
		push(root.left);
		root=root.left
	}
	root = pop()
	visit(root)
	
	if(root.right)
		push(root.right)
		root=cur.right

但是随后发现,假如是一颗只有左子树的简洁的树,那么就会陷入死循环,主要是如果右子树为空,就无法更新root了。


换一种思路,
首先如果一开始就push根结点,那么进入while循环后,还是需要继续push,这样就很不整洁,所以可以将第一个push和while里面的push合并。

stack=[]
while(root || stack.length>0){
	while(root){
		push(root);
		root=root.left
	}
	//这样就会把所有的左结点全部入栈。
	//最终直到叶子的位置上,就停止了while循环,此时需要出栈来访问。
	root = pop();
	visit(root)
	
	//右结点还没有访问呢,此时是否需要判断if(root.right)
	//如果判断并且右结点没有,那么就无法更新root,所以应该无条件更新root为右结点
	root = root.right;
	//即使root.right为null,到了下一层循环,就不会添加左结点,而是继续pop上一层了。
}
  1. 另一种是右孩子入栈,根结点入栈,左孩子入栈。

3.2 JS代码测试

var node = {
    value:0,
    left:{
        value:1,
        left:{
            value:3,
        },
        right:{
            value:4
        }
    },
    right:{
        value:2,
        left:{
            value:5,
        },
        right:{
            value:6
        }
    }
}

//先序
function pre_tranverse(root){
    var stack = [root];
    var visitResult = [];//从左到右为遍历顺序
    while(stack.length > 0){
        var cur = stack.pop();
        visitResult.push(cur.value);

        if(cur.right){
            stack.push(cur.right);
        }
        if(cur.left){
            stack.push(cur.left);
        }
    }


    return visitResult;
}

//后序
function post_tranverse(root){
    var stack = [root];
    var visitResult = [];//从左到右为遍历顺序
    while(stack.length > 0){
        var cur = stack.pop();
        visitResult.unshift(cur.value);

        if(cur.left){
            stack.push(cur.left);
        }

        if(cur.right){
            stack.push(cur.right);
        }
      
    }


    return visitResult;
}

console.log(pre_tranverse(node));
console.log(post_tranverse(node));


function mid_tranverse(root){
   var stack =[];
   var visitResult = [];//从左到右为遍历顺序
   while(root || stack.length > 0){
        while(root){
            stack.push(root)
            root = root.left
        }

        root = stack.pop();
        visitResult.push(root.value)

        root = root.right
   }
   return visitResult;
}
console.log(mid_tranverse(node));


先、后、中的输出结果:

(7) [0, 1, 3, 4, 2, 5, 6]
(7) [3, 4, 1, 5, 6, 2, 0]
(7) [3, 1, 4, 0, 5, 2, 6]

标签:node,结点,right,cur,递归,中序,二叉树,root,left
来源: https://www.cnblogs.com/redcode/p/16357297.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有