二叉树的非递归遍历(树的深度优先遍历(根左右..)树的广度优先遍历(层次遍历))

转:树的层次遍历_听到微笑的博客-CSDN博客_树的层次遍历

二叉树的前序、中序、后序遍历我想大家应该都很熟悉了,那我们今天就来讲一下二叉树的层次遍历。

二叉树的前序、中序、后序遍历需要用到栈(递归的过程也就是一个栈),层次遍历需要借助队列这个数据结构。

层次遍历的思路


我们给出一个二叉树:

这棵二叉树的层次遍历次序为:A、B、C、D、F、G

以人的思维来看层次遍历貌似比前、中、后序遍历更加简单易懂,但是程序到底如何实现这样的效果呢?这里我们需要用到一个数据结构,那就是队列。

核心思想:每次出队一个元素,就将该元素的孩子节点加入队列中,直至队列中元素个数为0时,出队的顺序就是该二叉树的层次遍历结果。

可能这句话不太好理解,我们依旧是以画图的形式来表达:

1. 初始状态下,队列中只保留根节点的元素:

2. 当A出队时,将A的孩子节点加入队列中:

3.重复上面的动作,队首元素出队时,将孩子节点加入队尾.......

...........

相信看了上面的过程,大家对层次遍历的思路已经有了清晰的认识,那么我们就需要用代码来实现它了:

层次遍历的实现

public class MyTree {
    public static class TreeNode{
        public Object key;
        public TreeNode parent;
        public List<TreeNode> childrens = new ArrayList<>();
 
        public TreeNode(Object key) {
            this.key = key;
        }
    }
 
    private int size = 0;
 
    private TreeNode root;
 
    public MyTree(TreeNode root) {
        this.root = root;
        size++;
    }
 
 
    /**
     * 层次遍历
     *
     * @param treeNode
     * @return
     */
    public List<TreeNode> levelOrder(TreeNode treeNode) {
        /**
         * 层次遍历使用到了广度优先搜索,技巧:深度优先用递归,广度优先用队列。
         */
        Queue<TreeNode> queue = new LinkedList<>();
 
        List<TreeNode> list = new LinkedList<>();
        queue.add(treeNode);
        while (queue.size()>0){
            //出一个,进n个
            //出一个
            TreeNode node = queue.poll();
            list.add(node);
            //进n个
            List<TreeNode> childens = node.childrens;
            for (TreeNode childNode : childens) {
                queue.add(childNode);
            }
        }
        return list;
    }
 
}

树的深度优先遍历、广度优先遍历

树的深度优先遍历、广度优先遍历 - 知乎

对于树形结构主要有两种遍历方式:深度优先遍历和广度优先遍历。

我们使用下边的节点类来表示树形结构(多叉树)。

public class Node {
	private String value;
	private List<Node> children;

	public Node(String value, List<Node> children) {
		this.value = value;
		this.children = children;
	}

	public Node() {
	}

	public String value() {
		return this.value;
	}

	public void setValue(String value) {
		this.value = value;
	}

	public void addChild(Node node) {
		if (node == null) {
			return;
		}
		this.children.add(node);
	}

	public List<Node> getChildren() {
		return this.children;
	}
}

一个简单的树结构图

一、深度优先遍历

深度优先遍历指的是,从树的根节点开始,先遍历左子树,然后遍历右子树。

我们借助栈结构来实现深度优先遍历。上图的深度优先遍历结果为:ABDECFG

代码如下:

Stack<Node> stack = new Stack<Node>();
List<Node> result = new ArrayList<Node>();
stack.push(root);
while (!stack.isEmpty()) {
	Node top = stack.pop();
	result.add(top);
	List<Node> children = top.getChildren();
	if (children != null && children.size() > 0) {
		for (int i = children.size() - 1; i >= 0; i--) {
			stack.push(children.get(i));
		}
	}
}

二、广度优先遍历

从根节点开始,沿着树的宽度依次遍历树的每个节点。

我们借助队列结构来实现树的广度优先遍历。上图的遍历结果为:ABCDEFG

代码如下:

Queue<Node> queue = new LinkedBlockingQueue<Node>();
List<Node> result = new ArrayList<Node>();
queue.add(root);
while (!queue.isEmpty()) {
	Node first = queue.poll();
	result.add(first);
	List<Node> children = first.getChildren();
	if (children != null && children.size() > 0) {
		for (int i = 0; i < children.size(); i++) {
			queue.add(children.get(i));
		}
	}
}

 

二叉树的非递归前序遍历

转:https://blog.csdn.net/weixin_51173281/article/details/113701937

前序特点:根–左–右

思路:每次访问一个结点后,在向左子树遍历下去之前,利用这个栈记录该结点的右孩子(如果有的话)结点的地址,以便在左子树退回时可以直接从栈顶取得右子树的根结点,继续右子树的前序遍历。

访问:出栈输出;记录:入栈。

