目录标题
树 Tree
树的定义、结点、结点的度、树的度、内部结点、叶子结点
树是n(n大于等于0)个结点的有限集。 n=0时称为空树。
在任意一颗非空树中: 1),有且仅有一个根root结点。 2),当n>1时,其余结点可分为m(m>0)个互不相交的有限集,其中每一个集合本身又是一棵树,并且称为根的子树SubTree。
结点拥有的子树数称为节点的度。度为0的结点称为叶子结点或者终端结点,度不为0的结点称为非终端结点或者分支结点。除根结点以外,分支结点也称为内部结点。树的度是树内各结点的度的最大值。比如下面的树,整棵树中D结点的度最大是3,所以整棵树的度就是3。
`
结点的层次、兄弟结点、树的深度
结点的层次(level)从根开始定义起,根为第一层,根的孩子为第二层。若某结点在第i层,则其子树的根就在第i+1层。其双亲在同一层的结点互为堂兄弟。树中结点的最大层次称为树的深度(depth)或者高度。也就是说,一棵树有几层,深度就是几。比如上图,就是4层结点,深度是4。
有序与无序树
如果将树中结点的各子树看成从左至右是有序的,不能互换的,则称该树为有序树,则否称为无序树。
树林
森林是m(m>0)棵互不相交的树的集合。
树的存储结构
简单的顺序存储不能满足树的实现,需要结合顺序存储和链式存储来实现。
三种表示法:
1,双亲表示法;
2,孩子表示法;
3,孩子兄弟表示法;
双亲表示法
在每个结点中,附设一个指示器,指示其双亲结点到链表中的位置。
这种表示法的优点是表示解单,找父结点方便,但是找子结点不方便。找子结点就得遍历整个树才能找到。
孩子表示法
方案1:这种方案,找子结点就非常方便。
对于上面的树,用方案1的孩子表示法表示出来:
开辟一个固定长度的数组,比如5个单位大小的数组,首先是投结点,剩下的存放子结点指针,当存不满的时候,未使用的结点置空,但是有可能会有多个节点,5个单位不够存的情况,这种情况情况下只能丢弃那些存不下的结点了。
这种方式,找孩子结点方便了,但是找父结点又很不方便,需要遍历才能找出父节点。
方案2:
改进方案:
动态开辟子结点指针域存储空间大小,并且专门有一个空间存储该结点又几个孩子。有几个孩子开辟几个空间大小。
这个方案虽然改良了,但是依然没有解决父节点查询困难的问题。
最终方案:
把每个结点的孩子排列起来,以链表作为存储结构,则n个结点又n个链表,如果是叶子节点则此单列表为空,然后n个头指针又组成一个线性表,采用顺序存储结构,存放在一个一位数组中。
这种数据结构-----非常眼熟,非常类似于HashMap的散列列表存储结构,其实不是类似,而是就是。
这种表示的优点就是查找父节点非常方便,只需要找链表的头指针即可,找子结点也非常方便。
孩子兄弟表示法
任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此我们设置两个指针,分别指向该结点的第一个孩子和此节点的右兄弟。
通过把每个结点的左孩子和右兄弟都列出来,就完整表示出了这棵树。
这种表示法的优点是孩子结点方便的找到,找兄弟结点也是可以方便的找到,但是找父节点就不是很好找了,必须遍历才能找到父节点。
二叉树
二叉树的经典案例:
猜100以内的整数,注意猜的次数不能超过7个,回答者只回答大了还是小了。
特殊二叉树
斜树
所有的结点都只有左子树的二叉树叫左斜树。所有结点都只有右子树的二叉树叫右子树。二者统称为斜树。
线性表结构其实可以理解为树的一种表达形式。
满二叉树
在一颗二叉树中,如果所有分支节点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
完全二叉树
对一颗具有n个结点的二叉树按层次编号,如果编号为i(i大于等于1,小于等于n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
二叉树的性质
性质1:在二叉树的第i层上至多有2i-1个结点(i>=1)。
性质2:深度为k的二叉树至多有2k-1个结点(k>=1)。
性质3:对任何一颗二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2+1.
复习定义:
结点拥有的子树数称为节点的度。度为0的结点称为叶子结点或者终端结点,度不为0的结点称为非终端结点或者分支结点。
对于上面的完全二叉树,深度是4,终端结点是8个,度为2 的结点有7个 ,验证公式:8=7+1.
再比如这棵非完全二叉树,深度是4,终端结点是5个,度为2的结点有4个,验证公式:5=4+1.
验证得出,公式均是成立的。
再证明一下这个性质3:
设n是总结点树,n1为度为1的结点数,n2为度为2的结点数,n0是终端结点数。
n1度为1的结点,是E即,n1=1;n2度为2的结点,是A/B/C/D即,n2=4;
n0是终端结点,n0=n1+n2=1+4=5个叶子结点。
看图得出,结论正确无误。
性质4:具有n个结点的完全二叉树深度小于等于[log2(n+1)] ,即:[x]=[log2(n+1)] ([x]表示不大于 x的最大整数)。
N=2k-1 ,k是层次或者高度。
这是知道一个完全二叉树的高度或者层次,计算结点个数的公式。
反过来,就可以推导出,已知结点个数求深度的公式:log2(n+1)
推导过程:
N=2k-1
2k=N+1
k=log2(N+1)
这是对完全二叉树是N个结点,得出公式k=log2(N+1),那么,对于非完全二叉树,结点数量肯定是小于N的,比如是m个,那么肯定就是小于k=log2(N+1)公式计算出的值的。
性质5:如果对一颗有n个结点的完全二叉树(其深度为[log2n]+1) 的结点按层序编号(从第1层到第[log2n]+1层,每层从左到 右),对任意一个结点i(1<=i<=n)有:
1).如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结 点[i/2]
2).如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩 子是结点2i。
3).如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。
以上面的二叉树分析:
1).如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结 点[i/2]
比如,
i=1的时候,1结点就是根节点,没有双亲;
i=2或者3的时候,2结点的双亲结点就是2/2=1,3结点也类似,3/2=1;
i=4或者5的时候,4的双亲结点就是4/2=2,5结点也类似5/2=2;
i=6或者7的时候,6的双亲结点就是6/2=3,7结点也类似7/2=3;
i=8或者9的时候,8的双亲结点就是8/2=4,9结点也类似9/2=4;
i=11的时候,11的双亲结点就是11/2=5;
2).如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
比如,
n是结点的数量,上图中共有10个结点,n=10,
当i=6的时候,26>10,根据性质,6结点无左孩子,即6结点是叶子节点,看图发现正确;
当i=7的时候,27>10,根据性质,7结点无左孩子,即7结点是叶子节点,看图发现正确;
当i=8的时候,28>10,根据性质,8结点无左孩子,即8结点是叶子节点,看图发现正确;
当i=9的时候,29>10,根据性质,9结点无左孩子,即9结点是叶子节点,看图发现正确;
当i=11的时候,211>10,根据性质,11结点无左孩子,即11结点是叶子节点,看图发现正确;
在比如这棵树,共有7个结点;
当i=4的时候,24>7,根据性质,4结点无左孩子,否则其左孩子是24=8结点,8是左孩子结点,看图发现正确;
当i=5的时候,25>7,根据性质,5结点无左孩子,否则其左孩子是25=10结点,5结点是叶子结点没有左结点,看图发现正确;
当i=8的时候,28>7,根据性质,8结点无左孩子,否则其左孩子是28=16结点,8结点是叶子结点没有左结点,看图发现正确;
当i=9的时候,29>7,根据性质,9结点无左孩子,否则其左孩子是2*9=18结点,9结点是叶子结点没有左结点,看图发现正确;
3).如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。
这一条性质和上面的4-2性质(如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。)类似,左孩子是2i,右孩子肯定就是2i+1,不再仔细分析证明。
二叉树的存储结构
二叉树的顺序存储结构
用顺序存储结构存储二叉树。
这样顺序存储的话,不好确定父节点和子结点,也无法完全构造出一个二叉树。但是完全二叉树可以,为什么,因为完全二叉树结点排序是有顺序的。
一般二叉树是完全二叉树的子集,是完全二叉树缺了某些结点的二叉树。比如上图红色箭头的结点都是不存在的,是我们还原完全二叉树所填补出来的结点。
一般二叉树按顺序存储的时候,把那些参考完全二叉树不存在的结点,在存储的时候都空出来,
二叉链表
用链式结构存储二叉树。
中间数据域,左结点指针,右结点指针。
二叉树的遍历
打印出来就表示已经访问到结点了。
前序遍历
规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
先根,再左,再右。
遍历顺序:A-B-D-G-H-C-E-I-F
void ProOrderTraverse(Tree T){
if(T == null){
return;
}
printf(“%c”,T-data);
ProOrderTraverse(T->lchild);
ProOrderTraverse(T->rchild);
}
中序遍历
规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树
先左,再根,再右。
遍历顺序:G-D-H-B-A-E-I-C-F
void ProOrderTraverse(Tree T){
if(T == null){
return;
}
ProOrderTraverse(T->lchild);
printf(“%c”,T-data);
ProOrderTraverse(T->rchild);
}
后序遍历
规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点
先叶子左,再叶子右,再根。
遍历顺序:G-H-D-B-I-E-F-C-A
void ProOrderTraverse(Tree T){
if(T == null){
return;
}
ProOrderTraverse(T->lchild);
ProOrderTraverse(T->rchild);
printf(“%c”,T-data);
}
层序遍历
规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层,按从左到右的顺序对结点逐个访问
遍历顺序:A-B-C-D-E-F-G-H-I
java实现二叉树
迭代–迭代–迭代,重要的事情说三遍,迭代的思想。
package com.demo.binarytree;
import java.util.Stack;
public class BinaryTree {
private TreeNode root = null;
public BinaryTree(){
root = new TreeNode(1, "A");
}
/**
* 构建二叉树
* A
* B C
* D E F
*/
public void createBinaryTree(){
TreeNode nodeB = new TreeNode(2, "B");
TreeNode nodeC = new TreeNode(3, "C");
TreeNode nodeD = new TreeNode(4, "D");
TreeNode nodeE = new TreeNode(5, "E");
TreeNode nodeF = new TreeNode(6, "F");
root.leftChild = nodeB;
root.rightChild = nodeC;
nodeB.leftChild = nodeD;
nodeB.rightChild = nodeE;
nodeC.rightChild = nodeF;
}
/**
* 求二叉树的高度
* @author Administrator
*
*/
public int getHeight(){
return getHeight(root);
}
private int getHeight(TreeNode node) {
if(node == null){
return 0;
}else{
int i = getHeight(node.leftChild);
int j = getHeight(node.rightChild);
return (i<j)?j+1:i+1;
}
}
/**
* 获取二叉树的结点数
* @author Administrator
*
*/
public int getSize(){
return getSize(root);
}
private int getSize(TreeNode node) {
if(node == null){
return 0;
}else{
return 1+getSize(node.leftChild)+getSize(node.rightChild);
}
}
/**
* 前序遍历——迭代
* @author Administrator
*
*/
public void preOrder(TreeNode node){
if(node == null){
return;
}else{
System.out.println("preOrder data:"+node.getData());
preOrder(node.leftChild);
preOrder(node.rightChild);
}
}
/**
* 前序遍历——非迭代
*/
public void nonRecOrder(TreeNode node){
if(node == null){
return;
}
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.push(node);
while(!stack.isEmpty()){
//出栈和进栈
TreeNode n = stack.pop();//弹出根结点
//压入子结点
System.out.println("nonRecOrder data"+n.getData());
if(n.rightChild!=null){
stack.push(n.rightChild);
}
if(n.leftChild!=null){
stack.push(n.leftChild);
}
}
}
/**
* 中序遍历——迭代
* @author Administrator
*
*/
public void midOrder(TreeNode node){
if(node == null){
return;
}else{
midOrder(node.leftChild);
System.out.println("midOrder data:"+node.getData());
midOrder(node.rightChild);
}
}
/**
* 后序遍历——迭代
* @author Administrator
*
*/
public void postOrder(TreeNode node){
if(node == null){
return;
}else{
postOrder(node.leftChild);
postOrder(node.rightChild);
System.out.println("postOrder data:"+node.getData());
}
}
public class TreeNode{
private int index;
private String data;
private TreeNode leftChild;
private TreeNode rightChild;
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public TreeNode(int index,String data){
this.index = index;
this.data = data;
this.leftChild = null;
this.rightChild = null;
}
}
public static void main(String[] args){
BinaryTree binaryTree = new BinaryTree();
binaryTree.createBinaryTree();
int height = binaryTree.getHeight();
System.out.println("treeHeihgt:"+height);
int size = binaryTree.getSize();
System.out.println("treeSize:"+size);
// binaryTree.preOrder(binaryTree.root);
// binaryTree.midOrder(binaryTree.root);
// binaryTree.postOrder(binaryTree.root);
binaryTree.nonRecOrder(binaryTree.root);
}
}