树结构的应用day6.5

堆排序

堆排序基本介绍

1)堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn)加粗样式,它也是不稳定排序。
2)堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆,注意:没有要求结点的左孩子的值和右孩子的值的大小关系。
3)每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
4)大顶堆举例说明
在这里插入图片描述
5)小顶堆举例说明
在这里插入图片描述
6)一般升序采用大顶堆,降序采用小顶堆

堆排序基本思想

堆排序的基本思想是:
1)将待排序序列构造成一个大顶堆
2)此时,整个序列的最大值就是堆顶的根节点。
3)将其与末尾元素进行交换,此时末尾就为最大值。
4)然后将剩余n-1 个元素重新构造成-一个堆, 这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

可以看到在构建大顶堆的过程中,元素的个数逐渐减少,最后就得到一个有序序列了.

堆排序步骤图解说明

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

堆排序代码实现***比较难理解

要求:给你-一个数组{4,6,8,5,9} ,要求使用堆排序法,将数组升序排序。
说明:
1}堆排序的速度非常快, 机器上8百万数据3秒左右。O(nlogn)
2)代码实现

package com.xhl.Sort;
import java.text.SimpleDateFormat;
//堆排序
import java.util.Arrays;
import java.util.Date;

public class HeapSort {

	public static void main(String[] args) {
		//要求将数组进行升序排序
//		int arr[] = {4,6,8,5,9};
//		System.out.println("排序前:");
//		System.out.println(Arrays.toString(arr));
		int[] arr = new int[8000000];
		for(int i=0;i<8000000;i++) {
			arr[i]=(int) (Math.random()*8000000);
		}
		
		Date data1 = new Date();
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String data1Str = dateFormat.format(data1);
		System.out.println("排序前的时间为:"+data1Str);
		
		heapSort(arr);
		
//		System.out.println("排序后:");
//		System.out.println(Arrays.toString(arr));
		Date data2 = new Date();
		String data2Str = dateFormat.format(data2);
		System.out.println("排序后的时间为:"+data2Str);
	}

	private static void heapSort(int[] arr) {
		int temp = 0;
		
		//将无序序列构建成一个堆,根据升降需求选择大顶堆or小顶堆
		//arr.length/2-1得到最后一个叶子节点的父节点的位置
		for(int i=arr.length/2-1;i>=0;i--) {
			adjustHeap(arr,i,arr.length);
		}
		
		/*
		 * 2、将堆顶元素与末尾元素交换,将最大元素“沉”到数组末端
		 * 3、重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素
		 *   反复执行调整+交换步骤,直到整个序列有序
		 */
		for(int j=arr.length-1;j>0;j--) {
			temp = arr[j];
			arr[j]=arr[0];
			arr[0] = temp;
			adjustHeap(arr, 0, j);
		}
	}

	//将一个数组(二叉树),调整成一个大顶堆
	/*
	 * 功能:完成将以i对应的非叶子结点的树调整成大顶堆
	 * 举例intarr[]= {4,6,8,5,9};=>i=1 => adjustHeap=>得到{4,9, 8,5, 6}
	 * 如果我们再次调用adjustHeap 传入的是i=0=>得到{4,9,8,5,6}=> {9,6,8,5, 4}
	 */
	private static void adjustHeap(int[] arr, int i, int length) {
		int temp = arr[i];
		//先取出当前元素的值,保存在临时变量中
		
		//1、k=i*2+1是i结点的左结点
		for(int k=i*2+1;k<length;k=k*2+1) {
			if(k+1<length&&arr[k]<arr[k+1]) {//说明左子结点的值小于右子结点的值
				k++;//指向右子结点
			}
			
			if(arr[k]>temp) {//如果子节点大于父节点
				arr[i]=arr[k];//把较大的值赋给当前结点
				i=k;//i指向k,继续循环比较
			}else {
				break;
			}
		}
		
		//当for循环结束后,我们将以i为父节点的树的最大值放在了zuiding(局部)
		arr[i]=temp;
	}

}


在这里插入图片描述

赫夫曼树

基本介绍

  1. 给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree),还有的书翻译为霍夫曼树。
    2)赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近

