Java实现二叉树
主要内容:
二叉树的链表表示法,
二叉树的先根、中根和后根三种遍历方法。
二叉树的查找、插入和构建。
二叉查找树BST的查找、插入、删除和构建。
平衡二叉树的查找、插入和构建。
代码完美可以运行,暂时不写基础内容了,直接上代码。
package com.tree.binary_Tree;
//递归定义
/* 要不没有根节点 是一颗空树
要不由根结点 左子树 和 右子树构成 且左右子树均为二叉树*/
import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
import org.junit.Test;
import java.util.LinkedList;
//物理结构 链表 ————> 二叉链表
class BinaryNode {
private int data; //数据域
private int layer; //层次
private int height; //高度,AVL需要结点高度计算平衡因子
private BinaryNode lchild; //左子树
private BinaryNode rchild; //右子树
public BinaryNode() { //无参构造
}
/*
public BinaryNode(int data) {
this.data = data;
}
public BinaryNode(int data, BinaryNode lchild, BinaryNode rchild) {
this.data = data;
this.lchild = lchild;
this.rchild = rchild;
}*/ //构造方法先去掉吧
public int getHeight(){return height; }
public void setHeight(int height){this.height = height; }
public int getLayer() {return layer; }
public void setLayer(int layer){this.layer = layer; }
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public BinaryNode getLchild() {
return lchild;
}
public void setLchild(BinaryNode lchild) {
this.lchild = lchild;
}
public BinaryNode getRchild() {
return rchild;
}
public void setRchild(BinaryNode rchild) {
this.rchild = rchild;
}
}
//二叉树的操作类
public class BinaryTree {
//创建一个新结点
public static BinaryNode newNode(int data){
BinaryNode binaryNode = new BinaryNode(); //先new出实例
binaryNode.setData(data);
binaryNode.setLchild(null);
binaryNode.setRchild(null);
binaryNode.setHeight(1); //新生成的结点初始高度为1 这是为了AVL树相关内容,其他内容不受次代码影响
return binaryNode;
}
//二叉树的遍历
//1> 先根遍历 参数为root 是一颗给定二叉树的根结点
public static void preOrder(BinaryNode root){
if (root == null) {
return;
}
System.out.println(root.getData()); //根
preOrder(root.getLchild()); //左
preOrder(root.getRchild()); //右
}
//2> 中根遍历
public static void inOrder(BinaryNode root){
if (root == null) {
return;
}
inOrder(root.getLchild()); //左
System.out.println(root.getData()); //根
inOrder(root.getRchild()); //右
}
//3> 后根遍历
public static void postOrder(BinaryNode root){
if (root == null) {
return;
}
postOrder(root.getLchild()); //左
postOrder(root.getRchild()); //右
System.out.println(root.getData()); //根
}
//4> 难点 层次遍历
//思想 利用队列 BFS思想
public static void layerOrder(BinaryNode root){
//搞个队列
LinkedList list = new LinkedList();
if (root != null){ //先把根插入队列里
list.add(root);
}
while (!list.isEmpty()){
BinaryNode node = (BinaryNode) list.getFirst(); //取出队首的元素
list.removeFirst(); //将队首元素移除
System.out.println(node.getData()); //访问它
if (node.getLchild() != null){ //如果它有左右子树,则把它们分别依次加入队列中
list.add(node.getLchild());
}
if (node.getRchild() != null){
list.add(node.getRchild());
}
}
}
//5> 常考 要求记录各个结点的层数 这样的情况下需要修改BinaryNode结点的结构,附加一个层次信息如下
/*class BinaryNode{
private int data; //数据域
private int layer; //层次
private com.tree.binary_Tree.BinaryNode lchild; //左子树
private com.tree.binary_Tree.BinaryNode rchild; //右子树
}*/
//下面函数层次遍历二叉树,并且给出每个结点的层数
public static void getNodelayer(BinaryNode root){
LinkedList list = new LinkedList();
if (root != null){
root.setLayer(0); //根结点设成第0层
list.add(root);
}
while (!list.isEmpty()){
BinaryNode node = (BinaryNode) list.getFirst();
list.removeFirst(); //弹出队首元素
System.out.println(node.getData() + "层数为:" + node.getLayer());
if (node.getLchild() != null){
node.getLchild().setLayer(node.getLayer() + 1);
list.add(node.getLchild());
}
if (node.getRchild() != null){
node.getRchild().setLayer(node.getLayer() + 1);
list.add(node.getRchild());
}
}
}
//6> 给定一个二叉树的先序序列和中序序列 重建这棵树 递归思想
//参数说明 【preL, preR】是先序序列的端点 【inL, inR】是中序序列的端点
// 假设序列里没有重复数值 int[] pre 是先序数组, int[] in 是中序数组
//基本思想是每次通过一对儿【preL, preR】,【inL, inR】能唯一确定一个结点的位置
public static BinaryNode create(int preL, int preR, int inL, int inR, int[] pre, int[] in){
if (preL > preR){ //递归基线 区间长度为0即可退出
return null;
}
int num = pre[preL]; //根结点值域
BinaryNode root = new BinaryNode();
root.setData(num); //设置根结点的值域
int k = 0; //在中根序列中找根结点的位置 用k记录
for (int i = inL; i <= inR; ++i) { // (注意这个i 不是丛0 到 in.length-1)
if (in[i] == num){
k = i;
break;
}
}
int numLeft = k - inL; //计算一下左子树的区间长度
//对于左子树 递归区间为 【preL + 1, preL + numLeft】 + 【inL, k-1】 递归数组不用变还是int[] pre和 int[] in
root.setLchild(create(preL + 1, preL + numLeft, inL, k-1, pre, in));
//对于右子树 递归区间为 【preL + numLeft + 1, preR】 + 【k + 1, inR】
root.setRchild(create(preL + numLeft + 1, preR, k + 1, inR, pre, in));
return root; //返回根的地址
}
// 7> 查找某一个结点 二叉树的遍历
/* 查找是否有数据域等于给定key的结点 如果有则将其值改为newkey
* 参数 BinaryNode root 以root为根的一颗二叉树
* int key 待查找值*/
public static void search(BinaryNode root, int key, int newkey){
if (root == null){
return;
}
if (root.getData() == key){
root.setData(newkey);
}
search(root.getLchild(), key, newkey);
search(root.getRchild(), key, newkey);
}
//8> 二叉查找树
//8.1> 查找 值为key 的结点 没有找到则给出信息 递归 复杂度O(logn)
public static void search(BinaryNode root, int key){
if (root == null){
System.out.println("NO!");
return;
}
if (root.getData() == key){
System.out.println("找到了" + key + "!");
} else if (key < root.getData()){
search(root.getLchild(), key);
} else {
search(root.getRchild(), key);
}
}
/*// 8.2> 插入结点 跟差找流程一样,只不过在查找失败时说明在这个位置应该插入新的结点
// 这个代码块跳出去之后树没有发生改变,只发生了值传递
public static void insert(BinaryNode root, int key){
if (root == null){
root = BinaryTree.newNode(key);
System.out.println(root.getData());
System.out.println("==================");
return;
}
if (root.getData() == key){
System.out.println("已经有" + key + "了!");
return;
} else if (key < root.getData()){
insert(root.getLchild(), key);
} else {
insert(root.getRchild(), key);
}
}*/
// 8.2.1> 插入结点 跟差找流程一样,只不过在查找失败时说明在这个位置应该插入新的结点
public static BinaryNode insert(BinaryNode root, int key){
if (root == null){
root = newNode(key);
return root;
}
if (root.getData() == key){
System.out.println("已经有" + key + "了!");
} else if (key < root.getData()) {
root.setLchild(insert(root.getLchild(), key));
} else {
root.setRchild(insert(root.getRchild(), key));
}
return root;
}
// 8.3> 二叉查找树的构建 不使用双序列构建,直接用插入的方式在插入结点的过程中就自然地构建好了二叉查找树
//参数 int[] data 元素互不相同,但是不需要保证是递增的 顺序是任意的 但是同一组数据的不同排序会构建出不同的二叉树
public static BinaryNode createBST(int[] data){
BinaryNode root = null; //根结点 初始为空
for (int i = 0; i < data.length; i++) {
root = BinaryTree.insert(root, data[i]); // 这是难点
//这root本质就是个初值为null的指针,每执行insert(root, data[i])一次就生成
//一个新的二叉树,只需要用root指向它的根结点,就能保证找到这个树
}
return root;
}
//9> 二叉查找树的删除
//9.1> 寻找root为根的二叉查找树中的最小权值和最大权值结点
//最小结点即二叉排序树最左边的结点,最大结点即是二叉树中最右的结点
//最小结点丛root开始,一直沿着lChild链找直到lChild为空 最大结点丛root开始一直沿着rChild链找直到rChild为空
public static BinaryNode findMax(BinaryNode root){
while (root.getRchild() != null){
root = root.getRchild();
}
return root;
}
public static BinaryNode findMin(BinaryNode root){
while (root.getLchild() != null){
root = root.getLchild();
}
return root;
}
//删除二叉查找树数值域为key的结点,如果删的是叶节点则直接删除即可,如果待删除的结点有左子树时,用它的前驱代替删除了的结点
//并在它的左子树里删除前驱结点,如果待删除的结点没有左子树但是有右子树,则用它的后继代替它,并在其右子树中删除其后继
//前驱是小于它的最大点,后继是大于它的最小点
public static BinaryNode deleteNode(BinaryNode root, int key){
if (root == null){
return null;
}
if (root.getData() == key){
if (root.getLchild() == null && root.getRchild() == null){
root = null; //是叶节点,直接删除
return root;
} else if (root.getLchild() != null){
BinaryNode preNode = BinaryTree.findMax(root.getLchild()); //找前驱
root.setData(preNode.getData()); //用前驱的数值代替待删除结点数值
root.setLchild(deleteNode(root.getLchild(), preNode.getData())); //在左子树中递归删除前驱结点
} else { //无左子树但是有右子树
BinaryNode nextNode = BinaryTree.findMin(root.getRchild()); //到后继结点
root.setData(nextNode.getData()); //换数值
root.setRchild(deleteNode(root.getRchild(), nextNode.getData())); //在右子树中递归删除后继结点
}
} else if (key < root.getData()){ //去左子树中递归删除
root.setLchild(deleteNode(root.getLchild(), key));
} else { //去右子树中递归删除
root.setRchild(deleteNode(root.getRchild(), key));
}
return root;
}
//10> AVL树走起!
//这个函数获取以root为根的树的高度
public static int getHeight(BinaryNode root){
if (root == null){
return 0;
}
return root.getHeight();
}
//计算结点root的平衡因子
public static int getBalanceFacter(BinaryNode root){
if (root == null){
return 0;
}
if (root.getLchild() != null && root.getRchild() != null){
return root.getLchild().getHeight() - root.getRchild().getHeight();
} else {
if (root.getLchild() == null){
return - root.getRchild().getHeight();
}else {
return root.getLchild().getHeight();
}
}
}
//更新root结点的高度,即左右子树的最大高度再加一
public static void updateHeight(BinaryNode root){
if (root.getLchild() != null && root.getRchild() != null){
root.setHeight(Math.max(root.getLchild().getHeight(), root.getRchild().getHeight()) + 1);
} else {
if (root.getLchild() == null){
root.setHeight(root.getRchild().getHeight() + 1);
} else {
root.setHeight(root.getLchild().getHeight() + 1);
}
}
}
// 10.1> AVL树的查找
//直接用二叉查找树的查找代码即可
// 10.2> AVL树的插入操作
//左旋
public static BinaryNode leftRotation(BinaryNode root){
BinaryNode temp = root.getRchild(); //标记root的右儿子,即将成为根结点
root.setRchild(temp.getLchild()); // temp的左子树接为root的右子树
temp.setLchild(root); //temp的左子树变为root
// 这两布很关键,root和temp的左右子树都调整了,需要重新更新它们的height
updateHeight(root);
updateHeight(temp);
root = temp; //temp结点变为根结点
return root;
}
//右旋
public static BinaryNode rightRotation(BinaryNode root){
BinaryNode temp = root.getLchild(); //标记root的左儿子,即将成为根结点
root.setLchild(temp.getRchild()); // temp的左子树接为root的右子树
temp.setRchild(root); //temp的左子树变为root
// 这两布很关键,root和temp的左右子树都调整了,需要重新更新它们的height
updateHeight(root);
updateHeight(temp);
root = temp; //temp结点变为根结点
return root;
}
// 插入代码 在普通的二叉查找树基础上进行旋转树
public static BinaryNode insertAVL(BinaryNode root, int key){
if (root == null){ //为空时说明没有找到key,此处就是插入位置
root = newNode(key);
return root;
}
if (root.getData() == key){
System.out.println("已经有" + key + "了!");
} else if (key < root.getData()){ //要在左子树里插入
root.setLchild(insertAVL(root.getLchild(), key));
updateHeight(root); //更新树高
if (getBalanceFacter(root) == 2){ //不平衡了 需要调整
if (getBalanceFacter(root.getLchild()) == 1){ //LL型
root = rightRotation(root); //一次右旋搞定
} else if (getBalanceFacter(root.getLchild()) == -1){ //LR型
root.setLchild(leftRotation(root.getLchild())); //先给root的左子树左旋
root = rightRotation(root); //然后root右旋
}
}
} else { //要在右子树里插入
root.setRchild(insertAVL(root.getRchild(), key)); //递归插入
updateHeight(root); //更新高度
if (getBalanceFacter(root) == -2){ //不平衡需要调整了
if (getBalanceFacter(root.getRchild()) == -1){ //RR型
root = leftRotation(root); //root一次左旋
} else if (getBalanceFacter(root.getRchild()) == 1){ //RL型
root.setRchild(rightRotation(root.getRchild()));
root = leftRotation(root);
}
}
}
return root;
}
//最后就是AVL树的数组建立方法 基于二叉查找树,边插入 边建树 边调整 三步同时进行
public static BinaryNode createAVL(int[] data){
BinaryNode root = null; //根结点初始化为空
for (int i = 0; i < data.length; i++) {
root = insertAVL(root, data[i]);
}
return root;
}
//AVL的删除操作比较复杂 暂时不予涉猎
}
//测试一般二叉树的构建和遍历
class Test01{
public static void main(String[] args) {
int[] pre = new int[]{1,2,4,5,3,6,7};
int[] in = new int[]{4,2,5,1,6,3,7};
BinaryNode root = BinaryTree.create(0, 6, 0, 6, pre, in);
BinaryTree.preOrder(root);
System.out.println("==============");
BinaryTree.postOrder(root);
System.out.println("==============");
BinaryTree.layerOrder(root);
}
}
//测试二叉查找树的构建和验证
class Test02{
public static void main(String[] args) {
int[] pre = new int[]{4,3,2,9,6,11};
int[] in = new int[]{2,3,4,6,9,11};
BinaryNode root = BinaryTree.create(0, 5, 0, 5, pre, in);
/*BinaryTree.search(root,4);
BinaryTree.search(root,8);*/
root = BinaryTree.insert(root, 7);
root = BinaryTree.insert(root, 10);
BinaryTree.inOrder(root);
}
}
//测试二叉查找树的数组构建
class Test03{
public static void main(String[] args) {
//同一组数据的不同排列
int[] data1 = new int[]{1,2,4,6,8,13,55,100};
int[] data2 = new int[]{2,4,1,8,13,6,100,55};
BinaryNode root1 = BinaryTree.createBST(data1);
BinaryNode root2 = BinaryTree.createBST(data2);
//两者的中根序是一样的
BinaryTree.inOrder(root1);
System.out.println("===");
BinaryTree.inOrder(root2);
//但是两者别的遍历结果不一样说明树结构是不一样的
System.out.println("===");
BinaryTree.preOrder(root1);
System.out.println("===");
BinaryTree.preOrder(root2);
}
}
//测试二叉查找树的删除操作
class Test04{
public static void main(String[] args) {
int[] data = new int[]{2,4,1,8,13,6,100,55};
BinaryNode root = BinaryTree.createBST(data);
BinaryTree.preOrder(root);
System.out.println("===");
root = BinaryTree.deleteNode(root,13);
BinaryTree.preOrder(root);
}
}
//测试AVL
class Test05{
public static void main(String[] args) {
int[] data = new int[]{8,4,9,1,6};
BinaryNode root = BinaryTree.createBST(data);
BinaryTree.preOrder(root);
System.out.println("===");
root = BinaryTree.insertAVL(root,3);
BinaryTree.preOrder(root);
}
}
知识小结
二叉树的三种遍历中
1 前和后其实性质类似,能够确定出二叉树的根结点
2 只有通过中遍历才能确定出左右子树
3 因此至少要知道中 和 前后任意一者才能确定出二叉树
4 这个结论前提是二叉树的数值域各不相同
完全二叉树的数组存储法
1 完全二叉树有k层 (根为第1层)
2 结点个数为2^k - 1 个
3 整个数组 大小为2^k 即比结点个数多1
4 先将根结点的值域存放在数组下标1处 数组第一个元素空开不存数据或者设个其他默认值
5 之后按层次遍历依次存放剩余结点
6 数组放好后,对任意结点,其下标为i,则其左右结点分别为2i 和2i + 1
7 叶结点的判断条件为其下标 i*2 > 结点总数 即2^k - 1
二叉查找树相关
1 是二叉树
2 左子树的值小于等于根
3 右子树的值大于根
4 用来排序的 排序结果用中序遍历 由小到大
5 便于查找 O(h)
6 用数组构建二叉查找树时,不同的排序方式构造出的树结构不一样
7 如果数组元素是递增排序,则构建出的二叉树是退化树
即它们的中根序一样,但是别的遍历结果不一样
二叉查找树的删除其他注意问题
1 本代码是递归删除法 容易理解
2 还有一种删除前驱或者后继的思路 这也是最开始习惯于想到的思路
那就是记录后继next结点的父节点p,则next一定是p的左儿子,这时next本身是一定没有左子树了
如果也没有右子树那么直接让p的左儿子指针赋值空就ok,如果next结点有右子树,则将next的右子树
接到p的左子树即可。
3 删除前驱是同理的。前驱结点pre必定是其父节点的右儿子且本身没有右子树,考察它有无左子树就ok
4 这个方法需要额外能向上查询到父节点
5 本代码实现其实是优先删除前驱了,即有左子树的情况下肯定删前驱
这样操作多了会导致二叉查找树不平衡,退化成一条链
6 5中的解决方法,1> 交替删除前驱或后继 2> 记录子树高度,优先删较高的一枝子树
二叉查找树引出平衡二叉树
二叉查找树具有高效的查找功能,尽可能保证每个结点均有左右子节点最好
这种形态的二叉查找树高度最小,故查找复杂度才低