树(上)
什么是树?
客观世界中许多事物存在层次关系。
1.人类社会家谱。
2.社会组织结构。
3.图书信息管理。
查找:根据某个给定的关键字K,从集合R中找出关键字与K相同的记录。
1.静态查找:集合中记录是固定的。(没有插入和删除操作,只有查找)
2.动态查找:集合中记录是动态变化的。(除查找,还可能发生插入和删除)
静态查找
方法1:顺序查找
//顺序查找
typedef struct LNode *List;
struct LNode
{
ElementType Element[MAXSIZE];
int Length;
};
int SequentialSearch(List Tb1,ElementType K)//在Element[1]-Element[n]中查找关键字为K的数据元素
{
int i;
Tb1->Element[0]=K;//建立哨兵
for(i=Tb1->Length;Tb1->Element[i]!=K;i--)
{
return i;//查找成功返回单元下标,否则返回0
}
}
时间复杂度为O(n),有没有一种查找方式时间复杂度比O(n)更低呢?
提示:分层次组织在管理上具有更高的效率!
方法2:二分查找
前提:假设n个数据元素的关键字满足有序(比如:小到大),并且是存放(数组),那么可以进行二分查找。
//二分查找
int BinartSearch(List Tb1,ElementType K)//在表Tb1中查找关键字为K的数据元素
{
int left,mid,right,NotFound=-1;
left=1;//初始左边界
right=Tb1->Length;//初始右边界
while(left<=right)
{
mid=(left+right)/2;//计算中间元素坐标
if(k<Tb1->Element[mid])
right=mid-1;//调整右边界
else if(K>Element[mid])
right=mid+1;//调整左边界
else
return mid;//查找成功,返回数据元素的下标
}
return NotFound;//查找不成功,返回-1
}
二分查找算法具有对数的时间复杂度O(logN)。
树的定义
树(Tree):n(n>=0)个结点构成的有限集合。
当n=0时,称为空树。
对于任一棵非空树(n>0),它具备以下性质:
1.树中有一个称为“根(Root)”的特殊结点,用r表示;
2.其余结点可分为m(m>0)个互不相交的有限集T1,T2…Tm,其中每个集合本身又是一棵树,称为原来树的“子树(SubTree)”
树与非树?
1.子树是不相交的。
2.除了根结点外,每个结点有且仅有一个父结点。
3.一个N个结点的树有N-1条边。
树的一些基本术语
1.结点的度(Degree):结点的子树个数。
2.树的度:树所有结点中最大的度数。
3.叶结点(Leaf):度为0的结点。
4.父结点(parent):有子树的结点是其子树的根结点的父结点。
5.子结点(Child):若A结点是B结点的父结点,则称B结点是A结点的子结点;子结点也称孩子结点。
6.兄弟结点(Sibling):具有同一父结点的各结点彼此是兄弟结点。
7.路径和路径长度:从结点n1到nk的路径为一个结点序列n1,n2…nk,ni是ni+1的父结点。路径所包含的边的个数为路径的长度。
8.祖先结点(Ancestor):沿树根到某一结点路径上的所有结点都是这个结点的祖先结点。
9.子孙结点(Descendant):某一结点的子树中的所有结点是这个结点的子孙。
10.结点的层次(Level):规定根结点在1层,其他的任一结点的层数是其父结点的层数加1.
11.树的深度(Depth):树中所有结点中的最大层次是这棵树的深度。
二叉树的定义
二叉树T:一个有穷的结点集合。
这个集合可以为空。
若不为空,则它是由根结点和称为其左子树Tl,和右子树Tr的两个不相交的二叉树组成。
二叉树具有五种基本形态;
二叉树有左右顺序之分。
特殊二叉树
1.斜二叉树(Skewed Binary Tree)
2.完美二叉树(Perfect Binary Tree)
3.完全二叉树(Complete Binary Tree)
性质:
1.一个二叉树第i层的最大结点数为:2^i-1,i>=1;
2.深度为k的二叉树有最大结点总数为:2^k-1,k>=1;
3.对任何非空二叉树T,若n0表示叶结点的个数,n2是度为2的非叶结点个数,那么两者满足关系n0=n2+1。
二叉树的存储结构
1.顺序存储结构(数组)
完全二叉树:按从上至下,从左至右顺序存储n个结点的完全二叉树的结点父子关系:
非根结点(序号>1)的父结点的序号是[i/2];
结点(序号为i)的左孩子结点的序号是2i,右孩子的是2i+1.若2i<=n(2i+1<=n)则没有左孩子(右孩子)
2.链式存储结构(链表)
//初始化
typedef struct TreeNode *BinTree;
typedef BinTree Position;
struct TreeNode
{
ElementType data;
BinTree Left;
BinTree Right;
}
二叉树的遍历
先序:根——左——右。
中序:左——根——右。
后序:左——右——根。
层序:从上到下,从左到有。
1.先序遍历
遍历过程为:
(1)访问根结点;
(2)先序遍历其左子树;
(3)先序遍历其右子树。
void PreOderTraversal(BinTree BT)
{
if(BT)
{
printf("%d",BT->data);
PreOderTraversal(BT->Left);
PreOderTraversal(BT->Right);
}
}
2.中序遍历
遍历过程为:
(1)先序遍历其左子树;
(2)访问根结点;
(3)先序遍历其右子树。
void PreOderTraversal(BinTree BT)
{
if(BT)
{
PreOderTraversal(BT->Left);
printf("%d",BT->data);
PreOderTraversal(BT->Right);
}
}
3.后序遍历
遍历过程为:
(1)先序遍历其左子树;
(2)先序遍历其右子树;
(3)访问根结点。
void PreOderTraversal(BinTree BT)
{
if(BT)
{
PreOderTraversal(BT->Left);
PreOderTraversal(BT->Right);
printf("%d",BT->data);
}
}
总结:先序,中序,后序遍历过程,遍历过程中经过结点的路线是一样的,只是访问各结点的时机不同。
4.层序遍历:
队列实现:遍历从根结点开始,首先将根结点入队,然后开始执行循环:结点出队,访问该结点,其左右儿子入队。
层序基本过程:
从队列中取出第一个元素
访问该元素所指结点;
若该元素所指结点的左,右孩子结点非空,则将其左,右孩子的指针顺序入队。
void LevelOrderTraversal(BinTree BT)
{
Queue Q;
BinTree T;
if(!BT)
return;
Q=CreateQueue(MAXSIZE);
AddQ(Q,BT)
while(!IsEmptyQ(Q))
{
T=DeleteQ(Q);
printf("%d",T->data);
if(T->Left)
AddQ(Q,T->Left);
if(T->Right)
AddQ(Q,T->Right);
}
}
遍历二叉树,输出其子叶结点
void PreOrderPrintLeaves(BinTree BT)
{
if(BT)
{
if(!BT->Left&&!BT->Right)
printf("%d",BT->data);
PreOrderPrintLeaves(BT->Left);
PreOrderPrintLeaves(BT->Right);
}
}
求树的高度
int PostOrderGetHeight(BinTree BT)
{
int HL,HR,MAXSH;
if(BT)
{
HL=PostOrderGetHeight(HL->Left);
HR=PostOrderGetHeight(HR->Right);
MAXH=(HL>HR)?HL:HR;
return MAXH+1;
}
else
return 0;
}