赫夫曼树几个重要概念和举例说明

  1. 路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1
  2. 结点的权及带权路径长度:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积
  3. 树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL(weighted path length),权值越大的结点离根结点越近的二叉树才是最优二叉树。
  4. WPL最小的就是赫夫曼树
    在这里插入图片描述

赫夫曼树创建思路图解

给你一个数列{13, 7,8,3,29,6, 1},要求转成一颗赫夫曼树.
➢思路分析(示意图):
{13, 7,8,3,29, 6, 1}
构成赫夫曼树的步骤:.

  1. 从小到大进行排序,将每一个数据,每个数据都是一个节点,每个节点可以看成是一颗最简单的二叉树
    2)取出根节点权值最小的两颗二叉树
    3)组成一颗新的二叉树,该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
  2. 再将这颗新的二叉树,以根节点的权值大小再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树
    5)图解:
    在这里插入图片描述

赫夫曼树代码实现

package com.xhl.huffmantree;

//创建结点类
//为了让Node对象持续排序Collections集合排序
//让Node 实现Comparable接口

public class Node implements Comparable<Node> {

	private int value;
	private Node left;
	private Node right;
	
	public int getValue() {
		return value;
	}

	public void setValue(int value) {
		this.value = value;
	}

	public Node getLeft() {
		return left;
	}

	public void setLeft(Node left) {
		this.left = left;
	}

	public Node getRight() {
		return right;
	}

	public void setRight(Node right) {
		this.right = right;
	}

	//前序遍历
	public void preOrder() {
		System.out.println(this);
		if(this.left != null) {
			this.left.preOrder();
		}
		if(this.right!=null) {
			this.right.preOrder();
		}
	}
	
	public Node(int value) {
		this.value = value;
	}
	
	
	
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "Node[value="+value+"]";
	}

	@Override
	public int compareTo(Node arg0) {
		// TODO Auto-generated method stub
		//表示从小到大
		return this.value-arg0.value;
	}

}

package com.xhl.huffmantree;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class HuffmanTree {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int arr[] = {13,7,8,3,29,6,1};
		Node root = createHuffmanTree(arr);
		System.out.println();
		preOrder(root);
	}

	private static Node createHuffmanTree(int[] arr) {
		//第一步为了操作方便
		//1.遍历arr数组.
		//2.将arr的每个元素构成成一个Node
		//3.将Node放入到ArrayList中
		List<Node> nodes = new ArrayList();
		for(int value:arr) {
			nodes.add(new Node(value));
		}
		//处理的过程是一个循环的过程
		while(nodes.size()>1) {
			Collections.sort(nodes);
			
			System.out.println("nodes = "+ nodes);
			//取出根节点权值最小的两颗二叉树
			//(1) 取出权值最小的结点(二叉树)
			Node leftNode = nodes.get(0);
			//(2) 取出权值第二小的结点(二叉树)
			Node rightNode = nodes.get(1);
			
			Node parent = new Node(leftNode.getValue()+rightNode.getValue());
			parent.setLeft(leftNode);
			parent.setRight(rightNode);
			
			//(4)从ArrayList删除处理过的二叉树
			nodes.remove(leftNode);
			nodes.remove(rightNode);
			
			//(5)将parent加入到nodes
			nodes.add(parent);
		}
		System.out.println("nodes = "+ nodes);
		return nodes.get(0);
	}
	
	//前序遍历
	public static void preOrder(Node root) {
		if(root != null) {
			root.preOrder();
		}else {
			System.out.println("空树");
		}
	}

}

在这里插入图片描述

赫夫曼编码

基本介绍

1)赫夫曼编码也翻译为 哈夫曼编码(Huffinan Coding),又称霍夫曼编码,是一种编码方式, 属于一种程序算法
2)赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。
3)赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~90%之间
4)赫 夫曼码是可变字长编码(VLC)的一种。Huffman 于1952年提出一种编码方法,称之为最佳编码

