文章目录
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。
- 停止条件为node==null
- 当cur无左树node=node->right
- node有左树,找到node左树的最右节点,记作right。
- 当right->right=null时,证明这是第一次走这个节点,让right->right=node,node=node->left
- 当right->right=node,证明是第二次走这个节点,还原树让right->right=null
- 最后让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);
}
}
}

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

被折叠的 条评论
为什么被折叠?



