树
树是一种非常常见且非常重要的数据结构。与线性数据结构不同,树是非线性数据结构,因而也比链表、数组等要复杂。但是学好树也不难,学不好的话,那就只能找棵树自挂东南枝了。
树的定义:树是由结点或顶点和边组成的(可能是非线性的)且不存在着任何环的一种数据结构。没有结点的树称为空(null或empty)树。一棵非空的树包括一个根结点,还(很可能)有多个附加结点,所有结点构成一个多级分层结构。
1. 二叉树
1.1定义
二叉树(Binary Tree)是n(n>=0)个节点的有限集合,该集合或者空集(称为空二叉树),或者由一个根节点和两棵互不相交的,分别称为根节点的左子树和右子树的二叉树组成。
满二叉树除最后一层无任何子节点外,每一层上的所有结点都有两个子结点。也可以这样理解,除叶子结点外的所有结点均有两个子结点。节点数达到最大值,所有叶子结点必须在同一层上。
完全二叉树 若设二叉树的深度为h,除第 h 层外,其它各层 (1~(h-1)层) 的结点数都达到最大个数,第h层所有的结点都连续集中在最左边,这就是完全二叉树。
1.2 性质
-
二叉树性质
- 在非空二叉树中,第i层的结点总数不超过2i-1, i>=1;
- 深度为h的二叉树最多有2h-1个结点(h>=1),最少有h个结点;
- 对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;
- 具有n个结点的完全二叉树的深度为log2(n+1);
- 有N个结点的完全二叉树各结点如果用顺序方式存储,则结点之间有如下关系:
若I为结点编号则 如果I>1,则其父结点的编号为I/2;
如果2I<=N,则其左儿子(即左子树的根结点)的编号为2I;若2I>N,则无左儿子;
如果2I+1<=N,则其右儿子的结点编号为2I+1;若2I+1>N,则无右儿子。 - 给定N个节点,能构成h(N)种不同的二叉树,其中h(N)为卡特兰数的第N项,h(n)=C(2*n, n)/(n+1)。
- 设有i个枝点,I为所有枝点的道路长度总和,J为叶的道路长度总和J=I+2i。
-
满二叉树的性质:
- 一颗树深度为h,最大层数为k,深度与最大层数相同,k=h;
- 叶子数为2h;
- 第k层的结点数是:2k-1;
- 总结点数是:2k-1,且总节点数一定是奇数
-
完全二叉树
- 完全二叉树是效率很高的数据结构,堆是一种完全二叉树或者近似完全二叉树,所以效率极高,像十分常用的排序算法、Dijkstra算法、Prim算法等都要用堆才能优化,二叉排序树的效率也要借助平衡性来提高,而平衡性基于完全二叉树。
2. 二叉查找树
2.1 定义
又称为是二叉排序树(Binary Sort Tree)或二叉搜索树。可能是一棵空树,或者是具有下列性质的二叉树:
1) 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2) 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
3) 左、右子树也分别为二叉排序树;
4) 没有键值相等的节点。
2.2 性质
- 对二叉查找树进行中序遍历,即可得到有序的数列。
这里补充一下二叉树的遍历顺序,首先看图:- 前序遍历:规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。如图所示,遍历的顺序为:ABDECF。
- 中序遍历:规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。如图所示,遍历的顺序为:DBEAFC。
- 后序遍历:规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访向左右子树,最后是访问根结点。如图所示,遍历的顺序为:DEBFCA。
- 二叉查找树的时间复杂度:它和二分查找一样,插入和查找的时间复杂度均为O(logn),但是在最坏的情况下仍然会有O(n)的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。我们追求的是在最坏的情况下仍然有较好的时间复杂度,这就是平衡查找树设计的初衷。
- 二叉查找树的高度决定了二叉查找树的查找效率。
2.3二叉查找树的插入过程:
- 若当前的二叉查找树为空,则插入的元素为根节点;
- 若插入的元素值小于根节点值,则将元素插入到左子树中;
- 若插入的元素值不小于根节点值,则将元素插入到右子树中。
2.4 二叉查找树的删除
2.4.1 删除叶子节点
p为叶子节点,直接删除该节点,再修改其父节点的指针(注意分是根节点和不是根节点)
删除叶子节点25,则
2.4.2 删除单支节点
p为单支节点(即只有左子树或右子树)。让p的子树与p的父亲节点相连,删除p即可(注意分是根节点和不是根节点)
删除上图的值为16节点
2.4.3 节点完整的点
删除有左右子树的节点,有两个办法,即用左子树的最大节点取代待删除节点,或者用最小右子树的节点取代待删除节点。
- 用左子树的最大节点取代待删除节点
- 用右子树的最小节点取代待删除节点
那就是用36取代33。即
这两种方法的所产生的二叉树形态看起来似乎不一样,那么为什么有这两种结果呢?其实这只有一种结果,判断其是否正确的依据是删除该节点之后,二叉树的中序遍历结果是否保持相对一致。在删除之前,二叉搜索树的中序遍历结果是
1 -> 3 -> 5 -> 10 -> 12 -> 15 -> 17 -> 28-> 33-> 36-> 38
当我们删除33节点时,中序遍历的结果就是28后面是36。对于这两个方法产生的中序遍历结果,都是这样的。
3.代码实现
alk is cheap, show me the code.
Node.java
public class Node<T extends Comparable> {
public T val;
public Node left;
public Node right;
public Node(T val){
this.val = val;
}
@Override
public String toString() {
return "Node{" +
"val=" + val +
'}';
}
}
BInarySearchTree.java
public class BinarySearchTree {
//二叉搜索树根节点
public static Node root;
public BinarySearchTree() {
this.root = null;
}
/**
* 查找
* * 树深(N) O(lgN)
* * 1. 从root节点开始
* * 2. 比当前节点值小,则找其左节点
* * 3. 比当前节点值大,则找其右节点
* * 4. 与当前节点值相等,查找到返回TRUE
* * 5. 查找完毕未找到, * *
*
* @param key * @return
*/
public Node search(Node key) {
Node current = root;
int res = 0;
while (current != null && ((res
= key.val.compareTo(current.val)) != 0)) {
if (res < 0) current = current.left;
else current = current.right;
}
return current;
}
/**
* 插入节点
* @param key
* @return
*/
public Node insert(Node key) {
//当前节点
Node current = root;
//上个节点
Node parent = null;
//如果根节点为空,那么插入的节点为跟节点
if (current == null) {
root = key;
return key;
}
while (true) {
parent = current;
int res = key.val.compareTo(current.val);
if (res < 0) {
current = current.left;
if (current == null) {
parent.left = key;
return key;
}
} else {
current = current.right;
if (current == null) {
parent.right = key;
return key;
}
}
}
}
/**
* 1.找到删除节点
* 2.如果删除节点左节点为空 , 右节点也为空;
* 3.如果删除节点只有一个子节点 右节点 或者 左节点
* 4.如果删除节点左右子节点都不为空
* @param key
* @return
*/
public Node delete(Node key) {
Node parent = root;
Node current = root;
boolean isLeft = false;
//找到待删除节点,以及是否是左子树
while (!key.val.equals(current.val)) {
parent = current;
int res = key.val.compareTo(current.val);
if (res < 0) {
isLeft = true;
current = current.left;
} else {
isLeft = false;
current = current.right;
}
if (current == null) return null;
} //如果刪除的左右节点都为空
if (current.left == null && current.right == null) {
if (current == root)
root = null;
//该节点在左子树
if (isLeft) parent.left = null;
else parent.right = null;
} else if (current.left == null) {
// 该节点为右子树
if (current == root) root = current.right;
else if (isLeft) {
parent.left = current.right;
} else {
parent.right = current.right;
}
} else if (current.right == null) {
if (current == root) root = current.left;
else if (isLeft) parent.right = current.left;
else parent.left = current.left;
} else if (current.right != null && current.left != null) {
Node successor = getDeleteSuccessor(current);
if (current == root) {
root = successor;
} else if (isLeft) {
parent.left = successor;
} else {
parent.right = successor;
}
successor.left = current.left;
}
return current;
}
private Node getDeleteSuccessor(Node deleteNode) {
Node successor = null;
Node successorParent = null;
Node current = deleteNode.right;
while (current != null) {
successorParent = successor;
successor = current;
current = current.left;
}
// 检查后继者(不可能有左节点树)是否有右节点树
// 如果它有右节点树,则替换后继者位置,加到后继者父亲节点的左节点.
if (!successor.equals(deleteNode.right)) {
successorParent.left = successor.right;
successor.right = deleteNode.right;
}
return successor;
}
public void toString(Node root) {
if (root != null) {
toString(root.left);
System.out.print("value = " + root.val + " -> ");
toString(root.right);
}
}
}
测试类,首先是
Person.java
public class Person implements Comparable<Person> {
public int age;
public String name;
public Person() {
}
public Person(int age,String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age;
}
@Override
public int hashCode() {
return Objects.hash(age);
}
@Override
public int compareTo(Person o) {
return this.age - o.age;
}
}
BSTTest.java
public class BSTTest {
public static void main(String[] args) {
BinarySearchTree b = new BinarySearchTree();
b.insert(new Node(new Person(3, "zhangsan")));
b.insert(new Node(new Person(8, "lisi")));
b.insert(new Node(new Person(1, "wangwu")));
b.insert(new Node(new Person(4, "zhaoer")));
b.insert(new Node(new Person(6, "qianba")));
b.insert(new Node(new Person(2, "sunjiu")));
b.insert(new Node(new Person(10, "guyi")));
b.insert(new Node(new Person(9, "chenqi")));
b.insert(new Node(new Person(20, "wuda")));
b.insert(new Node(new Person(25, "qiao")));
// 打印二叉树
b.toString(b.root);
System.out.println();
// 是否存在节点值10
Node node01 = b.search(new Node(new Person(10, "guyi")));
System.out.println("是否存在节点值为10 => " + node01.val);
// 是否存在节点值11
Node node02 = b.search(new Node(new Person(11, "ergou")));
System.out.println("是否存在节点值为11 => " + node02);
// 删除节点8
Node node03 = b.delete(new Node(new Person(8, "lisi")));
//Node node03 = b.delete1(b.root, new Node(new Person(8,"lisi")));
System.out.println("删除节点8 => " + node03.val);
b.toString(b.root);
}
}
运行结果如图