原理剖析

  • 通信领域中信息的处理方式——定长编码

  • 在这里插入图片描述

  • 通信领域中信息的处理方式——变长编码
    在这里插入图片描述

  • 通信领域中信息的处理方式——赫夫曼编码
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    ➢说明:

  • 原来长度是359,压缩了(359-133) / 359 = 62.9%

  • 此编码满足前缀编码,即字符的编码都不能是其他字符编码的前缀。不会造成匹配的多义性赫夫曼编码是无损处理方案。

➢注意事项
注意,这个赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是wpl是一样的,都是最小的,最后生成的赫夫曼编码的长度是一样,比如:如果我们让每次生成的新的二叉树总是排在权值相同的二叉树的最后一个,则生成的二叉树为:
在这里插入图片描述

实践——数据压缩(创建赫夫曼树)

在这里插入图片描述

package com.xhl.huffmantree.eg;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class HuffmanTree {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String strs = "i like like like java do you like a java";
		Map<Character, Integer> map = charCount(strs);
		Node root = createHuffmanTree(map);
		System.out.println();
		preOrder(root);
	}


	private static Map<Character, Integer> charCount(String strs) {
		Map<Character, Integer> map = new HashMap<>();
		strs.replace(" ", "");
		for(int i=0;i<strs.length();i++) {
			int count = map.getOrDefault(strs.charAt(i), 0)+1;
			map.put(strs.charAt(i), count);
		}
		return map;
	}
	private static Node createHuffmanTree(Map<Character, Integer> map) {
		//第一步为了操作方便
		//1.遍历arr数组.
		//2.将arr的每个元素构成成一个Node
		//3.将Node放入到ArrayList中
		List<Node> nodes = new ArrayList();
		for(Character key : map.keySet()) {
			nodes.add(new Node((int) map.get(key)));
		}
		//处理的过程是一个循环的过程
		while(nodes.size()>1) {
			Collections.sort(nodes);
			
			System.out.println("nodes = "+ nodes);
			//取出根节点权值最小的两颗二叉树
			//(1) 取出权值最小的结点(二叉树)
			Node leftNode = nodes.get(0);
			//(2) 取出权值第二小的结点(二叉树)
			Node rightNode = nodes.get(1);
			
			Node parent = new Node(leftNode.getValue()+rightNode.getValue());
			parent.setLeft(leftNode);
			parent.setRight(rightNode);
			
			//(4)从ArrayList删除处理过的二叉树
			nodes.remove(leftNode);
			nodes.remove(rightNode);
			
			//(5)将parent加入到nodes
			nodes.add(parent);
		}
		System.out.println("nodes = "+ nodes);
		return nodes.get(0);
	}
	
	//前序遍历
	public static void preOrder(Node root) {
		if(root != null) {
			root.preOrder();
		}else {
			System.out.println("空树");
		}
	}

}

在这里插入图片描述
在这里插入图片描述

实践——数据压缩(生成赫夫曼编码和赫夫曼编码后的数据)

在这里插入图片描述

package com.xhl.huffmantree.eg;

//创建结点类
//为了让Node对象持续排序Collections集合排序
//让Node 实现Comparable接口

public class Node implements Comparable<Node> {

	private char key;
	private int value;
	private Node left;
	private Node right;
	
	public char getKey() {
		return key;
	}

	public void setKey(char key) {
		this.key = key;
	}

	public int getValue() {
		return value;
	}

	public void setValue(int value) {
		this.value = value;
	}

	public Node getLeft() {
		return left;
	}

	public void setLeft(Node left) {
		this.left = left;
	}

	public Node getRight() {
		return right;
	}

	public void setRight(Node right) {
		this.right = right;
	}

	//前序遍历
	public void preOrder() {
		System.out.println(this);
		if(this.left != null) {
			this.left.preOrder();
		}
		if(this.right!=null) {
			this.right.preOrder();
		}
	}
	
	public Node(char key ,int value) {
		this.key = key;
		this.value = value;
	}
	
