六、 使用树结构解决编程问题
二叉树的性质
性质1:在二叉树的第i层上至多有2i个节点(i≥0)。
性质2:若二叉树的深度为d(d≥0),则该二叉树最少有d个节点,最多有2d-1个节点。
性质3:含有n个节点的二叉树的深度最大值为n,最小值为Math.Ceiling (log2(n+1))。
性质4:对任何一棵二叉树,如果其叶子节点数为n0,度为2的节点数为n2,则n0=n2+1
证明:设n1为二叉树T中度为1的节点数,
n为节点总数,B为边总数。
有 n=n0+n1+n2 (1)
n=B+1 (2)
B=n1+2n2 (3)
由(2)(3),得
n=n1+2n2+1 (4)
(4)-(1),得 n0=n2+1
证毕。
性质5:如果将一棵有n个节点的完整二叉树自顶向下,同一层自左向右连续给节点编号0、1、2、…、n-1。则有以下关系:
(1)若i=0,则i无父节点,若i>0,则i的父节点为Math.Floor((i-1)/2);
(2)若2*i+1<n,则i的左孩子为2*i+1;
(3)若2*i+2<n,则i的右孩子为2*i+2;
二叉树有各种类型:
严格二叉树
满二叉树——深度d的二叉树拥有刚好2d–1个节点。
完整二叉树——二叉树除了最深一层外都是满的,并且最深一层的节点是从左到右连续分布的。
表示一个二叉树
class Node//节点类定义
{
public char info;
public Node lchild;
public Node rchild;
}
class BinaryTree//二叉树类定义
{
public Node root;
public BinaryTree()
{root=null; }
public void creatBitree( Node ptr);
public void find(int element,ref Node parent,ref Node currentNode);
public void inorder( Node ptr);
public void preorder( Node ptr);
public void postorder( Node ptr);
}
遍历二叉树有四种方式:
中序遍历(inorder traversal)
前序遍历(preorder traversal)
后序遍历(postorder traversal)
层序遍历(levelorder traversal)
public void inorder( Node ptr) //中序遍历算法。
{ if (root == null)
{ Console.WriteLine("Tree is empty");
return;
}
if(ptr!=null)
{ inorder(ptr.lchild);
Console.Write (ptr.info+” “);
inorder(ptr.rchild);
}
}
public void preorder( Node ptr)//前序遍历算法
{if (root == null)
{ Console.WriteLine("Tree is empty");
return;
}
if(ptr!=null)
{
Console.Write (ptr.info+” “);
preorder(ptr.lchild);
preorder(ptr.rchild);
}
}
public void postorder( Node ptr)//后序遍历算法。
{if (root == null)
{ Console.WriteLine("Tree is empty");
return;
}
if(ptr!=null)
{
postorder(ptr.lchild);
postorder(ptr.rchild);
Console.Write (ptr.info+” “);
}
}
层序遍历(Levelorder Traversal)二叉树,是指从二叉树的第一层(根节点)开始,自上至下逐层遍历,在同一层中,则按从左到右的顺序对节点逐个访问。
层序遍历二叉树:
(1)若树为空,直接返回。
(2)初始化队列,并将根节点入队。
(3)重复执行步骤4,直到队列为空遍历结束。
(4)出队一个节点,访问该节点;如果该节点有左孩子,则将其左孩子入队列;如果该节点有右孩子,则将其右孩子入队列。
遍历操作的时间复杂度均为O(n)。
应用二叉树遍历的例子:
//利用二叉树遍历计算二叉树节点个数
public int nodeNumber(Node ptr)
{ if ( ptr = = null ) return 0;
int n1= nodeNumber (ptr.lchild);
int n2= nodeNumber (ptr.rchild);
return n1+n2+1;
}
//利用二叉树遍历计算二叉树的深度
public int treeDepth (Node ptr )
{
if ( ptr = = null ) return 0;
int d1= treeDepth (ptr.lchild);
int d2= treeDepth (ptr.rchild);
return 1 +(d1>d2?d1:d2);
}
树的应用
使用霍夫曼树进行消息编码
由根节点到某个节点所经过的边数称为由根节点到该节点的路径长度。
设一棵具有n个带权值叶节点的二叉树,那么从根节点到各个叶节点的路径长度与对应叶节点权值的乘积之和叫做二叉树的带权路径长度。
在此把其中具有最小带权路径长度的二叉树称为最优二叉树,最优二叉树也称作霍夫曼树。
构造霍夫曼树的步骤是:
(1)由给定的n个权值{W1、W2、…、Wn}构造n棵只有一个根节点(亦为叶节点)的二叉树,从而得到一个森林F={ T1、T2、…、Tn};
(2)在F中选取两棵根节点的权值最小的二叉树Ti、Tj,以Ti和Tj作为左、右子树构造一棵新的二叉树Tk。置新的二叉树Tk的根节点的权值为其左、右子树(Ti、Tj)上根节点的权值之和;
(3)在F中删去二叉树Ti、Tj;并把新的二叉树Tk加入F;
(4)重复(2)和(3)步骤,直到F中仅剩下一棵树为止。
算术表达式评估
前缀表达式:-×+a bc×+de f
中缀表达式:(a+b)×c-(d+e)×f
后缀表达式:a b+c×de+f×-
使用堆树排序数据
堆的构造
方法:从无序序列的下标为Math.Floor((n-2)/2)的元素(即此无序序列对应的完整二叉树的最后一个节点的父节点)起,逆向至下标为0的元素止,进行反复筛选
基本思想:
(1)对排序表中的数据元素,利用堆的调整算法形成初始堆(最大堆)。
(2)输出堆顶元素(或调整到堆的最后位置)。
(3)对剩余元素重新调整形成堆。
(4)重复执行第(2)、(3)步,直到所有数据元素被输出。
线索二叉树
在线索二叉树中,节点的右线索指向它中序的后继,并且左线索指向它中序前续
优点:遍历更快速、使用内存更少
表示一个线索二叉树
class Node//节点类
{ public int lthread;
public Node lchild;
public int info;
public Node rchild;
public int rthread;
}
class ThreadBST//线索二叉树类
{ private Node head;
public ThreadedBST() {} ;
public void insert( int element){}
public void find(int element,ref Node parent,ref Node currentNode){}
public void inorder( ){}
public void remove(int element) {}
}
线索二叉树的遍历
public Node InorderNext(Node ptr)
{
if (ptr.rthread == 0)
ptr = ptr.rchild;
else
{
ptr = ptr.rchild;
while (ptr.lthread != 0)
ptr = ptr.lchild;
}
return ptr;
}
public void Inorder()//中序遍历
{ Node currentNode;
if (head.lthread == 0)
{ Console.WriteLine("Tree is empty!");
return;
}
currentNode = head.lchild;
while (currentNode.lthread != 0)
currentNode = currentNode.lchild;
Console.WriteLine(currentNode .info);
while (currentNode.rchild != head)
{
currentNode=InorderNext(currentNode);
Console.WriteLine (currentNode .info);
}
}
平衡树
By Crayon
数据结构与算法(一):http://blog.csdn.net/crayon_chen/article/details/7700665
数据结构与算法(三):http://blog.csdn.net/crayon_chen/article/details/7700681
数据结构与算法(四):http://blog.csdn.net/crayon_chen/article/details/7700697