二叉查找树(转载)
原文地址:
http://www.cnblogs.com/fingerboy/p/5493786.html
http://www.cnblogs.com/gaochundong/p/binary_search_tree.html
树(Tree)是由多个节点(Node)的集合组成,每个节点又有多个与其关联的子节点(Child Node)。子节点就是直接处于节点之下的节点,而父节点(Parent Node)则位于节点直接关联的上方。树的根(Root)指的是一个没有父节点的单独的节点。
所有的树都呈现了一些共有的性质:
-
只有一个根节点;
-
除了根节点,所有节点都有且只有一个父节点;
-
无环。将任意一个节点作为起始节点,都不存在任何回到该起始节点的路径。(正是前两个性质保证了无环的成立。)
树中使用的术语
-
根(Root):树中最顶端的节点,根没有父节点。
-
子节点(Child):节点所拥有子树的根节点称为该节点的子节点。
-
父节点(Parent):如果节点拥有子节点,则该节点为子节点的父节点。
-
兄弟节点(Sibling):与节点拥有相同父节点的节点。
-
子孙节点(Descendant):节点向下路径上可达的节点。
-
叶节点(Leaf):没有子节点的节点。
-
内节点(Internal Node):至少有一个子节点的节点。
-
度(Degree):节点拥有子树的数量。
-
边(Edge):两个节点中间的链接。
-
路径(Path):从节点到子孙节点过程中的边和节点所组成的序列。
-
层级(Level):根为 Level 0 层,根的子节点为 Level 1 层,以此类推。
-
高度(Height)/深度(Depth):树中层的数量。比如只有 Level 0,Level 1,Level 2 则高度为 3。
类别 |
树名称 |
二叉查找树,笛卡尔树,T 树 |
|
AA 树,AVL 树, 伸展树(Splay Tree) |
|
B 树 (B-Tree) |
B 树,B+ 树,B* 树 |
字典树 (Trie-Tree) |
后缀树,基数树,三叉查找树,快速前缀树 |
空间数据分割树 (Spatial Data Partitioning Tree) |
R 树,R+ 树,R* 树, 线段树,优先 R 树 |
二叉树(Binary Tree)是一种特殊的树类型,其每个节点最多只能有两个子节点。这两个子节点分别称为当前节点的左孩子(left child)和右孩子(right child)。
完全二叉树(Complete Binary Tree):深度为 h,有 n 个节点的二叉树,当且仅当其每一个节点都与深度为 h 的满二叉树中,序号为 1 至 n 的节点对应时,称之为完全二叉树。
满二叉树(Full Binary Tree):一棵深度为 h,且有 2h - 1 个节点称之为满二叉树。
A complete binary tree is a binary tree in which every level, except possibly the last, is completely filled, and all nodes are as far left as possible.
A full binary tree is a tree in which every node other than the leaves has two children.
二叉查找树(BST:Binary Search Tree)是一种特殊的二叉树,它改善了二叉树节点查找的效率。二叉查找树有以下性质:
对于任意一个节点 n,
-
其左子树(left subtree)下的每个后代节点(descendant node)的值都小于节点 n 的值;
-
其右子树(right subtree)下的每个后代节点的值都大于节点 n 的值。
二叉查找树的新增,查找,删除操作的时间复杂度,最好的情况都是O(log2n),最坏的情况下为O(n)。这取决于插入数据的顺序。下图中显示了 O(log2n) 和线性增长 O(n) 的增长率之间的区别。时间复杂度为 O(log2n) 的算法运行时间为下面那条线。
从上图可以看出,O(log2n) 曲线几乎是水平的,随着 n 值的增加,曲线增长十分缓慢。举例来说,查找一个具有 1000 个元素的数组,需要查询 1000 个元素,而查找一个具有 1000 个元素的 BST 树,仅需查询不到10 个节点(log21024 = 10)。
从 BST 中删除节点比插入节点难度更大。因为删除一个非叶子节点,就必须选择其他节点来填补因删除节点所造成的树的断裂。如果不选择节点来填补这个断裂,那么就违背了 BST 的性质要求。
删除节点算法的第一步是定位要被删除的节点,这可以使用前面介绍的查找算法,因此运行时间为 O(log2n)。接着应该选择合适的节点来代替删除节点的位置,它共有三种情况需要考虑。
-
情况 1:如果删除的节点没有右孩子,那么就选择它的左孩子来代替原来的节点。二叉查找树的性质保证了被删除节点的左子树必然符合二叉查找树的性质。因此左子树的值要么都大于,要么都小于被删除节点的父节点的值,这取决于被删除节点是左孩子还是右孩子。因此用被删除节点的左子树来替代被删除节点,是完全符合二叉搜索树的性质的。
-
情况 2:如果被删除节点的右孩子没有左孩子,那么这个右孩子被用来替换被删除节点。因为被删除节点的右孩子都大于被删除节点左子树的所有节点,同时也大于或小于被删除节点的父节点,这同样取决于被删除节点是左孩子还是右孩子。因此,用右孩子来替换被删除节点,符合二叉查找树的性质。
-
情况 3:如果被删除节点的右孩子有左孩子,就需要用被删除节点右孩子的左子树中的最下面的节点(右子树的最小值)来替换它,就是说,我们用被删除节点的右子树中最小值的节点来替换。
对于线性的连续的数组来说,遍历数组采用的是单向的迭代法。从第一个元素开始,依次向后迭代每个元素。而 BST 则有三种常用的遍历方式:
-
前序遍历(Perorder traversal)
-
中序遍历(Inorder traversal)
-
后序遍历(Postorder traversal)
-
package com.wang.tree;
-
-
public class BinarySearchTree<T extends Comparable<T>>{
-
-
-
private static class Node<T>{
-
private T data;
-
private Node<T> left;
-
private Node<T> right;
-
-
public Node(T data){
-
this(data,null,null);
-
}
-
public Node(T data, Node<T> left, Node<T> right) {
-
this.data = data;
-
this.left = left;
-
this.right = right;
-
}
-
}
-
-
//私有变量 根节点root
-
private Node<T> root;
-
-
//无参构造函数,根节点为null
-
public BinarySearchTree(){
-
root=null;
-
}
-
-
//清空二叉查找树
-
public void makeEmpty(){
-
root=null;
-
}
-
//判断树是否为空
-
public boolean isEmpty(){
-
-
return root==null;
-
}
-
//查找集合中是否有元素value,有返回true
-
public boolean contains(T value){
-
-
return contains(value,root);
-
}
-
-
private boolean contains(T value, Node<T> t) {
-
-
if(t==null){
-
return false;
-
}
-
int result=value.compareTo(t.data);
-
if(result<0){
-
return contains(value,t.left);
-
}else if(result>0){
-
return contains(value,t.right);
-
}else{
-
return true;
-
}
-
}
-
-
//查找集合中的最小值
-
public T findMin(){
-
-
return findMin(root).data;
-
}
-
private Node<T> findMin(Node<T> t) {
-
if(t==null){
-
return null;
-
}else if(t.left==null){
-
return t;
-
}
-
-
-
return findMin(t.left);
-
}
-
-
//查找集合中的最大值
-
public T findMax(){
-
-
return findMax(root).data;
-
}
-
-
private Node<T> findMax(Node<T> t) {
-
if(t!=null){
-
while(t.right!=null){
-
t=t.right;
-
}
-
}
-
-
return t;
-
}
-
-
//插入元素
-
public void insert(T value){
-
-
root =insert(value,root);
-
}
-
-
private Node<T> insert(T value, Node<T> t) {
-
if(t==null){
-
return new Node(value,null,null);
-
}
-
int result=value.compareTo(t.data);
-
if(result<0){
-
t.left=insert(value,t.left);
-
}else if(result>0){
-
t.right=insert(value,t.right);
-
}
-
return t;
-
}
-
//移除元素
-
public void remove(T value){
-
root=remove(value,root);
-
-
-
}
-
-
private Node<T> remove(T value, Node<T> t) {
-
if(t==null){
-
return t;
-
}
-
-
int result=value.compareTo(t.data);
-
if(result<0){
-
t.left=remove(value,t.left);
-
}else if(result>0){
-
t.right=remove(value,t.right);
-
}else if(t.left!=null&&t.right!=null){//如果被删除节点有两个儿子
-
//1.当前节点值被其右子树的最小值代替
-
t.data=findMin(t.right).data;
-
//将右子树的最小值删除
-
t.right=remove(t.data, t.right);
-
}else{
-
//如果被删除节点是一个叶子 或只有一个儿子
-
t=(t.left!=null)?t.left:t.right;
-
}
-
-
return t;
-
}
-
-
//中序遍历打印
-
public void printTree(){
-
printTree(root);
-
}
-
-
private void printTree(Node<T> t) {
-
-
if(t!=null){
-
printTree(t.left);
-
System.out.println(t.data);
-
printTree(t.right);
-
}
-
}
-
}