	public Node(int value) {
		this.value = value;
	}
	
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "Node[value="+value+"]";
	}

	@Override
	public int compareTo(Node arg0) {
		// TODO Auto-generated method stub
		//表示从小到大
		return this.value-arg0.value;
	}

}
package com.xhl.huffmantree.eg;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class HuffmanTree {

	// 生成赫夫曼树对应的赫夫曼编码
	// 思路:
	// 1.将赫夫曼编码表存放在Map<Character,String>形式
	// a->01 b->100 c->11000等等[形式]
	static Map<Character, String> huffmanCodes = new HashMap<Character, String>();
	// 2.在生成赫夫曼编码表示,需要去拼接路径,定义一个StringBuilder 存储某个叶子结点的路径
	static StringBuilder sb = new StringBuilder();

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String strs = "i like like like java do you like a java";
		Map<Character, Integer> map = charCount(strs);
		Node root = createHuffmanTree(map);
		// preOrder(root);
		getCodes(root);
		for(char key :huffmanCodes.keySet()) {
			System.out.println(key+"-->"+huffmanCodes.get(key));
		}
	}

	private static Map<Character, Integer> charCount(String strs) {
		Map<Character, Integer> map = new HashMap<>();
		for (int i = 0; i < strs.length(); i++) {
			int count = map.getOrDefault(strs.charAt(i), 0) + 1;
			map.put(strs.charAt(i), count);
		}
		return map;
	}

	private static Node createHuffmanTree(Map<Character, Integer> map) {
		// 第一步为了操作方便
		// 1.遍历arr数组.
		// 2.将arr的每个元素构成成一个Node
		// 3.将Node放入到ArrayList中
		List<Node> nodes = new ArrayList();
		for (Character key : map.keySet()) {
			nodes.add(new Node(key, map.get(key)));
		}
		// 处理的过程是一个循环的过程
		while (nodes.size() > 1) {
			Collections.sort(nodes);

			// 取出根节点权值最小的两颗二叉树
			// (1) 取出权值最小的结点(二叉树)
			Node leftNode = nodes.get(0);
			// (2) 取出权值第二小的结点(二叉树)
			Node rightNode = nodes.get(1);

			Node parent = new Node(leftNode.getValue() + rightNode.getValue());
			parent.setLeft(leftNode);
			parent.setRight(rightNode);

			// (4)从ArrayList删除处理过的二叉树
			nodes.remove(leftNode);
			nodes.remove(rightNode);

			// (5)将parent加入到nodes
			nodes.add(parent);
		}
		return nodes.get(0);
	}

	// 前序遍历
	public static void preOrder(Node root) {
		if (root != null) {
			root.preOrder();
		} else {
			System.out.println("空树");
		}
	}

	/*
	 * 功能:将传入的node结点的所有叶子结点的赫夫曼编码得到,并放入到huffmanCodes集合
	 * 
	 * @param node传入结点
	 * 
	 * @param code路径: 左子结点是 0,右子结点1
	 * 
	 * @param stringBuilder用于拼接路径
	 */
	public static void getCodes(Node node, String code, StringBuilder sb) {
		StringBuilder sb2 = new StringBuilder(sb);
		// 将code加入到sb2
		sb2.append(code);
		if (node != null) {
			// 如果node为null则不处理
			// 判断当前node是叶子节点还是非叶子节点
			if (node.getKey() == 0) {
				getCodes(node.getLeft(), "0", sb2);
				getCodes(node.getRight(), "1", sb2);
			} else {
				huffmanCodes.put(node.getKey(), sb2.toString());
			}
		}
	}

	public static void getCodes(Node root) {
		if (root == null) {
			return ;
		}
		getCodes(root.getLeft(), "0", sb);
		getCodes(root.getRight(), "1", sb);
	}

}

在这里插入图片描述

二叉排序树

给你一个数列(7,3,10,12,5,1,9),要求能够高效的完成对数据的查询和添加

➢使用数组
数组未排序,优点: 直接在数组尾添加,速度快。缺点:查找速度慢[示意图]
数组排序,优点:可以使用二分查找,查找速度快,缺点:为了保证数组有序,在添加新数据时,找到插入位置后,后面的数据需整体移动,速度慢。

➢使用链式存储~链表
不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体移动。

➢使用二叉排序树

二叉排序树介绍

二叉排序树: BST: (Binary Sort(Search) Tree),对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
**特别说明:**如果有相同的值,可以将该节点放在左子节点或右子节点

