树
1、基本介绍
- 数组存储方式分析
- 优点:通过下标方式访问元素,速度快。对于有序数组,还可以使用二分查找提高检索速度
- 缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动值,效率较低
- 链式存储方式分析
- 优点:在一定程度上对数组存储方式有优化(如:插入一个数值节点,只需要将插入节点,链接到链表中即可,删除效率也很好)
- 缺点:在进行检索时,效率仍然较低(需从头到尾开始遍历)
- 树存储方式分析
- 能提高数据存储,读取的效率,比如利用二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度
- 节点
- 根节点
- 父节点
- 子节点
- 叶子节点(没有子节点的节点)
- 节点的权(节点值)
- 路径(从root节点找到该节点的路线)
- 层
- 子树
- 树的高度(最大的层数)
- 森林:多颗子树构成森林
2、二叉树
2.1、基本介绍
- 每个节点最多只能有两个子节点
- 二叉树子节点分为左节点和右节点
- 如果该二叉树的所有叶子节点都在最后一层,并且节点总数为 2^n-1(n为层数),称为满二叉树
- 如果该二叉树的所欲叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树
2.2、二叉树遍历
- 前序遍历:先输出父节点,再遍历左子树和右子树
- 中序遍历:先遍历左子树,在输出父节点,再遍历右子树
- 后序遍历:先遍历左子树,再遍历右子树,最后输出父节点
package tree;
//遍历二叉树
public class BinaryTreeDemo {
public static void main(String[] args) {
//创建二叉树
BinaryTree binaryTree = new BinaryTree();
HeroNode root = new HeroNode(1, "宋江");
HeroNode node2 = new HeroNode(2, "吴用");
HeroNode node3 = new HeroNode(3, "卢俊义");
HeroNode node4 = new HeroNode(4, "林冲");
//手动创建二叉树
root.left = node2;
root.right= node3;
node3.right = node4;
binaryTree.root = root;
//测试
System.out.println("前序遍历");
binaryTree.preOrder();
System.out.println("中序遍历");
binaryTree.infixOrder();
System.out.println("后序遍历");
binaryTree.postOrder();
}
}
//定义BinaryTree 二叉树
class BinaryTree{
public HeroNode root;
//前序遍历
public void preOrder(){
if (this.root != null){
this.root.preOrder();
}
}
//中序遍历
public void infixOrder(){
if (this.root != null){
this.root.infixOrder();
}
}
//后序遍历
public void postOrder(){
if (this.root != null){
this.root.postOrder();
}
}
}
//创建节点
class HeroNode{
public int no;
public String name;
public HeroNode left;
public HeroNode right;
public HeroNode(int no,String name){
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
//前序遍历
public void preOrder(){
System.out.println(this.toString()); // 先输出父节点
//递归向左子树前序遍历
if (this.left != null){
this.left.preOrder();
}
//递归向右子树前序遍历
if (this.right != null){
this.right.preOrder();
}
}
//中序遍历
public void infixOrder(){
//递归向左子树中序遍历
if (this.left != null){
this.left.infixOrder();
}
System.out.println(this.toString());
//递归向右子树中序遍历
if (this.right != null){
this.right.infixOrder();
}
}
//后序遍历
public void postOrder(){
//递归向左子树后序遍历
if (this.left != null){
this.left.postOrder();
}
//递归向右子树后序遍历
if (this.right != null){
this.right.postOrder();
}
System.out.println(this.toString());
}
}
2.3、二叉树查找
- 前序查找
- 先判断当前节点的no是否等于要查找的
- 如果相等,则返回当前节点
- 如果不等,则判断当前节点左子节点是否为空,如果不为空,则递归前序查找
- 如果左递归查找,找到节点,则返回,如果没有找到,则判断当前节点的右子节点是否为空,如果不为空,则继续向右递归前序查找
- 如果找到就返回,否则返回null
- 中序查找
- 判断当前节点的左子节点是否为空,如果不为空,则递归中序查找
- 如果找到,则返回,如果没有找到,就和当前节点比较,如果是则返回,如果不是则进行右递归中序查找
- 如果右递归中序查找找到则返回,否则返回null
- 后序查找
- 判断当前节点的左子节点是否为空,如果不为空,则递归后序查找
- 如果找到,则返回,如果没有找到,则判断当前节点右子节点是否为空,如果不为空,则右递归后序查找
- 如果找到,就返回,如果没有找到,就和当前节点比较,如果是就返回,否则返回null
package tree;
//遍历二叉树
public class BinaryTreeDemo {
public static void main(String[] args) {
//创建二叉树
BinaryTree binaryTree = new BinaryTree();
HeroNode root = new HeroNode(1, "宋江");
HeroNode node2 = new HeroNode(2, "吴用");
HeroNode node3 = new HeroNode(3, "卢俊义");
HeroNode node4 = new HeroNode(4, "林冲");
//手动创建二叉树
root.left = node2;
root.right= node3;
node3.right = node4;
binaryTree.root = root;
//测试
//System.out.println("前序遍历查找");
//HeroNode node = binaryTree.preOrderSearch(3);
//System.out.println("后序查找");
//HeroNode node = binaryTree.postOrderSearch(3);
System.out.println("中序查找");
HeroNode node = binaryTree.infixOrderSearch(3);
if (node != null){
System.out.println(node.toString());
}else{
System.out.println("没有找到");
}
}
}
//定义BinaryTree 二叉树
class BinaryTree{
public HeroNode root;
//前序遍历查找
public HeroNode preOrderSearch(int no){
if (root != null){
return root.preOrderSearch(no);
}else {
return null;
}
}
//中序遍历查找
public HeroNode infixOrderSearch(int no){
if (root != null){
return root.infixOrderSearch(no);
}else {
return null;
}
}
//后序遍历查找
public HeroNode postOrderSearch(int no){
if (root != null){
return root.postOrderSearch(no);
}else {
return null;
}
}
}
//创建节点
class HeroNode{
public int no;
public String name;
public HeroNode left;
public HeroNode right;
public HeroNode(int no,String name){
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
//前序遍历查找
/***
*
* @param no 查找节点的id
* @return 如果找到返回该节点,如果没有找到返回null
*/
public HeroNode preOrderSearch(int no){
//比较当前节点,如果是则返回
if (this.no == no){
return this;
}
HeroNode resNode = null;
//判断当前节点的左子节点,如果不为空则递归前序查找
if (this.left != null){
resNode = this.left.preOrderSearch(no);
}
//如果找到则返回
if (resNode != null){
return resNode;
}
//判断当前节点的右子节点,如果不为空则递归前序查找
if (this.right != null){
resNode = this.right.preOrderSearch(no);
}
//如果找到则返回,如果没有找到则返回null
return resNode;
}
//中序遍历查找
public HeroNode infixOrderSearch(int no){
//判断当前节点的左子节点是否为空,如果不为空,则递归中序查找
HeroNode resNode = null;
if (this.left != null){
resNode = this.left.infixOrderSearch(no);
}
if (resNode != null){
return resNode;
}
if (this.no == no){
return this;
}
if (this.right != null){
resNode = this.right.infixOrderSearch(no);
}
return resNode;
}
//后序遍历查找
public HeroNode postOrderSearch(int no){
HeroNode resNode = null;
if (this.left != null){
resNode = this.left.postOrderSearch(no);
}
if (resNode != null){
return resNode;
}
if (this.right != null){
resNode = this.right.postOrderSearch(no);
}
if (resNode != null){
return resNode;
}
if (this.no == no){
return this;
}
return null;
}
}
2.4、删除二叉树节点
- 规定:
- 如果删除的节点是叶子节点,则删除该节点
- 如果删除的节点是非叶子节点,则删除该子树
//递归删除节点
//1.如果删除的节点是叶子节点,则删除该节点
//2.如果删除的节点是非叶子节点,则删除该子树
public void delNode(int no){
//如果左子节点不为空并且是需要删除的节点
if (this.left != null && this.left.no == no){
//删除左节点
this.left = null;
return;
}
//如果右子节点不为空并且是需要删除的节点
if (this.right != null && this.right.no == no){
//删除右子节点
this.right = null;
return;
}
//向左子树进行递归删除
if (this.left != null){
this.left.delNode(no);
}
//向右子树进行递归删除
if (this.left != null){
this.right.delNode(no);
}
2.5、顺序存储二叉树
- 基本说明
- 从数据存储来看,数组存储方式和树的存储方式可以相互转换的,即数组可以转换成树,树也可以转换为数组
- 要求
- 下图的二叉树的结点,要求以数组的方式来存放
- 要求在遍历数组arr时,仍然可以以前序遍历,中序遍历和后序遍历的方式完成结点的遍历
- 顺序存储二叉树的特点
- 顺序二叉树通常只考虑完全二叉树
- 第n个元素的左子结点为 2 * n + 1
- 第n个元素的右子节点为 2 * n + 2
- 第n个元素的父节点为 (n-1) / 2
//顺序存储二叉树
public class ArrayBinaryTreeDemo {
public static void main(String[] args) {
int arr[] = {1,2,3,4,5,6,7};
ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(arr);
arrayBinaryTree.preOrder();
}
}
//编写ArrayBinaryTree,实现顺序存储二叉树
class ArrayBinaryTree{
public int[] arr; //存储二叉树节点的数组
public ArrayBinaryTree(int[] arr){
this.arr = arr;
}
//顺序二叉树的前序遍历
//重载,默认从0开始
public void preOrder(){
this.preOrder(0);
}
public void preOrder(int index){
//如果数组为空
if (arr == null || arr.length ==0){
System.out.println("数组为空,不能按照二叉树前序遍历");
}
//输出当前这个元素
System.out.println(arr[index]);
//向左递归遍历
if (index * 2 + 1 < arr.length){
preOrder(index * 2 +1);
}
//向右递归遍历
if (index * 2 + 2 < arr.length){
preOrder(index * 2 + 2);
}
}
}
2.6、线索化二叉树
- 将数列{1,3,6,8,10,14}构建成一颗二叉树
- 问题分析
- 当我们对上面的二叉树进行中序遍历时,数列为
- 但是6,8,10,14这几个节点的左右指针,并没有完全利用上
- 希望充分的利用各个节点的左右指针,让各个节点可以指向自己的前后节点
- 线索二叉树
- n个结点的二叉链表表中含有 n+1 [2n-(n-1)=n+1]个空指针域。利用二叉链表中的空指针域,存放指向该结点在某个遍历次序下的前驱和后继结点的指针(这种附加的指针称为“线索”)
- 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
- 一个结点的前一个结点,称为前驱结点
- 一个结点的后一个节点,称为后继结点
- 说明:当线索化二叉树后,Node结点的属性left和right,有如下情况
- left指向的是左子树,也可能是指向的前驱结点,比如①结点的left指向的左子树,而⑩结点的left指向的是前驱结点
- right指向的是右子树,也可能是指向的后继结点,比如①结点right指向的是右子树,而⑩结点的right指向的是后继节点
- 遍历线索化二叉树
- 对前面的中序线索的二叉树,进行遍历
- 因为线索化后,各个结点指向有变化,因此原来的遍历方法不能使用,这时需要使用新的方式遍历线索化二叉树,各个结点可以通过线性方式遍历,因此无需使用递归的方法,这样也提高了遍历效率。遍历的次序应当和中序遍历保持一致
//线索二叉树
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
//测试,中序线索二叉树功能
Node node01 = new Node(1, "name01");
Node node02 = new Node(3, "name02");
Node node03 = new Node(6, "name03");
Node node04 = new Node(8, "name04");
Node node05 = new Node(10, "name05");
Node node06 = new Node(14, "name05");
node01.left = node02;
node01.right = node03;
node02.left = node04;
node02.right = node05;
node03.left = node06;
ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
threadedBinaryTree.root = node01;
//线索化
threadedBinaryTree.threadedNodes(node01);
//以 10 节点做测试
System.out.println("10号节点的前驱节点:"+node05.left.no);
System.out.println("10号节点的后驱节点:"+node05.right.no);
//遍历中序线索化的二叉树
threadedBinaryTree.threadedList();
}
}
//实现线索化功能的二叉树
class ThreadedBinaryTree{
public Node root;
//为了实现线索化,需要创建指向当前节点的前驱的指针
public Node pre = null;
//编写对二叉树进行中序线索化的方法
/***
*
* @param node 当前需要线索化的节点
*/
public void threadedNodes(Node node){
//如果node==null 无法线索化直接退出
if (node == null){
return;
}
//1.先线索化左子树
threadedNodes(node.left);
//2.线索化当前节点
//处理当前节点的前驱节点
if (node.left == null){
//让当前节点的左指针,指向前驱节点
node.left = pre;
node.leftType = 1;
}
//处理当前节点的后驱节点
if (pre != null && pre.right == null){
//让前驱节点的右指针指向当前节点
pre.right = node;
pre.rightType = 1;
}
//每处理一个结点后,让当前节点是下一个节点的前驱节点
pre = node;
//3.线索化右子树
threadedNodes(node.right);
}
//遍历中序线索化二叉树
public void threadedList(){
//定义一个变量,存储当前遍历的节点
Node node = root;
while (node != null){
//循环找到leftType == 1 的节点,第一个找到的就是 8 号节点
//后面随着遍历而变化,因为当leftType == 1 时,说明该结点按照线索化处理后的有效结点
while (node.leftType == 0){
node = node.left;
}
//打印当前节点
System.out.println(node);
//如果当前节点的右指针指向的是后继节点,就一直输出
while (node.rightType == 1){
//获取当前节点的后继节点
node = node.right;
System.out.println(node);
}
// 替换这个遍历的节点
node = node.right;
}
}
}
class Node{
public int no;
public String name;
public Node left;
public Node right;
//如果leftType == 0 表示指向的是左子树,如果为 1 则表示指向前驱节点
//如果rightType == 0 表示指向的是右子树,如果为 1 则表示指向后继节点
public int leftType;
public int rightType;
public Node(int no,String name){
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "Node{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
3、赫夫曼树
- 给定n个权值作为n个叶子节点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)
- 赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近
- 路径和路径长度
- 在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径的长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为 L-1
- 结点的权及带权路径长度
- 若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点之间的路径长度与该结点的权的乘积
- 树的带权路径长度
- 树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL(weighted path length),权值越大的结点离根结点越近的二叉树才是最优二叉树
- WPL最小的就是赫夫曼树
- 赫夫曼树创建思路
- 从小打到进行排序,将每一个数据,每个数据都是一个结点,每个结点可以看成一棵最简单的二叉树
- 取出根结点权值最小的两棵二叉树
- 组成一棵新的二叉树,该新的二叉树的根结点的权值是前面两棵二叉树根结点权值的和
- 再将这颗新的二叉树,以根结点的权值大小再次排序,不断重复1-2-3-4的步骤,直到数列中,所有的数据都被处理,就得到一棵赫夫曼树
public class HuffmanTree {
public static void main(String[] args) {
int arr[] = {13,7,8,3,29,6,1};
Node rootNode = createHuffmanTree(arr);
//前序遍历赫夫曼树
rootNode.preOrder();
}
//创建赫夫曼树的方法
public static Node createHuffmanTree(int[] arr){
//为了操作方便
//1.遍历arr数组
//2.将arr的每一个元素构成一个Node
//3.将Node放入到ArrayList中
List<Node> nodes = new ArrayList<Node>();
for (int value : arr){
nodes.add(new Node(value));
}
while (nodes.size() > 1){
//将集合从小到大排序
Collections.sort(nodes);
//取出权值最小的两个二叉树
//1.取出权值最小的结点(二叉树)
Node leftNode = nodes.get(0);
//2.取出权值第二小的结点(二叉树)
Node rightNode = nodes.get(1);
//构建一棵新的二叉树
Node parent = new Node(leftNode.value + rightNode.value);
parent.left = leftNode;
parent.right = rightNode;
//从list重删除处理过的结点(二叉树)
nodes.remove(leftNode);
nodes.remove(rightNode);
//将parent加入到list
nodes.add(parent);
}
//返回赫夫曼树根结点
return nodes.get(0);
}
}
//创建节点
class Node implements Comparable<Node>{
int value; //权值
Node left; //左子节点
Node right; //右子节点
public Node(int value){
this.value = value;
}
//前序遍历
public void preOrder(){
System.out.println(this);
if (this.left != null){
this.left.preOrder();
}
if (this.right != null){
this.right.preOrder();
}
}
@Override
public String toString(){
return "Node [value = "+ value + "]";
}
public int compareTo(Node o) {
//从小到大
return this.value - o.value;
}
}
4、二叉排序树
- 需求
- 给你一个数列{7,3,10,12,5,1,9},要求能够高效的完成对数据的查询和添加
- 使用数组
- 数组未排序,优点:直接在数组尾添加,速度快,缺点:查找速度慢
- 数组排序,优点:可以使用二分查找,查找速度快,缺点:为了保护数组有序,在添加新数据时,找到插入位置后,后面的数据需整体移动,速度慢
- 使用链式存储-链表
- 不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体移动
-
二叉排序树
- 二叉排序树;BST:(Binary Sort(Search) Tree),对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
- 如果有相同的值,可以将该结点放在左子节点或右子节点
-
二叉排序树删除节点
-
删除叶子节点
- 找到需要删除的结点 targetNode
- 找到targetNode的父节点
- 根据targetNode是parent的左子节点还是右子节点对应删除
- 左子节点:parent.left = null
- 右子节点:parent.right = null
-
删除只有一棵子树的结点
- 找到需要删除的结点 targetNode
- 找到targetNode的父节点parent
- 判断targetNode有左子节点还是右子节点
- 左子节点
- targetNode是parent的左子节点:parent.left = targetNode.left
- targetNode是parent的右子节点:parent.right = targetNode.left
- 右子节点
- targetNode是parent的左子节点:parent.left = targetNode.right
- targetNode是parent的右子节点:parent.right = targeNode.right
- 左子节点
-
删除有两个子树的结点
- 找到需要删除的结点 targetNode
- 找到targetNode的父节点parent
- 从targetNode的右子树找到最小的结点
- 用一个临时变量将最小结点的值保存 temp
- 删除该最小结点
- targetNode.value = temp
package binarySortTree;
//二叉排序树
public class BinarySortTreeDemo {
public static void main(String[] args) {
int arr[]= {7, 3, 10, 12, 5, 1, 9, 2};
BinarySortTree binarySortTree = new BinarySortTree();
for (int i =0;i<arr.length;i++){
binarySortTree.add(new Node(arr[i]));
}
//中序遍历二叉排序树
binarySortTree.infixOrder();
//删除结点
binarySortTree.delNode(7);
binarySortTree.infixOrder();
}
}
//二叉排序树
class BinarySortTree{
private Node root;
//添加节点
public void add(Node node){
if (root == null){
root = node;
}else {
root.add(node);
}
}
//查找要删除的结点
public Node searchNode(int value){
if (root == null){
return null;
}
return root.searchNode(value);
}
//查找要删除节点的父节点
public Node searchParentNode(int value){
if(root == null){
return null;
}
return root.searchParentNode(value);
}
//删除节点
public void delNode(int value){
if (root == null){
return;
}else {
//找到需要删除的结点
Node targetNode = searchNode(value);
//如果没有找到要删除的结点
if (targetNode == null){
return;
}
//如果当前这棵二叉排序树只有一个结点
if (root.left == null && root.right == null){
root = null;
return;
}
//找到需要删除节点的父节点
Node parentNode = searchParentNode(value);
//删除的结点为叶子节点
if (targetNode.left == null && targetNode.right == null){
//判断targetNode是父节点的左子节点还是右子节点
if (parentNode.left != null && parentNode.left.value == targetNode.value){
//如果targetNode为parentNode的左子节点
parentNode.left = null;
}else if (parentNode.right != null && parentNode.right.value == targetNode.value){
//如果targetNode为parentNode的右子节点
parentNode.right = null;
}
//return;
}else if (targetNode.left != null && targetNode.right != null){
//删除的结点有两棵子树
System.out.println("删除具有两棵子树的结点");
//从targetNode的右子树找到最小的结点
Node tempNode = targetNode.right;
if (tempNode.left != null){
tempNode = tempNode.left;
}
delNode(tempNode.value);
targetNode.value = tempNode.value;
return;
}else{
//删除的的结点有一个子树
//如果删除结点有一棵左子树
if (targetNode.left != null){
if (parentNode != null){
//如果targetNode是parentNode的左子节点
if (parentNode.left.value == targetNode.value){
parentNode.left = targetNode.left;
}else {
//如果targetNode是parentNode的右子节点
parentNode.right = targetNode.left;
}
}else {
root = targetNode.left;
}
}else { //如果删除的结点有一棵右子树
if(parentNode != null) {
//如果 targetNode 是 parent 的左子结点
if(parentNode.left.value == value) {
parentNode.left = targetNode.right;
} else { //如果 targetNode 是 parent 的右子结点
parentNode.right = targetNode.right;
}
} else {
root = targetNode.right;
}
}
}
}
}
//中序遍历排序二叉树
public void infixOrder(){
if (root != null){
root.infixOrder();
}else {
System.out.println("二叉排序树为空,不能遍历");
}
}
}
//结点
class Node{
int value;
Node left;
Node right;
public Node(int value){
this.value = value;
}
//添加节点
public void add(Node node){
if (node == null){
return;
}
if (node.value < this.value){
if (this.left == null){
this.left = node;
}else {
//递归的向左子树添加节点
this.left.add(node);
}
}else {
if (this.right == null){
this.right = node;
}else {
//递归的向右子树添加节点
this.right.add(node);
}
}
}
//查找需要删除的结点
public Node searchNode(int value){
//找到需要删除的结点
if (value == this.value){
return this;
}else if (value < this.value){
//如果查找的值小于当前节点,向左子树递归查找
if (this.left == null) {
return null;
}
return this.left.searchNode(value);
}else {
//如果朝招的值大于当前节点,向右子树递归查找
if (this.right == null){
return null;
}
return this.right.searchNode(value);
}
}
//查找要删除节点得父节点
public Node searchParentNode(int value){
//当前节点就是要删除节点的父节点,返回当前节点
if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)){
return this;
}else {
//如果查找的值小于当前节点的值,并且当前节点的左子树不为空
if (value < this.value && this.left != null){
//向左递归查找
return this.left.searchParentNode(value);
}else if (value > this.value && this.right != null){
//如果查找的值大于当前节点的值,并当前节点的右子树不为空
//向右递归查找
return this.right.searchParentNode(value);
}else {
return null; //没有找到父节点
}
}
}
//中序遍历
public void infixOrder(){
if (this.left != null){
this.left.infixOrder();
}
System.out.println(this);
if (this.right != null){
this.right.infixOrder();
}
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
}
5、平衡二叉树
- 案例(说明二叉排序树可能出现的问题)
- 数列 {1,2,3,4,5,6}构建排序二叉树时
- 存在的问题
- 左子树全部为空,从形式上看更像一个单链表
- 插入速度没有影响
- 查询速度明显降低(因为需要一次比较),不能发挥排序二叉树的优势,因为每次比较还需要比较左子树,其查询速度比单链表还要慢
- 解决方案:平衡二叉树
- 平衡二叉树
- 平衡二叉树也叫平衡二叉排序树(Self-balancing binart search tree)又被称为AVL树,可以保证查询效率
- 具体有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等
- 左旋转(右子树高度 - 左子树高度 > 1)
- 创建一个新的结点,以当前节点的值创建
- 把新结点的左子结点设置为当前节点的左子结点
- 把新结点的右子结点设置为当前节点的右子结点的左子结点
- 把当前节点的值换为右子节点的值
- 把当前节点的右子结点设置成当前节点右子结点的右子结点
- 把当前节点的左子结点设置为新结点
- 右旋转(左子树高度 - 右子树高度 > 1)
- 创建新的结点,以当前结点的值创建
- 把新结点的右子节点设置为当前节点的右子节点
- 把新结点的左子节点设置为当前节点的左子节点的右子节点
- 把当当前节点的值设置为当前节点左子节点的值
- 把当前节点的左子节点设置为当前节点的左子节点的左子节点
- 把当前节点的右子节点设置为新结点
- 双旋转
- 当右子树的高度 - 左子树的高度 > 1
- 如果当前节点的右子树的左子树的高度大于它的右子树的高度,以当前右子节点为根结点的树先右旋转,以当前结点根结点的数再左旋转
- 否则直接将以当前节点为根结点的数左旋转
- 当左子树的高度 - 右子树的高度 > 1
- 如果当前节点的左子树的右子树的高度大于左子树的高度,当前节点的左子节点为根结点的树先左旋转,当前节点为根结点的树右旋转
- 否则直接将以当前节点为根结点的树右旋转
- 当右子树的高度 - 左子树的高度 > 1
package avl;
//平衡二叉树
public class AVLTreeDemo {
public static void main(String[] args) {
int arr[] = {10,12,8,9,7,6};
//创建一个平衡二叉树
AVLTree avlTree = new AVLTree();
//添加结点
for (int i =0;i<arr.length;i++){
avlTree.add(new Node(arr[i]));
}
System.out.println("树的高度:"+avlTree.root.height());
System.out.println("左子树的高度:"+avlTree.root.left.height());
System.out.println("右子树的高度:"+avlTree.root.right.height());
}
}
//创建AVL Tree
class AVLTree{
public Node root;
//添加节点
public void add(Node node){
if (root == null){
root = node;
}else {
root.add(node);
}
}
}
//结点
class Node{
int value;
Node left;
Node right;
public Node(int value){
this.value = value;
}
//返回以该结点为根结点的数的高度
public int height(){
return Math.max(left == null?0:left.height(), right ==null?0:right.height()) + 1;
}
//返回左子树的高度
public int leftHeight(){
if (left == null){
return 0;
}
return left.height();
}
//返回右子树的高度
public int rightHeight(){
if (right == null){
return 0;
}
return right.height();
}
//左旋转
private void leftRotate(){
//创建新的结点,以当前结点的值创建
Node node = new Node(value);
//把新的结点的左子树设置成当前节点的左子树
node.left = left;
//把新的结点的右子树设置成当前节点右子树的左子树
node.right = this.right.left;
//把当前节点的值替换成右子节点的值
value = this.right.value;
//把当前节点的右子树设置成当前节点右子树的右子树
this.right = this.right.right;
//把当前节点的左子节点设置成新的结点
this.left = node;
}
//右旋转
private void rightRotate(){
//创建新的结点,以当前结点的值创建
Node node = new Node(this.value);
//把新结点的右子节点设置为当前节点的右子节点
node.right = this.right;
//把新结点的左子节点设置为当前节点的左子节点的右子节点
node.left = this.left.right;
//把当当前节点的值设置为当前节点左子节点的值
this.value = this.left.value;
//把当前节点的左子节点设置为当前节点的左子节点的左子节点
this.left = this.left.left;
//把当前节点的右子节点设置为新结点
this.right = node;
}
//添加节点
public void add(Node node){
if (node == null){
return;
}
if (node.value < this.value){
if (this.left == null){
this.left = node;
}else {
//递归的向左子树添加节点
this.left.add(node);
}
}else {
if (this.right == null){
this.right = node;
}else {
//递归的向右子树添加节点
this.right.add(node);
}
}
//当添加完一个结点后,如果右子树的高度 - 左子树的高度 > 1
if (this.rightHeight() - this.leftHeight() > 1){
//如果当前节点的右子树的左子树的高度大于它的右子树的高度
if (this.right != null && this.right.leftHeight() > this.rightHeight()){
//当前右子树先右旋转
this.right.rightRotate();
//当前节点左旋转
this.leftRotate();
}else {
//左旋转
leftRotate();
}
return;
}
//当添加一个结点后,如果左子树的高度 - 右子树的高度 > 1
if (this.leftHeight() - this.rightHeight() > 1){
//如果它的左子树的右子树高度大于它的左子树的高度
if (this.left != null && this.rightHeight() > this.leftHeight()){
//当前节点的左子树先左旋转
this.left.leftRotate();
//当前节点为根结点的树右旋转
this.rightRotate();
}else {
//右旋转
rightRotate();
}
return;
}
}
}