您的位置:首页 > 其它

树、二叉树及二叉查找树

2016-05-30 16:21 120 查看



树状图是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

每个节点有零个或多个子节点;没有父节点的节点称为根节点;每一个非根节点有且只有一个父节点;除了根节点外,每个子节点可以分为多个不相交的子树。

(以下实现代码均在git)

1. 概念和基本术语

1.1树的定义

n (n >= 0) 个结点的有限集。如果 n = 0,称为空树;如果 n > 0,则

1)
有且仅有一个特定的称为根(Root)的结点,根只有直接后继,没有直接前驱;

2)
当n > 1,除根外其它结点划分为 m (m >0) 个互不相交的有限集T1, T2 ,…, Tm,其中每个集合本身又是一棵树,称为根的子树(SubTree)。

1.2相关术语

树的结点:包含一个数据元素及若干指向子树的分支;

孩子结点:结点的子树的根称为该结点的孩子;

双亲结点:B 结点是A 结点的孩子,则A结点是B 结点的双亲;

兄弟结点:同一双亲的孩子结点; 堂兄结点:同一层上结点;

祖先结点: 从根到该结点的所经分支上的所有结点子孙结点:以某结点为根的子树中任一结点都称为该结点的子孙

结点层:根结点的层定义为1;根的孩子为第二层结点,依此类推;

树的深度:树中最大的结点层

结点的度:结点子树的个数

树的度: 树中最大的结点度。

叶子结点:也叫终端结点,是度为 0 的结点;

分枝结点:度不为0的结点;

有序树:子树有序的树,如:家族树;

无序树:不考虑子树的顺序;

1.3树的其它三种表示法





2.二叉树

定义:一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根结点加上两棵分别称为左子树和右子树的、互不相交的二叉树组成。

特点:每个结点至多只有两棵子树(二叉树中不存在度大于2的结点)

五种形态



性质:(见二叉树的五大性质及证明

3. 二叉查找树

二叉树的一个重要的应用是它们在查找中的使用。假设树中的每个结点存储一项数据。使二叉树成为二叉查找树的性质是,对于树中的每个节点X,它的左子树中所有项的值小于X中的值,而它的右子树中所有项的值大于X中的值。

3.1 二叉查找树的数据结构

public class BinarySearchTree<AnyType extends Comparable<? super AnyType>> {
//树的节点
private static class Node<AnyType>{
AnyType element;     //节点数据
Node<AnyType> left;  //左孩子
Node<AnyType> right; //左孩子

Node(AnyType element){
this(element, null, null);
}

Node(AnyType element, Node<AnyType> lt, Node<AnyType> rt){
this.element = element;
left = lt;
right = rt;
}
}

private Node<AnyType> root;  //根节点

public BinarySearchTree(){
root = null;
}

public void makeEmpty(){
root = null;
}
public boolean isEmpty(){
return root == null;
}
}


3.2二叉查找树的元素查找

通过二叉查找树有序的性质,能有效节省查找时的效率,在平均情况下查找的时间复杂度为O(logn),但在最坏的情况下(此时二叉查找树退化成链)查找的时间复杂度会达到O(n)。

/**
* 查找元素的内部方法,递归
* @param x  要查找的元素
* @param t  子树的根节点
* @return   是否查找成功
*/
private boolean contains(AnyType x, Node<AnyType> t){
if(t == null)
return false;

int compareResult = x.compareTo(t.element);

if(compareResult < 0)
return contains(x, t.left);
else if(compareResult > 0)
return contains(x, t.right);
else
return true;    //匹配
}


3.3 二叉查找树的最大/最小值查找

在二叉查找树的性质中可知,对于树中的每个节点X,它的左子树中所有项的值小于X中的值而它的右子树中所有项的值大于X中的值。这样使用递归的方法可很简单地实现最大/最小值的查找。(以下对于最大值和最小值分别使用递归和非递归的形式实现)

/**
* 查找树中最小元素,递归
* @param t
* @return
*/
private Node<AnyType> findMin(Node<AnyType> t){
if(t == null)
return null;
else if(t.left == null){
return t;
}
return findMin(t.left);
}
/**
* 查找树中最大元素,非递归实现
* @param t
* @return
*/
private Node<AnyType> findMax(Node<AnyType> t){
if(t != null){
while(t.right != null){
t = t.right;
}
}
return t;
}


3.4 二叉查找树节点的插入

二叉查找树插入的实现与查找相似,即找到对应位置在执行插入操作。

/**
* 将元素插入到二叉查找树
* @param x  要查找的元素
* @param t  子树的根节点
* @return   插入后的树的根节点
*/
private Node<AnyType> insert(AnyType x, Node<AnyType> t) {
if(t == null)
return new Node<>(x, null, null);   //递归最后找到了属于自己的位置

int compareResult = x.compareTo(t.element);
if(compareResult < 0)
t.left = insert(x, t.left);
else if(compareResult > 0)
t.right = insert(x, t.right);
else
;    //什么都不做

return t;
}


3.5 二叉查找树节点的删除

二叉查找树节点的删除有两种情况:

1.被删除的节点只有一个儿子,则该结点可以在其父节点调整自己的链后删除;(若该节点是树叶的情况等同,此时赋值的值为null)

2.被删除的节点只有两个儿子,用其右子树的最小的数据代替该节点的数据,并递归第删除那个节点。

private Node<AnyType> remove(AnyType x, Node<AnyType> t) {
if(t == null)
return t;     //未找到该元素

int compareResult = x.compareTo(t.element);

if(compareResult < 0)
t.left = remove(x, t.left);
else if(compareResult > 0)
t.right = remove(x, t.right);
else if(t.left != null && t.right != null){   //要删除的节点有两个孩子
t.element = findMin(t.right).element;
t.right = remove(t.element, t.right);
}
else
t = (t.left != null)?t.left:t.right;  //要删除的节点有一个孩子或无孩子

return t;
}


4. 二叉查找树的遍历

4.1中序遍历

中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。在遍历左、右子树时,仍然先遍历左子树,再访问根结点,最后遍历右子树。

private void inOrder(Node<AnyType> root){
if(root != null){
inOrder(root.left);
System.out.print(root.element + " ");
inOrder(root.right);
}
}


4.2 先序遍历

首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树,如果二叉树为空则返回。

private void preOrder(Node<AnyType> root){
if(root != null){
System.out.print(root.element + " ");
preOrder(root.left);
preOrder(root.right);
}
}


4.3 后序遍历

后序遍历首先遍历左子树,然后遍历右子树,最后访问根结点,在遍历左、右子树时,仍然先遍历左子树,然后遍历右子树,最后遍历根结点。

private void postOrder(Node<AnyType> root){
if(root != null){
postOrder(root.left);
postOrder(root.right);
System.out.print(root.element + " ");
}
}


4.4
层序遍历

设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

/**
* 层次遍历二叉树(使用队列)
* 描述:先将根节点放入队列中,然后每次都从队列中取出一个节点打印,
*     若这个节点有子节点,则将它的子节点放入队列尾,直到队列为空
*/
public void layerTranverse(){
if(this.root == null){
return;
}

Queue<Node<AnyType>> q = new LinkedList<Node<AnyType>>();
q.add(this.root);
while(!q.isEmpty()){
Node<AnyType> n = q.poll();
System.out.print(n.element + " ");
if(n.left != null){
q.add(n.left);
}
if(n.right != null){
q.add(n.right);
}

}
}


4.5
遍历结果对比





参考资料:

1. 《数据结构与算法分析》

2. 《Java程序员面试笔试宝典》

3. Boss的PPT
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: