一.关于树的介绍
1.树的定义
所谓树(tree)就是指n(n>=0)个结点的有限集。当n为0时,我们称他为空树;在任意一颗非空树中:(1)有且仅有一个特定的成为gen(root)的结点;
(2)若是n>1,其余结点可以分成m(m>0)个互不相交的有限集,其中每个集合本身又是一颗树,成为树的字根。下图就是树;
2.树结点的分类
结点拥有的子树个数成为结点的度Degree。度为0的结点称为叶结点,也叫终端结点;度不为0的结点我们成为非终端结点,也叫分支结点。除根结点外,分支结点也称为内部结点。树的度时树内个结点的最大值。
3.结点间的关系(族谱)
结点的子树的根称为该结点的孩子(child),该结点称为孩子的双亲(parent)。同一个双亲的孩子之间互相称为兄弟(sibling)。结点的祖先是从根到该结点所经分支上的所有结点。以某个结点为根的子树中任一结点都是该结点的子孙。我们以下面的树为例进行分析:
A是B,C的双亲;B,C是兄弟关系;D是B的孩子;D和E间就是堂兄弟;而A,B,D是G的祖先;其他都是A的子孙。
4.树的层次
结点的层次是从根结点开始定义的,根是第一层,孩子为第二层,以此类推;若是某结点在第i层,那么它的子树就在第i+1层;树中结点的最大层次称为树的深度或者是高度。
树的存储方式是:双亲表示法,孩子表示法,孩子兄弟表示法。
二.二叉树概念
1.二叉树的定义
二叉树(Binary Tree) 是n(n>=0)个结点的有限集合,该集合或者是空集(空二叉树),或者由一个根结点和两颗互不相交的、分别称为根节点的左子树和右子树的二叉树组成。
特点
(1)每个结点最多有两棵子树;
(2)左子树和右子树是有顺序的;
(3)即使树中某个结点只有一颗子树,也要区分左右。
2.二叉树的形态
1.空二叉树;
2.只有一个根结点;
3.根结点只有左子树
4.根结点只有右子树;
5.根结点既有左子树也有右子树;
3.二叉树的特殊情况
3.1斜树
斜树分为左斜树和右斜树。所有结点都只有左子树的二叉树我们称为左子树;所有结点都只有右子树的二叉树我们称为右斜树。所以在极端情况下二叉树会退化成线性表(线性表是树的特殊表现形式),这种情况也叫做非平衡树。
3.2满二叉树
在一颗二叉树中,如果所有分支结点都存在左右子树,并且所有叶子结点都在同一层上,这种二叉树称为满二叉树,它是一种平衡二叉树。
3.3完全二叉树
对一颗具有n个结点的二叉树按层i序编号,如果编号i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这颗二叉树称为完全二叉树,它呢也是一种平衡二叉树,它只是倒数第二层的结点的子树是按从左到右连续拥有,倒数第二层后面的结点可以没有子树;但前面有子树必须是连续出现的;如下图:
4.二叉树的性质
(1).在二叉树的第i层上最多有2的i-1次方个结点(i>=1);
(2).深度为k的二叉树至多有2的k次方-1个结点(k>=1);
(3).对于任何一颗二叉树,如果终端结点数为n0,度为2的结点数为n2,则n0 = n2 + 1;
(4).具有n个结点的完全二叉树的深度为log以2为底n +1;
(5).如果对于一颗有n个结点的完全二叉树的结点按层序编号:
如果i=1,结点无双亲;如果i>1则双亲是i/2;
如果2i>n,则结点i无左孩子,否则其左孩子为2i;
如果2i+1>n,则结点i无右孩子,否则右孩子为2i+1;
5.二叉树的存储结构
5.1顺序存储结构
二叉树的顺序存储结构就是用以为数组存储树的结点,并且结点的存储位置也就是数组的下标要能体现出之间的逻辑关系。但顺序存储结构在极端情况下浪费空间,只适合平衡树的存储。如图:
5.2链式存储结构
二叉树每个结点最多有两个孩子,所以设计为一个数据域和两个指针域的结点进行链接,也叫二叉链表。
6.二叉树的遍历
二叉树的遍历分为两类:深度优先遍历DFS和广度优先遍历BFS
深度优先遍历:前序遍历、中序遍历、后序遍历;
广度优先遍历:层序遍历;
6.1前序遍历DLR
规则是二叉树为空,则空操作返回,否则先访问根结点,再前序遍历左子树,然后前序遍历右子树。
遍历上图结果为:ABDGHJCEIF。
6.2中序遍历LDR
规则是二叉树为空,则空操作返回,否则从根结点开始,中序遍历根结点的左子树,然后访问根结点,最后中序遍历右子树。
遍历上图结果为:GDJHBAEICF。
6.3后序遍历LRD
规则是二叉树为空,则空操作返回,否则从根结点开始,后序遍历根结点的左子树,然后后序遍历右子树,最后访问根结点。
遍历上图结果为:GJHDBIEFCA。
6.4层序遍历
规则是树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历;在同一层中,按从左到右的顺序对结点逐个访问。
遍历上图结果为:ABCDEFGHIJ。
三.二分搜索树(Binary Search Tree)
1.二分搜索树定义
它本身是二叉树,只不过呢对于二分搜索树的每个结点而言,大于其左结点的所有结点的值,小于其右子树的所有结点的值。同样,其子树也是一颗二分搜索树,那么该树中元素要具有可比性,不包含重复元素。如图:
2.代码实现
2.1结点类创建和二分搜索树的属性定义及其初始化
结点类拥有数据域E,两个指针域left,right表示左右孩子;创建一个结点初始是没有左右孩子的。对于二分搜索树则右属性root表示根结点,以及size表示元素个数。
package tree;
import java.util.Iterator;
import lianbiao.LinkedList;
public class BinarSearchTree<E extends Comparable<E>> implements Iterable<E> {
class Node{
public E e; //数据域
public Node left; //左孩子
public Node right; //右孩子
public Node(E e){
this.e = e;
left = null;
right = null;
}
@Override
public String toString() {
return e.toString();
}
}
private Node root;//根节点的指针;
private int size;//元素个数,(节点个数);
public BinarSearchTree(){
root = null;
size = 0;
}
2.2元素个数和判空操作的实现
public int size(){
return size;
}
public boolean isEmpty(){
return root == null && size == 0;
}
2.3添加元素(递归和迭代实现)
迭代实现:根据值创建结点,如果树是空树,则让root根指向创建的结点,元素个数加一;如果不空,则需要从根结点开始遍历,如果遍历的值比添加的元素小,则从右分支遍历,如果右孩子为空,则添加元素,否则就继续向下遍历;遍历的值比添加的元素大,则从左分支遍历,如果左孩子为空添加元素,否则继续向下遍历;否则就结束。
递归实现:
在以node为根的树中,插入元素e,并返回新树的根;
插入的值比根小则向左遍历,插入值比根大向右遍历,如果还有左右孩子,则继续向下遍历即可,直到遍历到空时,则将添加的元素作为上一个的孩子(左或右)向上返回;
public void add(E e){
//迭代
// Node node = new Node(e);
// if(isEmpty()){
// root = node;
// size++;
// }
// Node cur = root;
// while(true){
// //当前元素小,往右走
// if(cur.e.compareTo(node.e)<0){
// if(cur.right == null){
// cur.right = node;
// size++;
// break;
// }else{
// cur = cur.right;
// }
// //当前元素大,往左走;
// }else if(cur.e.compareTo(node.e)>0){
// if(cur.left == null){
// cur.left = node;
// size++;
// break;
// }else{
// cur = cur.left;
// }
// }else{
// break;
// }
// }
root = add(root,e);
}
//在以node为根的树中,插入元素e,并返回新树的根;
private Node add(Node node,E e){
if(node == null){
size++;
return new Node(e);
}
if(e.compareTo(node.e)<0){
node.left = add(node.left,e);
}else if(e.compareTo(node.e)>0){
node.right = add(node.right,e);
}
return node;
}
2.4判断是否包含元素(递归和迭代)
和添加元素相似,它是从根遍历,如果遍历的值比查找的元素大,则从左分支遍历,只要左分支不是空,就继续向下遍历,为空则不包含;如果遍历的值比查找的值小,则从右分支遍历,只要右分支不为空,继续向下遍历,为空,则不包含;如果遍历值和查找值相等则包含。
递归和迭代逻辑相似。
public boolean contains(E e){
Node cur = root;
while (true) {
if (e.compareTo(cur.e) < 0) {
if (cur.left == null) {
return false;
}
cur = cur.left;
} else if (e.compareTo(cur.e) > 0) {
if (cur.right == null) {
return false;
}
cur = cur.right;
} else {
return true;
}
}
//return contains(root,e);
}
private boolean contains(Node node, E e) {
if(node == null){
return false;
}
if(e.compareTo(node.e) < 0){
return contains(node.left,e);
}else if