文章目录
1.基本介绍
下面介绍的基于二叉树的基本查询的总结.
二叉树的插入方法是基于二叉搜索树的规则.left<root<right.
结点的高度:结点到其叶子结点的最长路径上的结点个数.
结点的深度:树的根结点到当前结点路径上结点个数.
树的高度:所有结点高度的最大值.
树的深度:所有结点深度的最大值.
树的高度是等于树的深度的.
但是对于所有的结点的高度不一定等于结点的深度.
2.二叉树示例
二叉树图
结果打印
-----------------------
树的高度:
递 归:4
非递归:4
-----------------------
前序遍历:
递 归:56,32,21,25,43,76,88,80,
非递归:56,32,21,25,43,76,88,80,
-----------------------
中序遍历:
递 归:21,25,32,43,56,76,80,88,
非递归:21,25,32,43,56,76,80,88,
-----------------------
中序遍历:
递 归:25,21,43,32,80,88,76,56,
非递归:25,21,43,32,80,88,76,56,
-----------------------
层序遍历:56,32,76,21,43,88,25,80,
3.数据结构
3.1结点
树的结点
/**
* 树的结点
* @param
*/
protected static class Node {
public int value;
public Node left;// 左节点
public Node right;// 右节点
public Node(int value) {
this.value = value;
this.left = null;
this.right = null;
}
}
3.2二叉树
包含一个根和结点个数
public class ExBinaryTree{
private Node root;// 根结点
private int size;
public ExBinaryTree() {
}
}
4.添加结点
/**
* 添加结点:有序添加,left<root<right 重复的元素:后添加的覆盖之前添加的
*
* @param value 结点的值
*/
public void add(int value) {
Node node = null;
// 1.如果是空树,那么将置为根结点
if (root == null) {
node = new Node(value);
root = node;
return;
}
// 2.非空树,先找到插入的位置.
Node parentNode = root;
Node tempNode = root;
int comparator = 0;
while (tempNode != null) {
comparator = value - tempNode.value;
parentNode = tempNode;
if (comparator == 0) {// 插入的结点和当前结点一样大
// 这里处理的是不可以插入重复的结点,覆盖旧结点
tempNode.value = value;
return;
} else if (comparator < 0) {// 插入的结点小
tempNode = tempNode.left;
} else {// 插入的节点大
tempNode = tempNode.right;
}
}
// 3.将新结点通过父结点添加到左节点/右节点
node = new Node(value);
if (comparator < 0) {
parentNode.left = node;
} else {
parentNode.right = node;
}
}
5.遍历
打印结点的值
/**
* 打印结点的值
* @param num
*/
private void printNodeValue(int num) {
System.out.print(num + ",");
}
5.1前序遍历
前序遍历-递归
/**
* 前序遍历:递归方法:root,left,right
*
*/
public void preOrderTraversal() {
preOrderTraversal(root);
}
/**
* 前序遍历:root,left,right
*
* @param node 遍历的结点
*/
private void preOrderTraversal(Node node) {
if (node == null)
return;
// 访问root
printNodeValue(node.value);
// 访问left
preOrderTraversal(node.left);
// 访问right
preOrderTraversal(node.right);
}
前序遍历-非递归
/**
* 前序遍历:非递归:root,left,right
*
*/
public void preOrderTraversal1() {
Node temp = root;
if (temp == null) {
return;
}
// 使用栈存储访问的结点
Stack<Node> stack = new Stack<>();
while (temp != null || !stack.isEmpty()) {
if (temp != null) {
stack.push(temp);
// 访问root
printNodeValue(temp.value);
// 访问left
temp = temp.left;
} else {
temp = stack.pop();
// 访问right
temp = temp.right;
}
}
}
5.2中序遍历
中序遍历-递归
/**
* 中序遍历:递归方法:left,root,right
*
*/
public void inOrderTraversal() {
inOrderTraversal(root);
}
/**
* 中序遍历:递归方法
*
* @param node 遍历到的结点
*/
private void inOrderTraversal(Node node) {
if (node == null)
return;
// 访问left
inOrderTraversal(node.left);
// 访问root
printNodeValue(node.value);
// 访问right
inOrderTraversal(node.right);
}
中序遍历-非递归
/**
* 中序遍历:非递归:left,root,right
*/
public void inOrderTraversal1() {
Node temp = root;
if (temp == null) {
return;
}
// 2.使用栈存储访问的结点
Stack<Node> stack = new Stack<>();
while (temp != null || !stack.isEmpty()) {
// 1.访问left
if (temp != null) {
stack.push(temp);
temp = temp.left;
} else {
temp = stack.pop();
// 2.访问root
printNodeValue(temp.value);
// 3.更改指针,去访问right
temp = temp.right;
}
}
}
5.3后序遍历
后序遍历-递归
/**
* 后序遍历:递归方法:left,right,root
*
*/
public void postOrderTraversal() {
postOrderTraversal(root);
}
/**
* 后序遍历:递归方法:left,right,root
*
* @param node 遍历到的方法
*/
private void postOrderTraversal(Node node) {
if (node == null)
return;
postOrderTraversal(node.left);
postOrderTraversal(node.right);
printNodeValue(node.value);
}
后序遍历-非递归
这里要解决两个问题
1.怎么判断左子树遍历完.
计算一个标记,只要已经打印的阶段,那么在访问上一个结点的话,那么其左子树就是已经访问完了,所以使用了isPeekNode变量,每次打印结点时,给其置为true.当访问未访问的结点时,再置为false.
2.怎么判断右子树遍历完.
如果上一个打印的结点是当前访问结点的右结点,那么说明右子树已经遍历完成,那么就不必再访问右节点,所以此时打印此节点即可.
注意点:
当此节点的左子树访问完毕后,该访问此结点的右结点,那么需要通过此结点访问其右结点,所以要再次把当前结点添加到栈中.再次访问其右节点.
/**
* 后序遍历:非递归:left,right,root
*/
public void postOrderTraversal1() {
Node temp = root;
if (temp == null) {
return;
}
Stack<Node> stack = new Stack<>();
Node lastNode = null;// 为了去判断右子树是否已经访问完毕
boolean isPeekNode = false;// 为了去判断左子树是否已经访问完毕
while (temp != null || !stack.isEmpty()) {
// 1.访问left:
// isPeekNode表示temp结点的左子树已经遍历完成,不必再遍历左侧
if (temp != null && !isPeekNode) {
stack.push(temp);// 入栈
temp = temp.left;
} else {
temp = stack.pop();// 出栈
// 2.访问right:
// temp.right != null:有右节点,
// temp.right == lastNode:表示右节点已经访问
if (temp.right != null && temp.right != lastNode) {
stack.push(temp);// 右子树未访问的,所以要把当前结点再入栈
temp = temp.right;
isPeekNode = false;
} else {
// 3.访问根结点:表示当前temp为根结点的树,已经访问完毕
printNodeValue(temp.value);//
lastNode = temp;
if (stack.isEmpty()) {// 这里是遍历到了最后一个结点
return;
}
temp = stack.peek();// 访问栈顶元素:继续往上遍历
isPeekNode = true;
}
}
}
}
5.4层序遍历
6.树高
树高-递归
每次都要计算出左右子树的最大值,然后+1,直到遍历到叶子结点
/**
* 获取树的高度:递归
*
* @return
*/
public int height() {
// 树的高度,其实就是根结点的高度
return height(root);
}
/**
* 获取某个结点的高度:递归的方法 就是当前结点到叶子结点的路径最长的值
*
* @param node
* @return
*/
private int height(Node node) {
if (node == null)
return 0;
return 1 + Math.max(height(node.left), height(node.right));
}
树高-非递归
按照层级遍历的思想,每遍历完一层,那么高度+1.
使用一个队列存储遍历过的结点.
levelSize:记录当前层级结点个数
/**
* 非递归
*
* @return
*/
public int height1() {
return height1(root);
}
/**
* 获取某个结点的高度:就是当前结点到叶子结点的路径最长的值 使用层次遍历
*
* @param node
* @return
*/
private int height1(Node node) {
if (node == null)
return 0;
// 树的高度
int height = 0;
// 存储着每一层的元素数量
int levelSize = 1;
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
Node tempNode;
while (!queue.isEmpty()) {
tempNode = queue.poll();
levelSize--;
if (tempNode.left != null) {
queue.offer(tempNode.left);
}
if (tempNode.right != null) {
queue.offer(tempNode.right);
}
if (levelSize == 0) { // 意味着即将要访问下一层
levelSize = queue.size();
height++;
}
}
return height;
}
7.调用示例
class TestEx {
public static void main(String[] args) {
method();
}
public static void method() {
int nums[] = new int[] { 56, 76, 32, 43, 21, 25, 88, 80 };
ExBinaryTree tree = new ExBinaryTree();
for (int i = 0; i < nums.length; i++) {
tree.add(nums[i]);
}
System.out.println("\n-----------------------");
System.out.println("树的高度:");
System.out.println("递 归:" + tree.height());
System.out.print("非递归:" + tree.height1());
System.out.println("\n-----------------------");
System.out.println("前序遍历:");
System.out.print("递 归:");
tree.preOrderTraversal();
System.out.print("\n非递归:");
tree.preOrderTraversal1();
System.out.println("\n-----------------------");
System.out.println("中序遍历:");
System.out.print("递 归:");
tree.inOrderTraversal();
System.out.print("\n非递归:");
tree.inOrderTraversal1();
System.out.println("\n-----------------------");
System.out.println("中序遍历:");
System.out.print("递 归:");
tree.postOrderTraversal();
System.out.print("\n非递归:");
tree.postOrderTraversal1();
System.out.println("\n-----------------------");
System.out.print("层序遍历:");
tree.levelOrderTraversal();
}
}
8.总结
对于求遍历和树高的方法中,最好用非递归方式,因为递归方式不断的方法嵌套调用,是一个压栈和出栈过程,比较耗费内存,如果结点个数比较多,树高比较大,那么递归方法会开辟更大的内存,有栈溢出的风险.