JavaSE_Java/C++模拟实现二叉树、C++/Java四种二叉树遍历题型(递归、非递归、Morris)遍历

本文详细介绍了使用Java和C++两种语言模拟构建二叉树的方法,包括前序、中序和后序遍历的递归、非递归及Morris遍历算法。同时,提供了层序遍历的实现,并展示了如何获取树的高度、节点个数、叶子节点个数等信息。此外,还探讨了完全二叉树的检测以及在不同语言中处理异常的方法。

1.Java模拟实现二叉树(前序构建二叉树)

Java构造二叉树的方式采用传递数组的形式。
利用2*根节点下标+1=左子节点下标,2 *根节点下标+2=右子节点下标的方式构建

import java.util.LinkedList;
import java.util.Queue;

class TreeNode{
    public int val;
    public TreeNode left;
    public TreeNode right;
    public TreeNode(int val){
        this.val=val;
        this.left=null;
        this.right=null;
    }
}

class MyTree{
    private final TreeNode root;

    private TreeNode buildTree(int[]buff,int pos){
        if(pos>=buff.length){
            return null;
        }
        TreeNode node=new TreeNode(buff[pos]);
        node.left=buildTree(buff,2*pos+1);
        node.right=buildTree(buff,2*pos+2);
        return node;
    }

    MyTree(int[]buff){//前序遍历创建树
        if(buff.length==0){
            throw new RuntimeException("初始化二叉树失败");
        }
        this.root=buildTree(buff,0);
    }

    //前序遍历
    private void _preDisplay(TreeNode node){
        if(node==null){
            return;
        }
        System.out.print(node.val+" ");
        _preDisplay(node.left);
        _preDisplay(node.right);
    }
    public void preDisplay(){
        if(root==null){
            throw new RuntimeException("根节点为空");
        }
        _preDisplay(this.root);
        System.out.println();
    }

    //获取树中的节点个数
    private int _getNodeSize(TreeNode node){
        if(node==null){
            return 0;
        }
        return _getNodeSize(node.left)+_getNodeSize(node.right)+1;
    }
    public int getNodeSize(){
        if(root==null){
            throw new RuntimeException("根节点为空");
        }
        return _getNodeSize(this.root);
    }

    //获取树的叶子节点个数
    private int _getLeafSize(TreeNode node){
        if(node==null){
            return 0;
        }
        if(node.left==null&&node.right==null){
            return 1;
        }
        return _getLeafSize(node.left)+_getLeafSize(node.right);
    }
    public int getLeafSize(){
        if(root==null){
            throw new RuntimeException("根节点位空");
        }
        return _getLeafSize(this.root);
    }

    //获取二叉树第k层节点个数
    private  int _getKSize(TreeNode node,int K){
        if(node==null||K<=0){
            return 0;
        }
        if(K==1){
            return 1;
        }
        return _getKSize(node.left,K-1)+_getKSize(node.right,K-1);
    }
    public int getKSize(int K){
        if(root==null){
            throw new RuntimeException("根节点位空");
        }
        return _getKSize(this.root,K);
    }

    //获取二叉树高度
    public int _getTreeHigh(TreeNode node){
        if(node==null){
            return 0;
        }
        int leftHigh=_getTreeHigh(node.left);
        int rightHigh=_getTreeHigh(node.right);
        return Math.max(leftHigh,rightHigh)+1;
    }
    public int getTreeHigh(){
        if(root==null){
            throw new RuntimeException("根节点位空");
        }
        return _getTreeHigh(this.root);
    }

    //检测值位val的节点是否存在
    private TreeNode _find(TreeNode node,int val){
        if(node==null){
            return null;
        }
        if(node.val==val){
            return node;
        }
        TreeNode find=_find(node.left,val);
        if(find!=null){
            return find;
        }
        find=_find(node.right,val);
        return find;
    }
    public TreeNode find(int val){
        if(root==null){
            throw new RuntimeException("根节点位空");
        }
        return _find(this.root,val);
    }