比如针对前面的数据(7,3,10, 12,5, 1,9),对应的二叉排序树为:

在这里插入图片描述

二叉排序树创建和遍历

在这里插入图片描述

二叉排序树的删除

二叉排序树的删除情况比较复杂,有下面三种情况需要考虑
1)删除叶子节点(比如: 2,5,9, 12)
2)删除只有一 颗子树的节点(比如: 1)
3)删除有两颗子树的节点. (比如: 7,3, 10)
4)操作的思路分析
在这里插入图片描述
在这里插入图片描述

package com.xhl.BinarySortTree;

public class Node {
	private int value;
	private Node left;
	private Node right;
	
	public Node(int value) {
		super();
		this.value = value;
	}

	public int getValue() {
		return value;
	}

	public void setValue(int value) {
		this.value = value;
	}

	public Node getLeft() {
		return left;
	}

	public void setLeft(Node left) {
		this.left = left;
	}

	public Node getRight() {
		return right;
	}

	public void setRight(Node right) {
		this.right = right;
	}
	
	/*
	 * 查找要删除的结点
	 * 
	 * @param value 希望删除的结点的值
	 * @return 如果找到返回该结点,否则返回null
	 */
	public Node search(int value) {
		if(value==this.value) {
			return this;
		}else if(value>this.value){
			if(this.right==null) {
				return null;
			}
			return this.right.search(value);
		}else {
			if(this.left==null) {
				return null;
			}
			return this.left.search(value);
		}
		
	}
	
	/*
	 * 查找删除的结点的 父结点
	 */
	
	public Node searchParent(int value) {
		if(this.left!=null&&this.left.value==value||
				this.right!=null&&this.right.value==value) {
			return this;
		}else {
			if(value<this.value) {
				if(this.left==null) {
					return null;
				}
				return this.left.searchParent(value);
			}else {
				if(this.right==null) {
					return null;
				}
				return this.right.searchParent(value);
			}
		}
	}
	
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "Node[value="+value+"]";
	}

	//添加结点的方法
	//递归的形式添加结点,注意需要满足二叉排序的要求
	public void add(Node node) {
		if(node ==null) {
			return;
		}
		if(node.value>this.value) {
			if(this.right==null) {
				this.right=node;
			}else {
				this.right.add(node);
			}
		}else {
			if(this.left==null) {
				this.left=node;
			}else {
				this.left.add(node);
			}
		}
	}
	
	//中序遍历
	public void infixOrder() {
		if(this.left!=null) {
			this.left.infixOrder();
		}
		System.out.println(this);
		if(this.right!=null) {
			this.right.infixOrder();
		}
	}
	
}
package com.xhl.BinarySortTree;

public class BinarySortTree {
	private Node root;

	public Node getRoot() {
		return root;
	}
	
	//查找要删除的节点
	public Node search(int value) {
		if(root == null) {
			System.out.println("空树");
			return null;
		}else {
			return root.search(value);
		}
	}
	
	//查找父节点
	public Node searchParent(int value) {
		if(root == null) {
			System.out.println("空树");
			return null;
		}else {
			return root.searchParent(value);
		}
	}
	
	/*
	 * 1、返回以node为根节点的二叉排序树的最小结点的值
	 * 2、删除node为根据结点的二叉排序树的最小结点
	 */
	
	public int delRightTreeMin(Node node) {
		Node target = node;
		//循环的查找左子节点,就会找到最小值
		while(target.getLeft()!=null) {
			target = target.getLeft();
		}
		delNode(target.getValue());
		return target.getValue();
	}
	