总结(说人话):从顶部出发,访问,先记录右孩子,再记录左孩子,循环直至栈空。

template<class T>
void BinaryTree<T>::PreOrder()
{
	LinkStack<BiTreeNode<T>*> S;				//队列S初始化
	if (root == NULL)
	{
		return;
	}
	S.Push(root);								//从顶部出发
	BiTreeNode<T>* q;
	while (!S.Is_Empty())
	{
		S.Pop(q);
		cout << q->data << " ";					//访问
		if (q->rightChild)						//先记录右孩子
		{
			S.Push(q->rightChild);
		}
		if (q->leftChild)						//再记录左孩子
		{
			S.Push(q->leftChild);
		}
	}
}
  • 先序的实际顺序:【根左】【右】
  • 中序的实际顺序:【左根】【右】
  • 后序的实际顺序:左右根

二叉树的非递归实现

树的深度优先遍历的非递归实现_一只想入研坑的程序猿的博客-CSDN博客_树的深度非递归

三种遍历的非递归实现思路
1.前序遍历的非递归实现

首先根结点入栈,然后执行循环条件为(top!=-1)的循环,依次出栈栈中元素,每出栈一个元素需要判断其右孩子和左孩子是否存在(先右后左,因为栈是“先进后出”,而前序遍历是根左右的顺序,先访问的是左子树,所以就要让左子树后入栈以便先访问),如果存在则将其入栈,否则一直出栈直到栈空为止,代码如下:

void preorderNonrecursion(BTNode *bt)
{
    if(bt)
    {
        BTNode* stack[maxsize],p;
        int top=-1;
        stack[++top]=bt;
        while(top!=-1)
        {
            p=stack[top--];
            visit(p);
            if(p->rchild)//先检查右子树的原因是由于栈的"后进先出"特点,因为先序遍历是左右,所以为了确保左子树优先访问则应该让它后入栈
            {
                stack[++top]=p->rchild;
            }
            if(p->lchild)
            {
                stack[++top]=p->lchild;
            }
        }
    }
}


2.后序遍历的非递归实现

后序是左右根的顺序,那么逆后序则是根右左的顺序,而前序又是根左右的顺序,那么我们可以得知,如果将前序遍历中对左右子树的检查顺序颠倒就可以得到根右左(逆后序)方式的遍历序列,看到逆我们则想到栈,如果可以以逆后序的遍历序列依次入栈在出栈则可以得到我们想要的后序遍历,代码如下:

void postorderNonrecursion(BTNode *bt)
{
    if(bt)
    {
        BTNode*stack1[maxsize];//栈1用来辅助遍历
        int top1=-1;
        BTNode*stack2[maxsize];//栈2用来辅助输出最终的后序遍历序列
        int top2=-1;
        BTNode *p=NULL;
        stack1[++top1]=bt;
        while(top1!=-1)
        {
            p=stack1[top--];
            stack2[++top2]=p;//原来的前序遍历这里是输出,现在则是将其入栈2
            if(p->lchild)//15和19行顺序交换
            {
                stack1[++top1]=p->lchild;
            }
            if(p->rchild)
            {
                stack1[++top1]=p->rchild;
            }
        }
        while(top2!=-1)//此时从栈顶依次输出的序列则为后序遍历序列
        {
            visit[stack[top2--]];
        }
    }
}


3.中序遍历的非递归实现

首先将根结点入栈,然后依次顺其左子树将其左孩子全部入栈,直到遇到某一节点无左孩子时则出栈并输出一个元素,然后判断其是否有右孩子,如果有右孩子则重复1
2.否则继续出栈一个元素,直到栈空并且刚出栈的这个元素无右孩子,代码如下:

void inorderNonrecursion(BTnode *bt)
{
    if(bt!=NULL)
    {
        BTbode *stack[maxsize];
        int top=-1;
        BTnode *p=bt;//若无此语句而直接用bt来遍历会导致最终bt指向空,但其实如果不用return返回bt的话也不会有大影响(实参指向不变)
        while(top!=-1||p)//top这个条件其实就是为了保证如果刚出栈的结点无右子树时好继续出栈元素准备的,p这个条件则是为了刚出栈的这个元素如果有右子树则对其右子树完全执行刚开始的最外层while循环而准备的
        {
            while(p)
            {
                stack[++top]=p;
                p=p->lchild;
            }
            if(top!=-1)
            {
                p=stack[top--];
                visit(p);
                p=p->rchild;//p如果为NULL则下一步执行继续出栈
                            //p如果不为空则下一步执行入栈根结点和所有左子树结点
            }
        }
    }
}
//该算法的执行流程是:
         //1.将根结点先入栈➡️相继入栈所有左孩子节点,直到某一棵树无左子树,则出栈并输出,然后检查其
         //右子树,如果存在右子树则重复1,否则继续出栈,直到栈空并且无右子树。



 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值