    //判断这棵树是否是完全二叉树
    /*
      完全二叉树使用队列,当弹出的队列元素为空时,整个队列都为空。
     */
    private boolean _checkQueue(Queue<TreeNode>queue){//检查队列的元素是否全部为null
        while(!queue.isEmpty()){
            TreeNode node=queue.poll();
            if(node!=null){
                return false;
            }
        }
        return true;
    }
    private boolean _isCompTree(TreeNode node){
        Queue<TreeNode> queue=new LinkedList<>();
        queue.offer(node);
        boolean ret=true;
        while(true){
            TreeNode nodeTmp=queue.poll();
            if(nodeTmp==null){
                ret=_checkQueue(queue);
                break;
            }
            queue.offer(nodeTmp.left);
            queue.offer(nodeTmp.right);
        }
        return ret;
    }
    public boolean isCompTree(){
        if(root==null){
            throw new RuntimeException("根节点位空");
        }
        return _isCompTree(this.root);
    }
}

public class TestTree {
    public static void main(String[] args) {
        MyTree tree=new MyTree(new int[]{1,2,3,4,5,6,7,8,9,10});
        tree.preDisplay();
        System.out.println(tree.getNodeSize());
        System.out.println(tree.getLeafSize());
        System.out.println(tree.getKSize(4));
        System.out.println(tree.getTreeHigh());
        System.out.println(tree.find(1111));
        System.out.println(tree.find(3));
        System.out.println(tree.isCompTree());
    }
}

2.C++模拟实现二叉树(层序构建二叉树)

C++创建二叉树时采用层序遍历的方法。使用队列辅助创建,同样需要传入数组.

这里在构造函数中抛出异常可能会导致内存泄漏。本应该用智能指针的,搞了半天没搞出来。
此外:这里添加了一个父亲节点和标记位来辅助删除多创建的节点。

#pragma once

#include<iostream>
#include<vector>
#include<queue>
#include<string>
#include<exception>
#include<memory>

struct TreeNode
{
	int val;
	TreeNode*left;
	TreeNode*right;
	TreeNode*parent;
	bool flag = false;
	TreeNode(int _val){
		val = _val;
		left = nullptr;
		right = nullptr;
		parent = nullptr;
	}
	TreeNode(){
		val = -1;
		left = nullptr;
		right = nullptr;
		parent = nullptr;
	}
};


class TreeError :public std::exception   //自己的异常类继承标准库中的异常类
{
public:                               //父类中为char*类型,把string转换为char*
	TreeError(std::string str) :std::exception(str.c_str()) {}
};

class MyTree{
private:
	TreeNode* root;
public:
	MyTree(std::vector<int>&buff){
		if (buff.empty()){
			throw new TreeError("数组大小有误");//可能由于抛异常导致内存泄漏
		}
		std::queue<TreeNode*>Queue;
		root = new TreeNode(buff[0]);
		Queue.push(root);
		for (int i = 0; i < buff.size(); i++){
			TreeNode*node = Queue.front(); Queue.pop();
			node->val = buff[i];
			node->left = new TreeNode(); node->left->parent = node; node->left->flag = false;//父节点的左边
			node->right = new TreeNode(); node->right->parent = node; node->right->flag = true;
			Queue.push(node->left); Queue.push(node->right);
		}
		while (!Queue.empty()){//删除多创建的节点
			TreeNode*node = Queue.front(); Queue.pop();
			if (node->flag == true){
				node->parent->right = nullptr;
			}
			else{
				node->parent->left = nullptr;
			}
			delete node;
			node = nullptr;
		}
	}
private:
	void _delNode(TreeNode*node){
		if (node == nullptr){
			return;
		}
		_delNode(node->left);
		_delNode(node->right);
		delete node;
	}
public:
	~MyTree(){
		_delNode(root);
	}
private:
	void _PreDisplay(TreeNode* node){
		if (node == nullptr){
			return;
		}
		std::cout << node->val << " " << std::endl;
		_PreDisplay(node->left);
		_PreDisplay(node->right);
	}
public:
	void PreDisplay(){
		if (root == nullptr){
			throw new TreeError("树节点为空");
		}
		_PreDisplay(root);
	}
	//获取二叉树第k层节点个数
private:
	int _getKSize(TreeNode*root, int K){
		if (root == nullptr || K <= 0){
			return 0;
		}
		if (K == 1){
			return 1;
		}
		return _getKSize(root->left, K - 1) + _getKSize(root->right, K - 1);
	}
public:
	int getKSize(int K){
		if (root == nullptr){
			throw new TreeError("树节点为空");
		}
		return _getKSize(root,K);
	}
};
#include"Tree.h"

