目录
树的概述
一、为什么需要树这种数据结构
1、数组存储方式分析
2、链式存储方式分析
3、树存储方式的分析
二、树的常用术语
二叉树
一、二叉树的概念
二、二叉树的遍历
思路分析
代码实现
三、前中后序查找
思路分析
?代码实现
四、删除节点
要求
思路分析
代码实现
顺序存储二叉树
代码实现
?线索化二叉树
线索二叉树构建和遍历
代码实现
?二叉排序树
一、二叉排序树介绍
二、二叉排序树创建和遍历
?三、二叉排序树的删除
代码实现
赫夫曼树
重要概念
思路分析
代码实现
赫夫曼编码
平衡二叉树(AVL树)
一、左旋转
代码实现
?二、右旋转
代码实现?
三、双旋转
思路分析
代码实现
多叉树
一、多叉树原理图解
1、二叉树的问题分析
2、多叉树
3、b树
?二、2-3树
思路
三、B树
?B树介绍
?B+树介绍
?B*树介绍
树的概述
一、为什么需要树这种数据结构
1、数组存储方式分析
优点:通过下标访问,速度快,对有序数组,还可以使用二分查找提高效率
缺点:如果检索某个值,或插入值(按一定顺序)要整体移动,效率低
2、链式存储方式分析
优点:在一定程度上对数组优化(插入删除这些效率高)
缺点:检索时,效率仍然较低,比如找某个值,需要从头到尾遍历
3、树存储方式的分析
能提高数据存储,读取存储的效率,比如利用二叉排序树,既保证数据检索速度,同时也能保证增删改查的速度
二、树的常用术语
二叉树
一、二叉树的概念
1、数有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树
2、二叉树的子节点分为左节点和右节点
3、如果二叉树的所有节点都在最后一层,并且节点总数=2n次方-1,n为层数,则为满二叉树
4、如果二叉树所有节点都在最后一层或者倒数第二层,而且最后一层的叶子节点左连续,倒数第二层叶子节点右连续,称完全二叉树
二、二叉树的遍历
思路分析
前序遍历:先输出父节点,再遍历左子树和右子树
中序遍历:先遍历左子树,在输出父节点,再遍历右子树
后序遍历:先遍历左子树,再遍历右子树,最后输出父节点
小结:父节点的顺序,就能确定是哪种遍历
代码实现
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,"张飞");
HeroNode node5 = new HeroNode(5,"刘备");
//我们先手动创建二叉树,后面我们学递归的方式创建
root.setLeft(node2);
root.setRight(node3);
node3.setRight(node4);
node3.setLeft(node5);
binaryTree.setRoot(root);
System.out.println("前序遍历: ");
binaryTree.preOrder();
System.out.println("中序遍历: ");
binaryTree.infixOrder();
System.out.println("后序遍历: ");
binaryTree.postOrder();
}
}
//定义BinaryTree 二叉树
class BinaryTree{
private HeroNode root;
public void setRoot(HeroNode root) {
this.root = root;
}
//前序遍历
public void preOrder(){
if (this.root!=null){
this.root.preOrder();
}else{
System.out.println("二叉树为空 无法遍历");
}
}
//中序遍历
public void infixOrder(){
if (this.root!=null){
this.root.infixOrder();
}else{
System.out.println("二叉树为空 无法遍历");
}
}
//后序遍历
public void postOrder(){
if (this.root!=null){
this.root.postOrder();
}else{
System.out.println("二叉树为空 无法遍历");
}
}
}
//先创建HeroNode节点
class HeroNode{
private int no;
private String name;
private HeroNode left;
private HeroNode right;
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
public String toString(){
return "HeroNode[no=" + no + ",name=" + name + "]";
}
//前序遍历的方法
public void preOrder(){
System.out.println(this);
//递归向左子树前序遍历
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);
//递归向右子树前序遍历
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);
}
}
三、前中后序查找
思路分析
?代码实现
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,"张飞");
HeroNode node5 = new HeroNode(5,"刘备");
//我们先手动创建二叉树,后面我们学递归的方式创建
root.setLeft(node2);
root.setRight(node3);
node3.setRight(node4);
node3.setLeft(node5);
binaryTree.setRoot(root);
System.out.println("前序遍历: ");
binaryTree.preOrder();
System.out.println("中序遍历: ");
binaryTree.infixOrder();
System.out.println("后序遍历: ");
binaryTree.postOrder();
//前序遍历
HeroNode resNode = binaryTree.preOrderSearch(5);
if (resNode != null){
System.out.printf("找到了,信息为"+resNode.getNo()+" "+resNode.getName());
}else {
System.out.println("没有找到该英雄");
}
}
}
//定义BinaryTree 二叉树
class BinaryTree{
private HeroNode root;
public void setRoot(HeroNode root) {
this.root = root;
}
//前序遍历
public void preOrder(){
if (this.root!=null){
this.root.preOrder();
}else{
System.out.println("二叉树为空 无法遍历");
}
}
//中序遍历
public void infixOrder(){
if (this.root!=null){
this.root.infixOrder();
}else{
System.out.println("二叉树为空 无法遍历");
}
}
//后序遍历
public void postOrder(){
if (this.root!=null){
this.root.postOrder();
}else{
System.out.println("二叉树为空 无法遍历");
}
}
//前序遍历查找
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;
}
}
}
//先创建HeroNode节点
class HeroNode{
private int no;
private String name;
private HeroNode left;
private HeroNode right;
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
public String toString(){
return "HeroNode[no=" + no + ",name=" + name + "]";
}
//前序遍历的方法
public void preOrder(){
System.out.println(this);
//递归向左子树前序遍历
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);
//递归向右子树前序遍历
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);
}
//前序遍历查找
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);
}
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 resNode;
}
}
四、删除节点
要求
- 如果删除的节点是叶子节点,则直接删除该节点
- 如果删除节点是非叶子节点,则删除该子树
- 测试,删除掉5号叶子节点和3号子树
思路分析
- 因为我们的二叉树是单向的,所有我们是判断当前结点的子节点是否需要删除节点,而不能去判断当前结点是不是需要删除的节点
- 如果当前节点的左子树不为空,并且左子节点就是要删除的节点,就将this.left = null
- 如果当前结点的右子节点不为空,并且右子节点就是要删除的节点,就将this.right = null
- 如果2和3都没有删除节点,那么就要向左子树进行递归删除
- 如果4也没有删除,则向右子树递归删除
- 考虑如果数是空树3root,如果只有一个root节点,则等价于将二叉树置空
代码实现
在heroNode类中新增方法delNode(递归删除节点)
//递归删除节点
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.right!=null){
this.right.delNode(no);
}
}
在BinaryTree类中添加方法delNode(int no)
public void delNode(int no){
if (root!=null){
//先判断本节点是不是 因为方法只能判断根节点下面的节点
if (root.getNo() == no){
root = null;
}else {
root.delNode(no);
}
}else {
System.out.println("这是一个空树,无法删除");
}
}
main方法中测试
System.out.println("删除前");
binaryTree.preOrder();
binaryTree.delNode(5);
System.out.println("删除后");
binaryTree.preOrder();
顺序存储二叉树
顺序存储二叉树的概念
?顺序存储二叉树的特点
- 顺序二叉树通常只考虑完全二叉树
- 第n个元素的左子节点为2*n+1
- 第n个元素的右子节点为2*n+2
- 第n个元素的父节点为(n-1)/2
n表示二叉树中的第几个元素(按零开始,编号如图所示)
代码实现
只有前序遍历的,中序遍历和后续遍历就是位置换一下 其他是一样的
public class ArrBinaryTreeDemo {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7};
//创建一个ArrBinaryTree
ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
arrBinaryTree.preOrder(0); //前序遍历
}
}
//编写ArrayBinaryTree 实现顺序存储二叉树遍历
class ArrBinaryTree{
private int[] arr; //存储数据节点的数组
public ArrBinaryTree(int[] arr) {
this.arr = arr;
}
//重载preOrder
public void preOrder(){
this.preOrder(0);
}
//编写一个方法,完全顺序存储二叉树的前序遍历
public void preOrder(int index){
//如果数组为空,或者arr.length = 0
if (arr ==null || arr.length == 0){
System.out.println("数组为空,不能前序遍历");
}
//输出当前这个元素
System.out.println(arr[index]);
//向左递归遍历
if ((index*2+1)<arr.length){
preOrder(2*index+1);
}
//向右递归遍历
if ((index*2+2)< arr.length){
preOrder(2*index+2);
}
}
}
?线索化二叉树
n个节点的二叉链表中含有n+1个空指针域(没有指向节点的指针,每个节点都有两个指针,叶子节点两个空指针域),利用二叉链表中空指针域,存放该节点在某种遍历次序下的前驱和后继节点的指针,称线索。
线索二叉树构建和遍历
说明:对前面的中序线索化的二叉树进行遍历
因为线索化后,各个节点的指向有所改变,所以原来的遍历方式不能使用,这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线性方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。遍历的次序应当和中序遍历保持一致
代码实现
public class ThreadedBinaryTree {
public static void main(String[] args) {
//测试中序线索二叉树的功能
HeroNode root = new HeroNode(1, "Tom");
HeroNode node2 = new HeroNode(3, "jack");
HeroNode node3 = new HeroNode(6, "smith");
HeroNode node4 = new HeroNode(8, "mary");
HeroNode node5 = new HeroNode(10, "king");
HeroNode node6 = new HeroNode(14, "dim");
root.setLeft(node2);
root.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6);
//测试线索化
BinaryTree binaryTree = new BinaryTree();
binaryTree.setRoot(root);
binaryTree.threadedNodes();
//以10号节点为例
HeroNode leftNode = node5.getLeft();
HeroNode rightNode = node5.getRight();
System.out.println("10号节点的前驱节点是=" + leftNode);
System.out.println("10号节点的后继节点是=" + rightNode);
//线索化的方式遍历线索化二叉树
System.out.println("线索化的方式遍历线索化二叉树");
binaryTree.threadedList();//8->3->10->1->14->6
}
}
//定义ThreadedBinaryTree实现了线索化功能的二叉树
class BinaryTree {
private HeroNode root;
//再递归进行线索化时,这个pre总是保留前一个节点
private HeroNode pre = null;
public void setRoot(HeroNode root) {
this.root = root;
}
//重载
public void threadedNodes() {
this.threadedNodes(root);
}
//遍历线索化二叉树的方法
public void threadedList() {
//定义临时存储变量node
HeroNode node = root;
while (node != null) {
//循环的找到leftType==1的节点,第一个找到的应该是8
while (node.getLeftType() == 0) {
node = node.getLeft();
}
System.out.println(node);
//如果当前节点的右指针指向的是后继节点,就一直输出
while (node.getRightType() == 1) {
node = node.getRight();
System.out.println(node);
}
//替换这个遍历的节点
node = node.getRight();
}
}
//建立中序线索化二叉树方法
/**
* @param node 当前需要线索化的节点
*/
public void threadedNodes(HeroNode node) {
//如果当前节点为空
if (node == null) {
return;
}
//(1)先线索化左子树
threadedNodes(node.getLeft());
//(2)线索化当前节点[有难度]
/*先处理当前节点的前驱节点*/
/*以8节点为例:8.left = null;8.letfType = 1*/
if (node.getLeft() == null) {
//让当前节点的左指针指向前驱节点
node.setLeft(pre);
//并且修改当前节点的左指针的类型,指向前驱节点
node.setLeftType(1);
}
/*再处理后继节点*/
if (pre != null && pre.getRight() == null) {
pre.setRight(node);
pre.setRightType(1);
}
//每处理一个节点后,让当前节点是下一个节点的前驱节点
pre = node;
//(3)再线索化右子树
threadedNodes(node.getRight());
}
}
?二叉排序树
一、二叉排序树介绍
我们先看需求
要求给一个数组(7,3,10,12,5,1,9)要求能高效的完成对数据的查询和添加
解决方案分析
数组没有排序,优点:直接数组尾添加,速度快。缺点:查询速度慢
数组有序,优点:可以使用二分查找,查找速度快。缺点:为了保证数组有序,在添加新数据,找到插入位置后,后面的数据整体移动,速度慢
使用链式存储-链表,不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体移动
这时候我们就可以使用?二叉排序树?
二叉排序树:对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点小,右子节点的值比当前节点的值大。
特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点
比如说我们在下面二叉树插入2,对应二叉排序树为:
二、二叉排序树创建和遍历
什么都不用说,我们直接上代码
public class BinarySortTreeDemo {
public static void main(String[] args) {
int[] arr = {7,3,10,12,5,1,9};
BinarySortTree binarySortTree = new BinarySortTree();
for (int i=0;i< arr.length;i++){
binarySortTree.add(new Node(arr[i]));
}
binarySortTree.infixOrder();
}
}
//创建二叉排序树
class BinarySortTree{
private Node root;
//添加节点的方法
public void add(Node node){
if (root == null){
root = node;
}else{
root.add(node);
}
}
//中序遍历
public void infixOrder(){
if (root!=null){
root.infixOrder();
}else{
System.out.println("二叉树为空,不能遍历");
}
}
}
//创建node节点
class Node{
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
//添加节点
//递归的形式添加节点,注意需要满足二叉排序树要求
public void add(Node node){
if(node == null){
return;
}
//判断传入节点的值和当前树根节点的值关系
if (node.value<this.value){
//如果左子节点为null直接挂上
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 void infixOrder(){
if (this.left!=null){
this.left.infixOrder();
}
System.out.println(this);
if (this.right!=null){
this.right.infixOrder();
}
}
}
最后看输出结果我们发现,只要是二叉排序树,中序遍历出来就是顺序的
?三、二叉排序树的删除
二叉排序树的删除情况比较复杂,下面有三种情况要考虑
- 删除叶子节点 2、5、9、12
- 删除只有一颗子树的节点1
- 删除有两颗子树的节点7、3、10
?
比如我们删10,就必须右子树找最小的11,然后把11的位置换到10?
代码实现
第一种情况(删除的节点是个叶子节点)
在node类新增方法
//查找要删除的节点
public Node search(int value){
if (value == this.value){
return this;
} else if (value<this.value) {
//如果左子节点为null就不用找且返回null
if (this.left == null){
return null;
}
return this.left.search(value);
}else{
if (this.right == null){
return null;
}
return this.right.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 && this.left!=null){
return this.left.searchParent(value);
}else if (value>=this.value&&this.right!=null){
return this.right.searchParent(value);//向右递归
}else {
return null;
}
}
}
在tree类新增方法
//查找要删除的节点
public Node search(int value){
if (root == null){
return null;
}else {
return root.search(value);
}
}
//查找父节点
public Node searchParent(int value){
if (root == null){
return null;
}else {
return root.searchParent(value);
}
}
//删除节点
public void deNode(int value){
if (root == null){
return;
}else {
Node search = search(value);
if (search==null){
return;
}
//如果我们发现当前这颗二叉排序树只有一个节点
if (root.left==null && root.right==null){
root = null;
return;
}
//查找他的父节点
Node parent = searchParent(value);
if (search.left==null&&search.right==null){
if (parent.left!=null && parent.left.value == value){
parent.left = null;
} else if (parent.right!=null && parent.right.value == value) {
parent.right = null;
}
}
}
}
main方法中测试?
binarySortTree.infixOrder();
//测试删除节点
binarySortTree.deNode(2);
binarySortTree.infixOrder();
第二种情况(删除的节点有一个节点)
在node类的delNode中添加条件
//查找他的父节点
Node parent = searchParent(value);
if (search.left==null&&search.right==null){
if (parent.left!=null && parent.left.value == value){
parent.left = null;
} else if (parent.right!=null && parent.right.value == value) {
parent.right = null;
}
//第三种情况 删除有两个节点的
} else if (search.left!=null && search.right!=null) {
//剩下的就是只有一个节点的
}else {
//如果要删除的节点有左子节点
if (search.left!= null){
if (parent.left.value==value){
parent.left = search.left;
}else { //说明是右子节点
parent.right = search.left;
}
}else { //要删除的节点有右子及节点
if (parent.left.value==value){
parent.left = search.right;
}else { //说明是右子节点
parent.right = search.right;
}
}
}
?我们还需要考虑一种情况 要删除的节点没有父节点时
//如果要删除的节点有左子节点
if (search.left!= null){
if(parent!=null){
if (parent.left.value==value){
parent.left = search.left;
}else { //说明是右子节点
parent.right = search.left;
}
}else {
root=search.left;
}
}else { //要删除的节点有右子及节点
if (parent!= null) {
if (parent.left.value==value){
parent.left = search.right;
}else { //说明是右子节点
parent.right = search.right;
}
}else {
root=search.right;
}
}
第三种情况(删除的节点有两个子节点)
我们可以把要删除的右子数里找最小的节点替换删除的
也可以在左子树找最大的替换删除的节点
//查找父节点
public Node searchParent(int value){
if (root == null){
return null;
}else {
return root.searchParent(value);
}
}
// node当做二叉排序树的根节点,返回以node为根节点的二叉排序树的最小结点的值
public int delRightTreeMin(Node node){
Node target = node;
while (target.left!= null){
target = target.left;
}
//这时target就指向最小节点
//删除最小结点
deNode(target.value);
return target.value;
}
//删除节点
public void deNode(int value){
if (root == null){
return;
}else {
Node search = search(value);
if (search==null){
return;
}
//如果我们发现当前这颗二叉排序树只有一个节点
if (root.left==null && root.right==null){
root = null;
return;
}
//查找他的父节点
Node parent = searchParent(value);
if (search.left==null&&search.right==null){
if (parent.left!=null && parent.left.value == value){
parent.left = null;
} else if (parent.right!=null && parent.right.value == value) {
parent.right = null;
}
//第三种情况 删除有两个节点的
} else if (search.left!=null && search.right!=null) {
int minVal = delRightTreeMin(search.right);
search.value = minVal;
//剩下的就是只有一个节点的
}else {
//如果要删除的节点有左子节点
if (search.left!= null){
if (parent.left.value==value){
parent.left = search.left;
}else { //说明是右子节点
parent.right = search.left;
}
}else { //要删除的节点有右子及节点
if (parent.left.value==value){
parent.left = search.right;
}else { //说明是右子节点
parent.right = search.right;
}
}
}
}
}
赫夫曼树
给定n个权值作为n个叶子节点,构造一颗二叉树,若带权路径长度达到最小WPL,称二叉树为最优二叉树,也叫赫夫曼树
赫夫曼树是带权路径长度最短的树,权值较大的节点离根较近
重要概念
?
思路分析
- 从小到大进行排序,将每个数据,每个数据都是一个节点,每个节点可以看成是一颗最简单的二叉树
- 取出根节点权值最小的两颗二叉树
- 组成一颗新的二叉树,该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
- 再将这颗新二叉树,以根节点权值大小排序,不断重复1234,直到所有数据处理
代码实现
public class huffmanTree {
public static void main(String[] args) {
int arr[]={13,7,8,3,29,6,1};
Node root = createHuffmanTree(arr);
preOrder(root);
}
//编写一个前序遍历方法
public static void preOrder(Node root){
if (root!=null){
root.preOrder();
}else {
System.out.println("是空树,不能遍历");
}
}
//创建哈夫曼树的方法
public static Node createHuffmanTree(int[] arr){
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);
//取出根节点权值最小的两颗二叉树
Node left = nodes.get(0);//取出最小的
Node right = nodes.get(1);//取出第二小的
//构成一个新的二叉树
Node parent = new Node(left.value + right.value);
parent.left = left;
parent.right = right;
//从ArrayList中删除已经处理过的二叉树
nodes.remove(left);
nodes.remove(right);
//将新的二叉树加入集合
nodes.add(parent);
//排序
Collections.sort(nodes);
}
return nodes.get(0);
}
}
class Node implements Comparable<Node>{
int value; //权值
Node left;
Node 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() {
return "Node{" +
"value=" + value +
'}';
}
@Override
public int compareTo(Node o) {
//从小到大排序
return this.value - o.value;
}
}
赫夫曼编码
赫夫曼编码是一种编码方式,属于程序算法,广泛用于数据文件压缩,压缩率在20%~90%
?
?注意:赫夫曼树根据排序方法的不同,也可能不太一样,比如大的方左边,这样对赫夫曼编码也不一样,但是wpl是一样的,都是最小的。最后生成的赫夫曼编码长度也是一样的。
平衡二叉树(AVL树)
?平衡二叉树也叫平衡二叉搜索树,AVL树。可以保证查询效率较高
他是一颗空树或他的左右两个子树的高度差绝对值不超过1,并且左右两个子树都是一颗平衡二叉树。实现方法有:红黑树、AVL 、替罪羊树、Treap、伸展树等
一、左旋转
代码实现
首先写出计算树高度的方法
// 返回 以该结点为根结点的树的高度
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();
}
左旋转的方法
当右子树的高度 - 左子树的高度 > 1的时候
//左旋转方法
private void leftRotate() {
//创建新的结点,以当前根结点的值
Node newNode = new Node(value);
//把新的结点的左子树设置成当前结点的左子树
newNode.left = left;
//把新的结点的右子树设置成带你过去结点的右子树的左子树
newNode.right = right.left;
//把当前结点的值替换成右子结点的值
value = right.value;
//把当前结点的右子树设置成当前结点右子树的右子树
right = right.right;
//把当前结点的左子树(左子结点)设置成新的结点
left = newNode;
}
?二、右旋转
代码实现?
右旋转的方法?
当左子树的高度 - 右子树的高度 > 1的时候
//右旋转
private void rightRotate() {
//创建新的结点,以当前根结点的值
Node newNode = new Node(value);
// 新节点的右子树设置为当前节点的右子树
newNode.right = right;
// 新节点的左子树设置为当前节点的左子树的右子树
newNode.left = left.right;
// 当前的值设置为左子树的值
value = left.value;
// 当前节点的左子树设置为左子树的左子树
left = left.left;
// 当节点的右子树设置为新节点
right = newNode;
}
三、双旋转
在某种情况下,有可能单次旋转并不能满足转换为平衡二叉树,比如这种:
?所以在这种情况下,我们可能就需要旋转两次了
思路分析
- 当符合右旋转的条件时
- 如果它的左子树的右子树的高度大于它的左子树的高度
- 那就先对当前的节点进行左旋转
- 再对当前结点进行右旋转即可
代码实现
//当添加完一个结点后,如果: (右子树的高度-左子树的高度) > 1 , 左旋转
if(rightHeight() - leftHeight() > 1) {
//如果它的右子树的左子树的高度大于它的右子树的右子树的高度
if(right != null && right.leftHeight() > right.rightHeight()) {
//先对右子结点进行右旋转
right.rightRotate();
//然后在对当前结点进行左旋转
leftRotate(); //左旋转..
} else {
//直接进行左旋转即可
leftRotate();
}
return ; //必须要!!!
}
//当添加完一个结点后,如果 (左子树的高度 - 右子树的高度) > 1, 右旋转
if(leftHeight() - rightHeight() > 1) {
//如果它的左子树的右子树高度大于它的左子树的高度
if(left != null && left.rightHeight() > left.leftHeight()) {
//先对当前结点的左结点(左子树)->左旋转
left.leftRotate();
//再对当前结点进行右旋转
rightRotate();
} else {
//直接进行右旋转即可
rightRotate();
}
}
多叉树
一、多叉树原理图解
1、二叉树的问题分析
二叉树需要加载到内存的,如果二叉树节点很多会出现:
- 在构建二叉树时候,需要多次进行io操作(海量数据存在数据库或文件中),节点海量,构建二叉树时,速度有影响
- 节点海量,造成二叉树高度很大,会降低操作速度
2、多叉树
3、b树
?二、2-3树
2-3树是最简单的B树结构,他通过重新组织节点可以降低树的高度,提高查找效率
- 2-3树的所有叶子节点都在同一层(只要是B树都满足这个条件)
- 有两个子节点的节点叫二节点。二节点要么没有节点,要么有两个节点,不能只有一个
- 有三个节点的节点叫三节点,三节点要么没有节点,要么有三个节点
- 2-3树是由二节点和三节点构成的树
- 三节点的三个子节点123,2要满足在两个父节点的值之间,如上面图2-3树10必须是8和12之间,7小于8和12,14大于8和12。
思路
?
?
三、B树
?B树介绍
?B-tree,平衡的意思Balanced。很多习惯把B-tree写成B-树,就是B树的意思。
数据存在整颗树里,不一定在叶子节点上
?B+树介绍
所有真实的数据都只存在叶子节点中
?B*树介绍
B*树是B+树的变体,在B+数的非根和非叶子节点增加指向兄弟的指针,一定程度上提高了我们的使用率。
|