	//删除结点
	public void delNode(int value) {
		if(root==null) {
			return;
		}else {
			//1、需求先去找到要删除的结点
			Node targetNode = search(value);
			//没有找到要删的结点
			if(targetNode==null) {
				return;
			}
			//如果二叉树只有一个结点
			if(root.getLeft()==null&&root.getRight()==null) {
				root = null;
				return;
			}
			
			//找到target的父节点
			Node parent = searchParent(value);
			if(targetNode.getLeft()==null&&targetNode.getRight()==null) {
				if(parent.getLeft()!=null&&parent.getLeft().getValue()==value) {
					parent.setLeft(null);
				}else if(parent.getRight()!=null&&parent.getRight().getValue()==value) {
					parent.setRight(null);
				}
			}else if(targetNode.getLeft()!=null&&targetNode.getRight()!=null) {
				//删除有两个子树的结点
				int minValue = delRightTreeMin(targetNode.getRight());
				targetNode.setValue(minValue);
			}else {
				//删除有一个子树的结点
				if(targetNode.getLeft()!=null) {//需要删除的结点只有一个左结点
					if(parent != null) {//删除的不是root
						if(value==parent.getLeft().getValue()) {
							parent.setLeft(targetNode.getLeft());
						}else {
							parent.setRight(targetNode.getLeft());
						}
					}else {
						root.setLeft(targetNode.getLeft());
					}
				}else {
					if(parent != null) {//删除的不是root
						if(value==parent.getLeft().getValue()) {
							parent.setLeft(targetNode.getRight());
						}else {
							parent.setRight(targetNode.getRight());
						}
					}else {
						root.setLeft(targetNode.getRight());
					}
				}
			}
		}
	}

	//添加结点
	public void add(Node node) {
		if(root == null) {
			root = node;
		}else {
			root.add(node);
		}
	}
	
	//中序遍历
	public void infixOrder() {
		if(root==null) {
			System.out.println("空树");
			return;
		}else {
			root.infixOrder();
		}
	}
}
package com.xhl.BinarySortTree;

public class BinarySortTreeDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr = {7,3,10,12,5,1,9,2};
		BinarySortTree bsTree = new BinarySortTree();
		for(int i:arr) {
			bsTree.add(new Node(i));
		}
		
		System.out.println("中序遍历");
		bsTree.infixOrder();
		
		bsTree.delNode(12);
		bsTree.delNode(1);
		bsTree.delNode(7);
		
		System.out.println("Root:"+bsTree.getRoot());
		
		System.out.println("中序遍历");
		bsTree.infixOrder();
		
	}

}

在这里插入图片描述

平衡二叉树(AVL树)

给你一一个数列{1,2,3,4.5,6}, 要求创建一 颗二叉排序树(BST),并分析问题所在.
➢左边BST存在的问题分析:

  1. 左子树全部为空,从形式上看,更像一个单链表
  2. 插入速度没有影响
  3. 查询速度明显降低(因为需要依次比较),不能发挥BST的优势,因为每次还需要比较左子树,查询速度比单链表还慢
  4. 解决办法—》平衡二叉树

基本介绍

1)平衡二叉树 也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树,可以保证查询效率较高。
2)具有以下特点: 它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、 伸展树等。
3)举例说明, 看看下面哪些AVL树,为什么?
在这里插入图片描述

单旋转(左旋转)

1)要求:给你一个数列,创建出对应的平衡二叉树数列{4,3,6,5,7,8}
2)思路分析(示意图)
在这里插入图片描述
在这里插入图片描述

// 左旋转方法
	public void leftRotate() {
		// 创建新的结点,以根节点的值
		Node newNode = new Node(value);
		// 设置新结点的左子树为根节点的左子树
		newNode.left = left;
		// 设置新结点的右子树为根节点的右子树的左子树
		newNode.right = right.left;
		// 将根节点的值改为根节点右子树的值
		value = right.value;
		// 将newNode设置为根节点的左子树
		left = newNode;
		// 将根节点右子树的右子树设置为根节点的右子树
		right = right.right;
	}

单旋转(右旋转)

1)要求:给你一个数列,创建出对应的平衡二叉树数列{4,3,6,5,7,8}
2)思路分析(示意图)
在这里插入图片描述

// 右旋转
	public void rightRotate() {
		// 创建新的结点,以根节点的值
		Node newNode = new Node(value);

		newNode.right = right;
		newNode.left = left.right;
		value = left.value;
		left = left.left;
		right = newNode;

	}

双旋转

