1.基础知识
1.1 常用术语
- 节点
- 根节点
- 父节点
- 子节点
- 叶子节点
- 节点的权(节点的值)
- 路径 (从root节点找到该节点的路线)
- 层
- 子树
- 树的高度(最大层数)
- 森林(多课子树可构成森林)
1.2 二叉树
- 二叉树概念:每个节点最多只能有两个子节点的一种形式称为二叉树
- 二叉树的左子节点和右子节点
- 如果该二叉树的所有叶子节点都在最后一层,并且叶子节点总数=2(n-1) (所有节点总数 = 2n -1),n为层数,则我们称为满二叉树。
- 如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树。(满二叉树是一种特殊的完全二叉树)
1.3 二叉树的遍历、查找、删除
- 前序遍历:父 左 右
- 中序遍历:左 父 右
- 后序遍历:左 右 父
遍历代码
package Package01;
public class BinaryTree {
public static void main(String[] args) {
//先需要创建一棵二叉树
BinaryTreee binaryTreee = new BinaryTreee();
HeroNode root = new HeroNode(1,"宋江");
HeroNode node2 = new HeroNode(2,"吴用");
HeroNode node3 = new HeroNode(3,"卢俊义");
HeroNode node4 = new HeroNode(4,"林冲");
//我们先手动创建二叉树,后面再使用递归创建二叉树
root.setLeft(node2);
root.setRight(node3);
node3.setRight(node4);
binaryTreee.setRoot(root);
//测试
System.out.println("前序遍历" );
binaryTreee.preOrder();
}
}
//定义一个BinaryTree
class BinaryTreee{
public 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; //默认为nul,不需要写在构造函数里面
private HeroNode right; //默认为null,不需要写在构造函数里面
public HeroNode(int no, String name){
super();
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;
}
@Override
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);
}
}
查找代码
// 二叉树-查找指定的节点
//要求: 1. 请编写前序查找,中序查找和后序查找的方法
// 2. 并分别使用三种查找方法,查找heroNOde5的节点
// 3. 并分析各种查找方式,分别比较了多少次
package Package01;
public class BinaryTree {
public static void main(String[] args) {
//需要创建一棵二叉树,首先要创建二叉树的节点
BinaryTreee binaryTreee = new BinaryTreee();
HeroNode root = new HeroNode(1,"宋江");
HeroNode node2 = new HeroNode(2,"吴用");
HeroNode node3 = new HeroNode(3,"卢俊义");
HeroNode node4 = new HeroNode(4,"林冲");
//我们先手动创建二叉树,将节点之间连接起来,后面再使用递归创建二叉树
root.setLeft(node2);
root.setRight(node3);
node3.setRight(node4);
binaryTreee.setRoot(root);
//测试遍历
System.out.println("++++++++++ 测试遍历 ++++++++++");
System.out.println("前序遍历:" );
binaryTreee.preOrder();
System.out.println("中序遍历:" );
binaryTreee.infixOrder();
System.out.println("后序遍历:" );
binaryTreee.postOrder();
//测试查找
System.out.println();
System.out.println("++++++++++ 测试查找 ++++++++++");
System.out.println("前序查找:");
HeroNode resNode = binaryTreee.preOrderSearch(4);
if(resNode != null){
System.out.printf("找到了,信息为no=%d name=%s",resNode.getNo(),resNode.getName());
}else{
System.out.printf("没有找到no = %d 的英雄",4);
}
}
}
//定义一个BinaryTree
class BinaryTreee{
public 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("二叉树为空,无法遍历");
}
}
//前序查找
/**
*
* @param no
* @return
*/
public HeroNode preOrderSearch(int no){
if(root != null){
return root.postOrderSearch(no);
}else{
return null;
}
}
//中序查找
/**
*
* @param no
* @return
*/
public HeroNode infixOrderSearch(int no){
if(root != null){
return root.infixOrderSearch(no);
}else{
return null;
}
}
//后序查找
/**
*
* @param no
* @return
*/
public HeroNode postOrsearSearch(int no){
if(root != null){
return root.infixOrderSearch(no);
}else{
return null;
}
}
}
//先创建HeroNode节点
class HeroNode{
private int no;
private String name;
private HeroNode left; //默认为nul,不需要写在构造函数里面
private HeroNode right; //默认为null,不需要写在构造函数里面
public HeroNode(int no, String name){
super();
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;
}
@Override
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);
}
//前序遍历查找
/**
*
* @param no 查找的no
* @return 如果找到就返回该Node,没有找到就返回null
*/
public HeroNode preOrderSearch(int no){
//比较当前节点是不是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;
}
//中序遍历
/**
*
* @param no 查找的no
* @return 返回查找的节点
*/
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;
}
//前序查找
/**
*
* @param no 需要找的节点
* @return
*/
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;
}
}
删除代码
-
考虑树如果是空树root,如果只有一个root节点,则等价于将二叉树置空。
-
(1). 因为我们的二叉树是单向的,所以我们需要判断当前节点的子节点是否需要删除节点。而不是考虑 删除当前节点。
-
(2). 如果当前节点的左子节点不为空,并且左子节点的编号就是需要删除的节点,this.left = null;并且就返回(结束递归删除)。
-
(3). 如果当前节点的右子节点不为空,并且右子节点的编号就是需要删除的节点,ths.right = null; 并且就返回(结束递归删除)。
-
(4). 如果第2和第3步没有删除节点,那么我们就需要向左子树进行递归删除
-
(5). 如果第4步没有删除节点,那么我们就需要向右子树进行递归删除
//完成删除操作
//规定:
// (1). 如果删除的节点是叶子节点,则删除该节点
// (2). 如果删除的是非叶子节点,则删除该子树
//思路:
// 考虑树如果是空树root,如果只有一个root节点,则等价于将二叉树置空。
// *** (1). 因为我们的二叉树是单向的,所以我们需要判断当前节点的子节点是否需要删除节点。而不是考虑
// 删除当前节点。
// (2). 如果当前节点的左子节点不为空,并且左子节点的编号就是需要删除的节点,this.left = null;
// 并且就返回(结束递归删除)。
// (3). 如果当前节点的右子节点不为空,并且右子节点的编号就是需要删除的节点,ths.right = null;
// 并且就返回(结束递归删除)。
// (4). 如果第2和第3步没有删除节点,那么我们就需要向左子树进行递归删除
// (5). 如果第4步没有删除节点,那么我们就需要向右子树进行递归删除
package Package01;
public class BinaryTree {
public static void main(String[] args) {
//先需要创建一棵二叉树
BinaryTreee binaryTreee = new BinaryTreee();
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);
binaryTreee.setRoot(root);
//测试遍历
System.out.println("++++++++++ 测试遍历 ++++++++++");
System.out.println();
System.out.println("前序遍历:" );
binaryTreee.preOrder();
System.out.println("中序遍历:" );
binaryTreee.infixOrder();
System.out.println("后序遍历:" );
binaryTreee.postOrder();
//测试查找
System.out.println();
System.out.println("++++++++++ 测试查找 ++++++++++");
System.out.println();
System.out.println("前序查找:");
HeroNode resNode = binaryTreee.preOrderSearch(4);
if(resNode != null){
System.out.printf("找到了,信息为no=%d name=%s",resNode.getNo(),resNode.getName());
}else{
System.out.printf("没有找到no = %d 的英雄",4);
}
//删除节点
System.out.println();
System.out.println();
System.out.println("++++++++++ 测试删除 ++++++++++");
System.out.println();
System.out.println("删除前,前序遍历:");
binaryTreee.preOrder();
System.out.println("删除后,前序遍历");
binaryTreee.delNode(5);
binaryTreee.preOrder();
}
}
//定义一个BinaryTree
class BinaryTreee{
public 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.postOrderSearch(no);
}else{
return null;
}
}
//中序查找
public HeroNode infixOrderSearch(int no){
if(root != null){
return root.infixOrderSearch(no);
}else{
return null;
}
}
//后序查找
public HeroNode postOrsearSearch(int no){
if(root != null){
return root.infixOrderSearch(no);
}else{
return null;
}
}
//**************************** 删除 **************************
//删除节点的代码不一样,判断树是不是只有root节点(root 是否 为需要删除的节点),判断root空不空都是在BinaryTreee里面进行的。
//删除节点的代码:root 是否 为需要删除的节点是放在BinaryTreee里面进行的。
//而查找代码: root 是否 是需要查找的节点是放在HeroNode类里面的
//在节点里面的delNode函数,是指已经进入节点在查找删除里面的情况了。
public void delNode(int no){
if(root != null){
if(root.getNo() == no){
root = null;
}else{
//递归删除
root.delNode(no);
}
}else{
System.out.println("这是一个空树,无法删除");
}
}
}
//先创建HeroNode节点
class HeroNode{
private int no;
private String name;
private HeroNode left; //默认为nul,不需要写在构造函数里面
private HeroNode right; //默认为null,不需要写在构造函数里面
public HeroNode(int no, String name){
super();
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;
}
@Override
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){
//比较当前节点是不是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;
}
//**************************** 删除 **************************
// *** (1). 因为我们的二叉树是单向的,所以我们需要判断当前节点的子节点是否需要删除节点。而不是考虑
// 删除当前节点。
// (2). 如果当前节点的左子节点不为空,并且左子节点的编号就是需要删除的节点,this.left = null;
// 并且就返回(结束递归删除)。
// (3). 如果当前节点的右子节点不为空,并且右子节点的编号就是需要删除的节点,ths.right = null;
// 并且就返回(结束递归删除)。
// (4). 如果第2和第3步没有删除节点,那么我们就需要向左子树进行递归删除
// (5). 如果第4步没有删除节点,那么我们就需要向右子树进行递归删除
public void delNode(int no){
// 思路:
// (2)如果当前节点的左子节点不为空,并且左子节点的编号就是需要删除的节点,this.left = null;
// 并且就返回(结束递归删除)。
if(this.left != null && this.left.no == no){
this.left = null;
return;
}
// (3). 如果当前节点的右子节点不为空,并且右子节点的编号就是需要删除的节点,ths.right = null;
// 并且就返回(结束递归删除)。
if(this.right != null && this.right.no==no){
this.right = null;
return;
}
// (4). 如果第2和第3步没有删除节点,那么我们就需要向左子树进行递归删除
if(this.left != null){
this.left.delNode(no);
}
// (5). 如果第4步没有删除节点,那么我们就需要向右子树进行递归删除
if(this.right != null){
this.right.delNode(no);
}
}
}
存储二叉树
顺序存储:
- (1). 顺序二叉树通常只考虑完全二叉树
- (2). 第n个元素的左子节点为 2 * n + 1
- (3). 第n个元素的右子节点为 2 * n + 2
- (4). 第n个元素的父亲节点为 (n-1) / 2
//顺序存储二叉树 ------> 数组 [0,1,2,3,4,......,n]
//(1). 顺序二叉树通常只考虑完全二叉树
//(2). 第n个元素的左子节点为 2*n + 1
//(3). 第n个元素的右子节点为 2*n + 2
//(4). 第n个元素的父亲节点为 (n-1)/2
//需求:给你一个数组 {1,2,3,4,5,6,7},要求以二叉树前序遍历的方式进行遍历。
//前序遍历的结果应当为: 1,2,4,5,3,6,7
package Package01;
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(); // 1,2,4,5,3,6,7
}
}
//编写一个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 + 1 < arr.length){
preOrder(2 * index +2);
}
}
}
1.4 线索化二叉树
- n 个节点的二叉链表中含有 n+1 [公式:2n - (n-1) = n+1] 个空指针域。利用二叉链表的空指针域,存放指向节点在某种遍历次序下的前驱和后继节点的指针 (这种附加的指针称为“线索”)。
- 加上这种线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可以分为前序线索二叉树、中序线索二叉树、后续线索二叉树三种。
- 一个结点的前一个节点,称为前驱节点。
- 一个结点的后一个节点,称为后继节点。
2.堆排序
- 将待排序列构造成一个大顶堆 {大顶堆是通过树结构的方式构建一个数组}
- 此时,整个序列的最大值就是堆顶的根节点
- 将其与末尾元素进行交换,此时末尾就为最大值 {将最大值放在末尾,前面的
n-1
个为次小值} - 将剩余
n-1
个元素重新构造成一个堆,这样会得到n
个元素的次小值。如此反复执行,便可以得到一个有序序列了。
3.赫夫曼树
-
给定n个权值作为n个叶子节点,构造一个二叉树,如果该数的带权路经长度(wql)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree),还有的书翻译为霍夫曼树。
-
赫夫曼树是带权路径长度最短的树,权值较大的节点离根较近。
-
赫夫曼树的构建步骤:
- 1.从小到大进行排序,将每一个数据,每一个数据可以看成每一个节点,每一个节点都是一颗最简单的二叉树
- 2.取出根节点权值最下的两颗二叉树
- 3.组成一颗新的二叉树,该二叉树的根节点的权值是前面两颗二叉树根节点权值的和
- 4.再将这颗新的二叉树,以根节点的权值大小再次排序,不断重复1-2-3-4步骤,直到数列中,所有的数据都被处理,就得到一个赫夫曼树。
-
赫夫曼编码基本介绍:
- 赫夫曼编码也翻译为霍夫曼编码,是一种编码方式,属于一种程序算法
- 赫夫曼编码是赫夫曼树在通信中的经典应用
- 赫夫曼编码广泛的用于数据文件的压缩。其压缩率通常在20%-90%之间
- 赫夫曼码是可变字长编码的一种