数据结构-树
1、树的定义
一棵树是由N(N >= 0)个节点构成的有限集合以及在该集合上定义的一种节点关系。其中:
-
集合中的每个元素被称为树的节点
-
最顶层的节点称为根节点
-
除根节点外,其余节点是互不相交的有限集合,这些集合也是一棵树,由于这些集合都在根节点的下面,因此,称为这颗树的子树
-
除根节点外,其余节点都有一个前驱节点和零个或多个后继结点
-
N个节点的树有N-1条边(树枝)
-
每一个节点的子树个数称为节点的度,树中各个节点的度的最大值为这棵树的度。节点的度和树的度共同提现了树的宽度
-
度数为0的节点称为叶节点(终端节点),度数不为0的节点称为分支节点(非终端节点)。根节点之外的分支节点称为内部节点
-
用线段连接的多个节点,上层的节点称为下层节点的父节点,下层节点称为上层节点的子节点,同一个父节点的多个子节点称为兄弟节点。从根节点到某个子节点经过的所有节点为这个子节点的祖先,以某个节点为根的子树中的任意节点为该节点的子孙
-
树的根节点层次为1,其余节点的层次为父节点的层次+1,一棵树中所有节点的层次的最大值为树的高度(深度)
-
对于树中任意两个不同的节点,果从一个节点出发,自上而下君着树中连着节点的线段能到达另一节点,称它们之间存在着一条路径。路径长度等于路径上的节点个数-1。
2、二叉树
2.1 二叉树的定义
二叉树(binary tree)是指树中节点的度不大于2的有序树。也就是每个节点最多有两个子节点。
2.2 二叉查找树
二叉查找树(Binary Search Tree)也称二叉搜索树、二叉排序树。它要么是一棵空树,要么是具有下列性质的二叉树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
2.2.1 比较器
二叉树每个节点中存储的数据会根据不同的需求而不同,那么针对这些不同的数据,如何来进行大小比较呢?这里就需要使用到比较器,Java 提供了两个比较器接口 Comparable和Comparator。
Comparable: 自然排序接口
Comparator:外排序接口
如果我们要排序的数据根据实际的应用场景需要进行不同规则的排序,那么我们应该使用Comparator接口,否则,应该使用Comparable接口
案例:
录入5位学生信息(姓名和成绩),将其存在数组中,然后按照学生的成绩降序排列
分析:这里只要求按照成绩排列,是固定的排列方式,因此我们考虑使用Comparable接口
public class Student implements Comparable<Student>{
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public int compareTo(Student o) {//比较大小的具体实现
return score - o.score;
}
@Override
public String toString() {
return name + ", " + score;
}
}
import java.util.Arrays;
import java.util.Scanner;
public class CompareTest {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Student[] students = new Student[5];
for(int i=0; i<students.length; i++){
System.out.printf("请输入第%d位学生姓名:\n", i+1);
String name = sc.next();
System.out.printf("请输入第%d位学生成绩:\n", i+1);
int score = sc.nextInt();
students[i] = new Student(name, score);
}
System.out.println("===========排序前===========");
for(Student stu: students){
System.out.println(stu);
}
Arrays.sort(students);
System.out.println("===========排序后===========");
for(Student stu: students){
System.out.println(stu);
}
}
}
如果现在要求将录入的学生信息根据用户的选择来进行排序,用户可以选择姓名升序排列和成绩降序排列。那么此时我们就不能再使用Comparable接口了,因为这个接口无法满足我们的需求。我们只能考虑使用Comparator接口
public class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return name + ", " + score;
}
}
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
public class CompareTest {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Student[] students = new Student[5];
for(int i=0; i<students.length; i++){
System.out.printf("请输入第%d位学生姓名:\n", i+1);
String name = sc.next();
System.out.printf("请输入第%d位学生成绩:\n", i+1);
int score = sc.nextInt();
students[i] = new Student(name, score);
}
System.out.println("请选择排序规则:1.按姓名升序,2.按成绩降序");
int num = sc.nextInt();
Comparator<Student> comp = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
if(num == 1 ){
return o1.getName().compareTo(o2.getName());
} else {
return o1.getScore() - o2.getScore();
}
}
};
System.out.println("===========排序前===========");
for(Student stu: students){
System.out.println(stu);
}
Arrays.sort(students, comp);
System.out.println("===========排序后===========");
for(Student stu: students){
System.out.println(stu);
}
}
}
2.2.2 二叉查找树实现
/**
* 树的节点
* @param <T> 节点存储的数据类型
*/
public class Node<T> {
Node<T> parent;
Node<T> left; //左节点
Node<T> right; //右节点
T data; //数据
public Node(Node<T> parent, T data){
this.parent = parent;
this.data = data;
}
}
import java.util.Comparator;
/**
* 二叉查找树:
* 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
* 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
* 任意节点的左、右子树也分别为二叉查找树;
* 没有键值相等的节点。
*
* BinarySearchTree 也就是通常说的BST
*/
public class BinarySearchTree<T> {
private Node<T> root; //根节点
private Comparator<T> comparator;
public BinarySearchTree(){
}
public BinarySearchTree(Comparator<T> comparator){
this.comparator = comparator;
}
public void insertNode(T data){//插入一个节点
if(root == null){//根节点为空,说明是第一次插入
root = new Node<>(null, data); //第一次插入时的节点就是根节点
return;
}
Node<T> node = root;
Node<T> parent = node;
int cmp = 0;
while (node != null){
parent = node;
if(comparator != null){
cmp = comparator.compare(data, node.data);
} else {
cmp = ((Comparable)data).compareTo(node.data);//存储数据比较
}
if(cmp > 0){//右子树
node = node.right;
} else if(cmp < 0){//左子树
node = node.left;
} else {
node.data = data;
return;
}
}
if(cmp > 0){
parent.right = new Node<>(parent, data);
} else {
parent.left = new Node<>(parent, data);
}
}
public int getHeight(){
if(root == null){
return 0;
}
return root.getHeight() + 1;
}
}
2.2.3 二叉树遍历
二叉树遍历分为前序遍历、中序遍历和后序遍历
前序遍历:访问根结点的操作发生在遍历其左右子树之前
中序遍历:访问根结点的操作发生在遍历其左右子树之间
后序遍历:访问根结点的操作发生在遍历其左右子树之后
Node.java
/**
* 前序遍历:访问根结点的操作发生在遍历其左右子树之前
* 树的遍历都是采用递归实现
*/
public void preOrder() {
System.out.print(this.data + "\t"); //先输出父结点
//递归向左子树前序遍历
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.print(this.data + "\t");
//递归向右子树中序遍历
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.data + "\t");
}
BinarySearchTree.java
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();
}
2.2.4 二叉树查找
Node.java
public Node<T> preFind(T search){
if(this.data.equals(search)) return this;
Node<T> result = null;
//递归向左子树前序遍历
if (this.left != null) {
result = this.left.preFind(search);
}
if(result != null) return result;
//递归向右子树前序遍历
if (this.right != null) {
result = this.right.preFind(search);
}
return result;
}
public Node<T> infixFind(T search){
//递归向左子树中序遍历
Node<T> result = null;
//递归向左子树中序遍历
if (this.left != null) {
result = this.left.preFind(search);
}
if(result != null) return result;
if(this.data.equals(search)) return this;
//递归向右子树中序遍历
if (this.right != null) {
result = this.right.preFind(search);
}
return result;
}
public Node<T> postFind(T search){
//递归向左子树后序遍历
Node<T> result = null;
//递归向左子树中序遍历
if (this.left != null) {
result = this.left.preFind(search);
}
if(result != null) return result;
if(this.data.equals(search)) return this;
//递归向右子树中序遍历
if (this.right != null) {
result = this.right.preFind(search);
}
if(this.data.equals(search)) return this;
return result;
}
BinarySearchTree.java
public Node<T> preFind(T search){
return this.root.preFind(search);
}
public Node<T> infixFind(T search){
return this.root.infixFind(search);
}
public Node<T> postFind(T search){
return this.root.postFind(search);
}
2.2.5 二叉树删除
BinarySearchTree.java
public boolean removeNode(T data){
//1.树为空,直接return false
if(root == null) return false;
//2.先循环找到要删除的元素
Node<T> removeNode = root; //要移除的节点,从根节点开始找
while(removeNode != null){
int cmp;
if(comparator == null){
cmp = ((Comparable<T>)removeNode.data).compareTo(data);
} else {
cmp = comparator.compare(removeNode.data, data);
}
if(cmp < 0){//删除的节点存储的元素比当前节点存储的元素值还大,那么就应该在右子树找
removeNode = removeNode.right;
}else if(cmp> 0){//删除的节点存储的元素比当前节点存储的元素值还小,那么就应该在左子树找
removeNode = removeNode.left;
}else{
//找到了,删除节点
removeNode(removeNode);
return true;
}
}
return false;//没找到该节点
}
private void removeNode(Node<T> tmp){
Node<T> parent = tmp.parent;
//分情况
//1.删节点的左子树为null
if(tmp.left == null){
if(parent == null){//只有根节点的父节点为空
root = tmp.right;
} else {//其余情况,只需要将删除节点的右子树赋给删除节点的父节点的右子树即可
parent.right = tmp.right;
}
//2.删节点的右子树为null
}else if(tmp.right == null){
//2.1删除节点为根节点
if(parent == null){//只有根节点的父节点为空
root = tmp.left;
}else {//其余情况,只需要将删除节点的左子树赋给删除节点的父节点的左子树即可
parent.left = tmp.left;
}
//3.删除节点的左右子树都不为null,我使用被删节点的右子树中找最小值
}else{
//找左子树中最大的节点
Node<T> maxNode = tmp;
while (true){
if(maxNode.right == null) break;
maxNode = maxNode.right;
}
Node<T> maxNodeParent = maxNode.parent;//最大节点的父节点
maxNodeParent.right = null;//父节点的右子树设置为空,实现删除
tmp.data = maxNode.data;
}
}