前面的两个数列,进行单旋转(即–次旋转)就可以将非平衡二叉树转成平衡二叉树但是在某些情况下,单旋转不能完成平衡二叉树的转换。比如数列
int[]ar={ 10, 11,7,6,8,9};运行原来的代码可以看到, 并没有转成AVL树.
int[] arr= {2,1,6,5,7,3};// 运行原来的代码可以看到,并没有转成AVL树
1)问题分析
在这里插入图片描述

2)解决思路分析

  • 当符号右旋转的条件时
  • 如果它的左子树的右子树高度大于它的左子树的高度
  • 先对当前这个结点的左节点进行左旋转
  • 在对当前结点进行右旋转的操作即可
package com.xhl.AVLTree;

public class Node {
	private int value;
	private Node left;
	private Node right;

	public Node(int value) {
		super();
		this.value = value;
	}

	public int getValue() {
		return value;
	}

	public void setValue(int value) {
		this.value = value;
	}

	public Node getLeft() {
		return left;
	}

	public void setLeft(Node left) {
		this.left = left;
	}

	public Node getRight() {
		return right;
	}

	public void setRight(Node right) {
		this.right = right;
	}

	//返回左子树高度
	public int leftHeight() {
		if(left==null) {
			return 0;
		}
		return left.height();
	}
	//返回右子树高度
	public int rightHeight() {
		if(right==null) {
			return 0;
		}
		return right.height();
	}
	//返回以该结点为根节点的树高度
	public int height() {
		return Math.max(left==null?0:left.height(), right==null?0:right.height())+1;
	}
	/*
	 * 查找要删除的结点
	 * 
	 * @param value 希望删除的结点的值
	 * 
	 * @return 如果找到返回该结点,否则返回null
	 */
	public Node search(int value) {
		if (value == this.value) {
			return this;
		} else if (value > this.value) {
			if (this.right == null) {
				return null;
			}
			return this.right.search(value);
		} else {
			if (this.left == null) {
				return null;
			}
			return this.left.search(value);
		}

	}

	/*
	 * 查找删除的结点的 父结点
	 */

	public Node searchParent(int value) {
		if (this.left != null && this.left.value == value || this.right != null && this.right.value == value) {
			return this;
		} else {
			if (value < this.value) {
				if (this.left == null) {
					return null;
				}
				return this.left.searchParent(value);
			} else {
				if (this.right == null) {
					return null;
				}
				return this.right.searchParent(value);
			}
		}
	}

	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "Node[value=" + value + "]";
	}

	// 添加结点的方法
	// 递归的形式添加结点,注意需要满足二叉排序的要求
	public void add(Node node) {
		if (node == null) {
			return;
		}
		if (node.value > this.value) {
			if (this.right == null) {
				this.right = node;
			} else {
				this.right.add(node);
			}
		} else {
			if (this.left == null) {
				this.left = node;
			} else {
				this.left.add(node);
			}
		}
		
		//当添加完一个结点以后,如果(右子树-左子树)>1,左旋转
		if(rightHeight()-leftHeight()>1) {
			//如果它的右子树的左子树的高度大于它的右子树的右子树的高度
			if(right!=null&&right.leftHeight()>right.rightHeight()) {
				//先对右子结点进行右旋转
				right.rightRotate();
			}
			//然后再对当前结点进行左旋转
			leftRotate();
			return;
		}
		
		//当添加完一个结点以后,如果(左子树-右子树)>1,右旋转
		if(leftHeight()-rightHeight()>1) {
			if(left!=null&&left.rightHeight()>left.leftHeight()) {
				left.leftRotate();
			}
			rightRotate();
		}
		
		
	}

	// 中序遍历
	public void infixOrder() {
		if (this.left != null) {
			this.left.infixOrder();
		}
		System.out.println(this);
		if (this.right != null) {
			this.right.infixOrder();
		}
	}

	// 左旋转方法
	public void leftRotate() {
		// 创建新的结点,以根节点的值
		Node newNode = new Node(value);
		// 设置新结点的左子树为根节点的左子树
		newNode.left = left;
		// 设置新结点的右子树为根节点的右子树的左子树
		newNode.right = right.left;
		// 将根节点的值改为根节点右子树的值
		value = right.value;
		// 将newNode设置为根节点的左子树
		left = newNode;
		// 将根节点右子树的右子树设置为根节点的右子树
		right = right.right;
	}

	// 右旋转
	public void rightRotate() {
		// 创建新的结点,以根节点的值
		Node newNode = new Node(value);

		newNode.right = right;
		newNode.left = left.right;
		value = left.value;
		left = left.left;
		right = newNode;

	}
	
	

}

