第三讲 树(上)
什么是树
树的定义
树的一些基本术语
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层,其它任一结点的层数是其父结点的层数加一
11.树的深度(Depth):树中所有结点中最大层次是这棵树的深度
树的表示
初步想法如下:
但是每个结点的结构(指针域个数)不同,如果不统一结构,就会给程序实现带来困难;如果统一结构,就会造成空间的浪费
什么是二叉树
儿子-兄弟表示法
二叉树:一个有穷的结点的集合
这个集合可以为空
若不为空,则它是由根节点喝称其为左子树TL和右子树TR的两个不相交的二叉树组成
- 二叉树具体有五种基本形态
特殊二叉树
- 斜二叉树
- 完美二叉树/满二叉树
- 完全二叉树
二叉树的几个重要性质
- 一个二叉树的第i层的最大结点树为2i-1,i>=1
- 深度为k的二叉树有最大的结点总数为2k-1,k>=1
- 对任何非空二叉树T,若n0表示叶节点的总数(子结点为0),n1表示子节点为1的结点的总数,n2表示子节点为2的结点的总数,则存在关系n0=n2+1
验证(利用边数)
- 边数=总的节点数-1
边数=n0+n1+n2-1- 边数=每个结点的个数✖它对边的贡献
边数=n0✖0 + n1✖1 + n2✖2
两便同时约掉n1,再约掉一个n2,得出结论
二叉树的抽象数据类型定义
类型名称:二叉树
数据对象集:一个又穷的结点的集合
若不为空,则由根节点和其左、右二叉树组成
操作集:
1.Boolean IsEmpty(BinTree BT):判别BT是否为空
2.void Traversal(BinTree BT):遍历,按某顺序访问每个结点
3.BinTree CreateBin():创建一个二叉树
二叉树的存储结构
1.顺序存储
完全二叉树:
一般二叉树
2.链表存储
- 定义树的数据结构
typedef struct TreeNode *BinTree;
typedef BinTree Position;
struct TreeNode{
ElementType Data;
BinTree Left;
BinTree Right;
};
二叉树的遍历(数组实现)
常用的遍历方法由:
void PreOrderTraversal(BinTree BT):**先序—**根、左子树、右子树
void InOrderTraversal(BinTree BT):**中序—**左子树、根、右子树
void PostOrderTraversal(BinTree BT):**后序—**左子树、右子树、根
void LevelOrderTraversal(BinTree BT):层次遍历,从上到下、从左到右
- (1)先序遍历
- (取访问到的第一次)
- ①访问根节点
- ②先序遍历其左子树
- ③先序遍历其右子树
void PreOrderTraversal(BinTree BT)
{
if(BT){ //判断是否为空
printf("%d", BT->Data); //访问根节点
PreOrderTraversal(BT->Left); //对左子树递归
PTrOrderTraversal(BT->Right); //对右子树递归
}
}
- (2)中序遍历
- (取访问到的第二次)
- ①中序遍历其左子树
- ②访问根节点
- ③中序遍历其右子树
void PreOrderTraversal(BinTree BT)
{
if(BT){ //判断是否为空
PreOrderTraversal(BT->Left); //对左子树递归
printf("%d", BT->Data); //访问根节点
PTrOrderTraversal(BT->Right); //对右子树递归
}
}
- (3)后序遍历
- (取访问到的第三次)
- ①后序遍历其左子树
- ②后序遍历其右子树
- ③访问根节点
void PreOrderTraversal(BinTree BT)
{
if(BT){ //判断是否为空
PreOrderTraversal(BT->Left); //对左子树递归
PTrOrderTraversal(BT->Right); //对右子树递归
printf("%d", BT->Data); //访问根节点
}
}
二叉树的非递归遍历(堆栈实现)
以中序为例
中序遍历非递归遍历算法
基本思路:使用堆栈
- 当遇到一个结点就把它压堆栈,并去遍历它的左子树
- 当左子树遍历结束后,从栈顶弹出这个结点并访问它
- 然后按其右指针再去中序遍历该结点的右子树
void InOrderTraversal(BinTree BT)
{
BinTree T=BT; //把BT赋给临时变量T
Stack S=CreateSatck(MaxSize); //创建并初始化堆栈S
while(T || !IsEmpty(S)){ //树不空并且堆栈不空
while(T){ //一直向左并将沿途结点压入堆栈
Push(S, T);
T=T->Left; //往左边走
//T为NULL时退出循环
}
if(!IsEmpty(S)){ //堆栈不空
T=Pop(S); //结点弹出栈
printf("%5d", T->Data); //打印结点
T=T->Right; //转向右子树
}
}
}
二叉树的层序遍历(队列实现)
二叉树遍历的核心问题:二维结构的线性化
- 从结点访问其左、右儿子结点
- 访问左儿子后,右儿子的结点怎么办?
- 需要一个存储结构保存暂时不访问的结点
- 存储结构:堆栈、队列
队列实现
遍历从根节点开始,首先将根节点入队,然后开始执行循环:结点出队、访问该结点、其左、右儿子入队
层序基本过程:先根节点入队,然后:
①从队列中取出一个元素
②访问该元素所指结点
③若该元素所指的结点的左、右孩子结点非空,则将其左、右儿子的指针顺序入队
void LevelOrderTraversal(BinTree BT)
{
Queue Q;
BinTree T;
if(!BT){
return ; //若是空树则直接返回
}
Q=CreatQueue(MaxSize); //创建并初始化队列Q
}
查找
查找:根据某个给定关键字K,从集合R中找出关键字与K相同的记录
- 静态查找
- 没有插入和删除操作,只有查找
- 动态查找
- 除了查找,还可能发生插入和删除
静态查找
- 方法1:顺序查找
时间复杂度O(n)
Tbl是一个一个指针的结构,有两个变量
typedef struct LNode *List;
struct LNode{
ElementType Element[MAXSIZE]; //数组Element
int length; //数组大小length
};
//方法1:顺序查找
int SequentialSearch(List Tbl, ElementType K)
{//在Element[1]~Element[n]中查找关键字为K的数据元素
int i;
Tb1->Element[0]=K; //建立哨兵K
for(i=Tb1->Length; Tb->Elemnet[i]!=K; i--){
//没有执行语句
}
return i;//查找成功返回所在单元下标,不成功返回0(初始哨兵的位置)
}
- 方法2:二分查找(Binary Search)
时间复杂度O(log2n)
假设n个数据元素的关键字满足有序(比如:从小到大),k1<k2<- - - <kn并且是连续存放 (数组),那么可以进行二分查找
//二分法查找算法
int BinarySearch(List Tbl, ElementType K)
{//在Tbl表中查找关键字为K的数据元素
int left, right, mid, NoFound=-1;
left=1; //初始左边界
right=1; //初始有边界
while(left<=right) //判断该数组里面是否有元素存在
{
mid=(left+right)/2; //计算中间元素坐标
if(K<Tbl->Element[mid]){
right=mid-1; //调整右边界
}else if(K>Tbl->Element[mid]){
left=mid+1; //调整左边界
}else{
return mid; //查找成功,返回元素的下标
}
}
return NotFound; //查找不成功,返回-1
}