using namespace std;

int main(){
	std::vector<int> vet = { 1, 2, 3 };
	MyTree tree(vet);
	tree.PreDisplay();
	cout << tree.getKSize(1) << endl;
	return 0;
}

其他的逻辑和上面的Java版本类似,不在赘述

3.Java/C++三种二叉树遍历方式

前序遍历

1)递归版本

Java:

//前序遍历
private void preDisplay(TreeNode node){
    if(node==null){
        return;
    }
    System.out.print(node.val+" ");
    preDisplay(node.left);
    preDisplay(node.right);
}

C++:

void PreDisplay(TreeNode* node){
	if (node == nullptr){
		return;
	}
	std::cout << node->val << " ";
	PreDisplay(node->left);
	PreDisplay(node->right);
}

2)非递归版本

Java:

private void preDisplayNotRecursion(TreeNode node){
    if(node==null){
        return;
    }
    Stack<TreeNode>stack=new Stack<>();
    while(node!=null||!stack.isEmpty()){
        while(node!=null){
            System.out.print(node.val+" ");
            stack.push(node);
            node=node.left;
        }
        node=stack.pop();
        node=node.right;
    }
}

C++:

void PreDisplayNotRecurve(TreeNode*node){//前序遍历非递归
	if (node == nullptr){
		return;
	}
	std::stack<TreeNode*>Stack;
	while (node!=nullptr||!Stack.empty()){
		while (node != nullptr){
			std::cout << node->val << " ";
			Stack.push(node);
			node = node->left;
		}
		node = Stack.top(); Stack.pop();
		node = node->right;
	}
}

3)Morris遍历

二叉树上的很多节点都有大量的空闲指针(如叶节点就有两个空闲指针),比如某些节点没有右孩子节点,那么这个节点的 right 指针就指向 null,我们将之称为空闲状态,而Morris遍历就是使用了这些空闲指针。

详情思路点击这里

大概思路为:
设当前节点为 node,初始 node = head。

  1. 停止条件为node==null
  2. 当cur无左树node=node->right
  3. node有左树,找到node左树的最右节点,记作right。
  4. 当right->right=null时,证明这是第一次走这个节点,让right->right=node,node=node->left
  5. 当right->right=node,证明是第二次走这个节点,还原树让right->right=null
  6. 最后让node向右子树移动

C++代码:

void PreMorrisDisplay(TreeNode*node){
	if (node == nullptr){
		return;
	}
	TreeNode*nodeRight = nullptr;
	while (node != nullptr){
		//如果左子树存在,找到左子树的最右节点
		nodeRight = node->left;
		if (nodeRight != nullptr){
			while (nodeRight->right != nullptr&&nodeRight->right != node){
				nodeRight = nodeRight->right;
			}
			if (nodeRight->right == nullptr){
				//第一次遍历到这个节点
				std::cout << node->val << " ";
				nodeRight->right = node;
				node = node->left;
				continue;
			}
			else if (nodeRight->right == node){
				//node遍历过两次,还原树
				nodeRight->right = nullptr;
			}
		}
		else{
			//左子树不存在
			std::cout << node->val << " ";
		}
		node = node->right;
	}
}

Java代码:

private void preMorrisDisplay(TreeNode node){
    if(node==null){
        return;
    }
    TreeNode nodeRight=null;
    while(node!=null){
        nodeRight=node.left;
        if(nodeRight!=null){
            while(nodeRight.right!=null&&nodeRight.right!=node){
                nodeRight=nodeRight.right;
            }
            if(nodeRight.right==null){
                System.out.print(node.val+" ");
                nodeRight.right=node;
                node=node.left;
                continue;
            }
            else {
                nodeRight.right=null;
            }
        }
        else{
            System.out.print(node.val+" ");
        }
        node=node.right;
    }
}