在这里插入图片描述
在这里插入图片描述

package com.xhl.AVLTree;

public class AVLTree {
	private Node root;

	public Node getRoot() {
		return root;
	}
	
	//查找要删除的节点
	public Node search(int value) {
		if(root == null) {
			System.out.println("空树");
			return null;
		}else {
			return root.search(value);
		}
	}
	
	//查找父节点
	public Node searchParent(int value) {
		if(root == null) {
			System.out.println("空树");
			return null;
		}else {
			return root.searchParent(value);
		}
	}
	
	/*
	 * 1、返回以node为根节点的二叉排序树的最小结点的值
	 * 2、删除node为根据结点的二叉排序树的最小结点
	 */
	
	public int delRightTreeMin(Node node) {
		Node target = node;
		
		//循环的查找左子节点,就会找到最小值
		while(target.getLeft()!=null) {
			target = target.getLeft();
		}
		delNode(target.getValue());
		return target.getValue();
	}
	
	//删除结点
	public void delNode(int value) {
		if(root==null) {
			return;
		}else {
			//1、需求先去找到要删除的结点
			Node targetNode = search(value);
			//没有找到要删的结点
			if(targetNode==null) {
				return;
			}
			//如果二叉树只有一个结点
			if(root.getLeft()==null&&root.getRight()==null) {
				root = null;
				return;
			}
			
			//找到target的父节点
			Node parent = searchParent(value);
			if(targetNode.getLeft()==null&&targetNode.getRight()==null) {
				if(parent.getLeft()!=null&&parent.getLeft().getValue()==value) {
					parent.setLeft(null);
				}else if(parent.getRight()!=null&&parent.getRight().getValue()==value) {
					parent.setRight(null);
				}
			}else if(targetNode.getLeft()!=null&&targetNode.getRight()!=null) {
				//删除有两个子树的结点
				int minValue = delRightTreeMin(targetNode.getRight());
				targetNode.setValue(minValue);
			}else {
				//删除有一个子树的结点
				if(targetNode.getLeft()!=null) {//需要删除的结点只有一个左结点
					if(parent != null) {//删除的不是root
						if(value==parent.getLeft().getValue()) {
							parent.setLeft(targetNode.getLeft());
						}else {
							parent.setRight(targetNode.getLeft());
						}
					}else {
						root.setLeft(targetNode.getLeft());
					}
				}else {
					if(parent != null) {//删除的不是root
						if(value==parent.getLeft().getValue()) {
							parent.setLeft(targetNode.getRight());
						}else {
							parent.setRight(targetNode.getRight());
						}
					}else {
						root.setLeft(targetNode.getRight());
					}
				}
			}
		}
	}

	//添加结点
	public void add(Node node) {
		if(root == null) {
			root = node;
		}else {
			root.add(node);
		}
	}
	
	//中序遍历
	public void infixOrder() {
		if(root==null) {
			System.out.println("空树");
			return;
		}else {
			root.infixOrder();
		}
	}
	
	
}

package com.xhl.AVLTree;

public class AVLTreeDemo {

	public static void main(String[] args) {
		int[] arr= {10,11,7,6,8,9};
		
		AVLTree avlTree = new AVLTree();
		
		for(int i=0;i<arr.length;i++) {
			avlTree.add(new Node(arr[i]));
		}
		
		System.out.println("infixOrder:");
		avlTree.infixOrder();
		
		System.out.println("the height of tree is "+avlTree.getRoot().height());
		System.out.println("the left_height of tree is "+ avlTree.getRoot().leftHeight());
		System.out.println("the right_height of tree is "+ avlTree.getRoot().rightHeight());
		
	}

}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值