中序遍历

1)递归版本

void InorDisplay(TreeNode*node){
	if (node == nullptr){
		return;
	}
	InorDisplay(node->left);
	std::cout << node->val << " ";
	InorDisplay(node->right);
}
private void _InorDisplay(TreeNode node){
      if(node==null){
          return;
      }
      _InorDisplay(node.left);
      System.out.print(node.val+" ");
      _InorDisplay(node.right);
}

2)非递归版本

void _InorDisplayNotRecurve(TreeNode*node){
	if (node == nullptr){
		return;
	}
	std::stack<TreeNode*>Stack;
	while (node!=nullptr||!Stack.empty()){
		while (node != nullptr){
			Stack.push(node);
			node = node->left;
		}
		node = Stack.top(); Stack.pop();
		std::cout << node->val << " ";
		node = node->right;
	}
}

Java:

private void _InorDisplayNotRecurve(TreeNode node){
    if(node==null){
        return;
    }
    Stack<TreeNode>stack=new Stack<>();
    while(node!=null||!stack.isEmpty()){
        while(node!=null){
            stack.push(node);
            node=node.left;
        }
        node=stack.pop();
        System.out.print(node.val+" ");
        node=node.right;
    }
}

3)Morris遍历

与前序遍历相比,中序Morris遍历只需要更换打印的位置即可,在第二次经过这个节点的时候进行打印

C++:

void _InorMorrisDisplay(TreeNode*node){
	if (node == nullptr){
		return;
	}
	TreeNode*nodeRight = nullptr;
	while (node != nullptr){
		//如果左子树存在,找到左子树的最右节点
		nodeRight = node->left;
		if (nodeRight != nullptr){
			while (nodeRight->right != nullptr&&nodeRight->right != node){
				nodeRight = nodeRight->right;
			}
			if (nodeRight->right == nullptr){
				//第一次遍历到这个节点
				nodeRight->right = node;
				node = node->left;
				continue;
			}
			else if (nodeRight->right == node){
				//node遍历过两次,还原树
				nodeRight->right = nullptr;
				std::cout << node->val << " ";
			}
		}
		else{
			//左子树不存在
			std::cout << node->val << " ";
		}
		node = node->right;
	}
}

Java:

private void _InorMorrisDisplay(TreeNode node){
    if(node==null){
        return;
    }
    TreeNode nodeRight=null;
    while(node!=null){
        nodeRight=node.left;
        if(nodeRight!=null){
            while(nodeRight.right!=null&&nodeRight.right!=node){
                nodeRight=nodeRight.right;
            }
            if(nodeRight.right==null){
                nodeRight.right=node;
                node=node.left;
                continue;
            }
            else {
                System.out.print(node.val+" ");
                nodeRight.right=null;
            }
        }
        else{
            System.out.print(node.val+" ");
        }
        node=node.right;
    }
}

后序遍历

1)递归版本

C++:

void _PostorderDisPlay(TreeNode*node){
	if (node == nullptr){
		return;
	}
	_PostorderDisPlay(node->left);
	_PostorderDisPlay(node->right);
	std::cout << node->val << " ";
}

Java:

private void _postorderDisPlay(TreeNode node){
    if(node==null){
        return;
    }
    _postorderDisPlay(node.left);
    _postorderDisPlay(node.right);
    System.out.print(node.val+" ");
}

2)非递归版本

注意:从栈中弹出的节点,我们只能确定其左子树肯定访问完了,但是无法确定右子树是否访问过。因此,我们在后序遍历中,引入了一个prev来记录历史访问记录。

C++:

void  _PostorderDisPlayNotRecurve(TreeNode*node){
	if (node == nullptr){
		return;
	}
	std::stack<TreeNode*>Stack;
	TreeNode*prev = nullptr;
	while (node != nullptr || !Stack.empty()){
		while (node != nullptr){
			Stack.push(node);
			node = node->left;
		}
		node = Stack.top(); Stack.pop();
		if (node->right == nullptr || node->right == prev){
			std::cout << node->val << " ";
			prev = node;
			node = nullptr;//避免下次循环重新将node入栈,导致重复遍历
		}
		else{
			Stack.push(node);
			node = node->right;
		}
	}
}

Java:

private void _postorderDisPlayNotRecurve(TreeNode node){
    if(node==null){
        return;
    }
    Stack<TreeNode>stack=new Stack<>();
    TreeNode prev=null;
    while(node!=null||!stack.isEmpty()){
        while(node!=null){
            stack.push(node);
            node=node.left;
        }
        node=stack.pop();
        if(node.right==null||node.right==prev){
            System.out.print(node.val+" ");
            prev=node;
            node=null;
        }
        else{
            stack.push(node);
            node=node.right;
        }
    }
}

3)Morris遍历

首先前序遍历,我们获得的节点顺序为根->左->右节点

后序遍历我们需要获得节点的顺序为左->右->根

Morris算法的大致遍历流程为:
先访问根节点,然后访问左子节点,接下来根据建立好的连接再次回到根节点,然后访问右子节点。

所以我们获取到根->右->左,再将顺序逆置即可。而最接近的是Morris的前序遍历根->左->右。我们只需要将Morris前序遍历改造一下就可以达到根->左->右。

以前Morrris前序构造时node节点开始向左走,走到叶子节点后,根据这个节点的右指针回到上一个节点。

这时我们要node开始向右走,走到叶子节点后,根据这个节点的左指针回到上一个节点。类似与先序遍历

这样遍历就可以得到根->右->左顺序,再将其逆置即可。

参考题解

C++:

void _PostorderMorrisDisPlay(TreeNode*node,std::vector<int>&ret){
	//ret存放后序遍历结果
	if (node == nullptr){
		return;
	}
	TreeNode*leftNode = nullptr;
	while (node != nullptr){
		leftNode = node->right;
		if (leftNode != nullptr){
			while (leftNode->left != nullptr&&leftNode->left != node){
				leftNode = leftNode->left;
			}
			if (leftNode->left == nullptr){
				ret.emplace_back(node->val);
				leftNode->left = node;
				node=node->right;
				continue;
			}
			else{
				leftNode->left = nullptr;//还原
			}
		}
		else{
			ret.emplace_back(node->val);
		}
		node = node->left;
	}
	std::reverse(ret.begin(), ret.end());
}

Java:

private void _postorderMorrisDisPlay(TreeNode node,ArrayList<Integer>array){
    if(node==null){
        return;
    }
    //array存放后序遍历结果
    TreeNode leftNode=null;
    while(node!=null){
        leftNode=node.right;
        if(leftNode!=null){
            while(leftNode.left!=null&&leftNode.left!=node){
                leftNode=leftNode.left;
            }
            if(leftNode.left==null){
                array.add(node.val);
                leftNode.left=node;
                node=node.right;
                continue;
            }
            else{
                leftNode.left=null;
            }
        }
        else{
            array.add(node.val);
        }
        node=node.left;
    }
    Collections.reverse(array);
}

层序遍历

使用队列保存需要遍历的节点
C++:

void _SequenceDisplay(TreeNode*node){
	if (node == nullptr){
		return;
	}
	std::queue<TreeNode*>q;
	q.push(node);
	while (!q.empty()){
		TreeNode*nodeTmp = q.front(); q.pop();
		std::cout << nodeTmp->val << " ";
		if (nodeTmp->left != nullptr){
			q.push(nodeTmp->left);
		}
		if (nodeTmp->right != nullptr){
			q.push(nodeTmp->right);
		}
	}
}

Java:

private void _SequenceDisplay(TreeNode node){
    if(node==null){
        return;
    }
    Queue<TreeNode>queue=new LinkedList<>();
    queue.offer(node);
    while(!queue.isEmpty()){
        TreeNode nodeTmp=queue.poll();
        System.out.print(nodeTmp.val+" ");
        if(nodeTmp.left!=null){
            queue.offer(nodeTmp.left);
        }
        if(nodeTmp.right!=null){
            queue.offer(nodeTmp.right);
        }
    }
}

4.代码位置

Java测试代码链接
C++测试代码链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BaiRong-NUC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值