树 第四章
本章的内容有:树,树的基本性质,二叉树,二叉树的顺序存储,二叉树的链式存储,二叉树的遍历(先序遍历,中序遍历,后序遍历),二叉树的应用
引子:当说到查找的时候,目前所学的查找都是在有序数列中进行查找,也就是所说的静态查找,比如说通过顺序查找(将序号为0的数列设置为哨兵,从后面往前查找),二分查找(通过每次二分,将查找的范围缩小一半,时间复杂度为O(logn))。当数据变得繁琐起来,比如说出现了插入和删除,我们就进入了二叉树的结构
树就像我们生活中看到的树一样,有根节点,有子节点,有叶节点
二叉树的基本性质:链接
二叉树分为:斜二叉树,完美二叉树(满二叉树),完全二叉树
二叉树的顺序存储结构:链接
二叉树的链式存储结构:
typedef Position BinTree;
typedef struct TNode
{
BinTree left,right;
ElementType Data;
}* Position;
二叉树的操作集:
1.生成: BinTree CreateTree();
2.是否为空:bool IsEmpty(BinTree BT);
3.遍历: bool Traversal(BinTree BT);
**二叉树的遍历:**当我们把数据存储在二叉树的结构上时,我们需要通过遍历才能找到某个数据
1.中序遍历(递归 recursion):遍历顺序为 左节点,根节点,右节点
void InorderTraversal(BinTree BT)
{
if(BT)
{
InorderTraversal(BT->left);
printf("%d",BT->Data);
InorderTraversal(BT->right);
}
}
中序遍历的非递归算法:堆栈的LIFO特点符合中序遍历的特点,所以这里用堆栈来实现
void InorderTraversal(BinTree BT)
{
BinTree T;
T = BT;
Stack S = CreateStack();
while(T||!IsEmpty(S))//堆栈的插入一档要检查是否为空,因为这里的堆栈用的链式存储,所以可以不用检查是否满栈
{
while(T)
{
Push(S,T);//将这个结点压入栈,而不是这个结点具体的值
T = T->left;
}
if(!IsEmpty(S))
{
T = Pop(S);
printf("%d",T->Data);
T= T->right;
}
}
}
2.先序遍历:
void PreorderTraversal(BinTree BT)
{
if(BT)
{
printf("%d",BT->Data);
InorderTraversal(BT->left);
InorderTraversal(BT->right);
}
}
先序遍历的非递归算法:
void PreorderTraversal(BinTree BT)
{
BinTree T;
T = BT;
Stack S = CreateStack();
while(T||!IsEmpty(S))
{
while(T)
{
Push(S,T);
printf("%d",T->Data);//先序遍历就是碰到根节点就输出
T = T->left;
}
if(!IsEmpty(S))
{
T = Pop(S);
T= T->right;
}
}
}
3.后序遍历:
void PostorderTraversal(BinTree BT)
{
if(BT)
{
InorderTraversal(BT->left);
InorderTraversal(BT->right);
printf("%d",BT->Data);
}
}
后序遍历的递归算法:这是一个难点,因为要先回到父节点,判断是否有右节点
MOOC里同学讨论的算法
void PostOrderTraversal(BinTree BT)
{
BinTree T=BT;
BinTree tempNode=NULL;
Stack S=CreatStack(MaxSize); //初始化堆
while(T||!IsEmpty(S))
{
while(T && T->left!=tempNode && T->right!=tempNode){ //是否遍历完,左孩子或右孩子是否已入栈
Push(S,T); //从(子)树根向左将结点入栈
T=T->left;
}
if(!IsEmpty(S)){
T=GetTop(S); //获取栈顶元素
if(T->right && T->right!=tempNode)
{ //右孩子存在且未入过栈
T=T->right; //如果左边最后的结点有右子树,继续上述入栈操作
}else
{
T=Pop(S); //没有右子树了,就出栈
printf("%5d",T->Data); //读根
tempNode=T; //存放已读根的左孩子
T=GetTop(S); //获取栈顶元素,即返回已读根的父结点
}
}
}
}
层序遍历算法:层序遍历的原理是每一层从第一个结点开始读取,这和队列FIFO的特征很符合,即先进去的结点,就先输出
void LevelorderTraversal(BinTree BT)
{
Queue Q;
BinTree T;
if(!BT) return 0;
Q = createQueue();
AddQ(Q,BT)
while(!IsEmpty(Q))
{
T = DeleteQ(Q);
printf("%d",T->Data);
if(T->left) AddQ(Q,T->left);
if(T->right) AddQ(Q,T->right7g);
}
}
例题:
1.输出二叉树的叶节点:
思路分析:二叉树的叶节点就是要判定它是否同时有左孩子和右孩子,只需要在遍历的基础上加上这个判定条件就可以了(下面的代码就是前序遍历加上判定条件)
void PrintBinTreeLeaves(BinTree BT)
{
if(BT)
{
if(!BT->left&&!BT->right) printf("%d",BT->Data);
PrintBinTreeLeaves(BT->left);
PrintBinTreeLeaves(BT->right);
}
}
2.求二叉树的高度:
思路分析:求一颗二叉树的高度就是求根节点的高度机上左右子树的高度,而每一刻左右子树又有一个根节点,就相当于把每一个子树分成若干个小子树,因为最底层的结点的高度为1,当判定它们没有左右孩子的时候,就可以从这里把高度开始累加,这里遍历二叉树用的是后序遍历,因为最后输出每一个树的根节点
int GetHeight(BinTree BT)
{
int leftHeight, rightHeight,MaxHeight;
if(BT)
{
leftHeight = GetHeight(BT->left);
rightHeight = GetHeight(BT->right);
MaxHeight = leftHeight>rightHeight?leftHeight:rightHeight;
return (MaxHeight+1);
}else return 0;
}
3.二叉树层序生成算法:
思路分析:二叉树的生成一定要先直到根节点是谁,所以最好用先序遍历的办法或者是层序遍历的办法
typedef int ElementType;
#define NoInfo 0
BinTree CreateBinTree()
{
ElementType Data;
BinTree BT, T;
Queue Q = CreateQueue();
//创建根节点
scanf("%d",&Data);
if(Data!=NoInfo)
{
BT =(BinTree)malloc(sizeof(struct TNode)});
BT->Data = Data;
BT->left = BT->right = NULL;
AddQ(Q,BT);
}else return NULL;
//分别创建左孩子和右孩子
while(!IsEmpty(Q))
{
T = DeleteQ(Q);
scanf("%d",&Data);
if(Data == NoInfo) T->left = NULL;
else
{
T->left = (BinTree)malloc(sizeof(struct TNode));
T->left->Data = Data;
T->left->left = T->left->right = NULL;
AddQ(Q,T->left);
}
scanf("%d",&Data);
if(Data == NoInfo) T->right = NULL;
else
{
T->right = (BinTree)malloc(sizeof(struct TNode));
T->right->Data = Data;
T->right->left = T->right->right = NULL;
AddQ(Q,T->right);
}
}
return BT;
}
二叉搜索树:
Binary search Tree,也叫做二叉排序树或者二叉查找树,二叉搜索树的基本结构和二叉树都是一样的,只是算法实现上不同的区别,二叉搜索树一般用链表来表示:
typedef struct TNode* Position;
typedef Position BinTree;
struct TNode
{
ElementType Data;
BinTree right,left;
}
二叉搜索树的基本操作集:
- Position Find(BinTree BST, ElementType X);
- Position FindMax(BinTree BST);
- Position FindMin(BinTree BST);
基本的查找操作:(递归方式)
Position Find(BinTree BST, ElementType X)
{
if(!BST) return NULL;
if(X>BST->Data) return Find(BST->right, X);
else if(X<BST->Data) return Find(BST->left,x);
else return BST;
}
(非递归方式)
Position Find(BinTree BST, ElementType X)
{
while(BST)//递归转化为非递归多用迭代循环
{
if(X>BST->Data) BST = BST->right;
else if(X<BST->Data) BST = BST->left;
else break;
}
return BST;
}
寻找最大值:
Position FindMax(BinTree BST)
{
if(!BST)return NULL;
else if(!BST->right) return BST;
else return FindMax(BST->right);
}
(非递归方法)
Position FindMax(BinTree BST)
{
if(BST)
while(BST->right) BST = BST->right;
return BST;
}
寻找最小值:
Position FindMin(BinTree BST)
{
if(BST!=NULL) return NULL;
else if(!BST->left) return BST;
else return FindMin(BST->left);
}
二叉搜索树的插入:
BinTree Insert(BinTree BST, ElementType X)
{
if(!BST)
{
BST = (BinTree)malloc(sizeof(struct TNode));
BST->Data = X;
BST->left = NULL;
BST->right = NULL;
}else
{
if(X>BST->Data) BST->right = Insert(BST->right, X);
else if(X<BST->Data) BST->left = Insert(BST->left, X);
}
return BST;
}
二叉搜索树的删除:
分为三种情况:
1.删除的结点为叶节点
2.删除的结点有一个子树
3.删除的结点有两个子树(要么取右子树最小值,要么取左子树最大值)
BinTree Delete(BinTree BST, ElementType X)
{
Position tempNode;
if(!BST) printf("要删除的元素未找到");
else
{
if(X<BST->Data) BST->left = Delete(BST->left, X);
else if(X>BST->Data) BST->right = Delete(BST->right, X);
else//找到了要删除的结点
{
if(BST->left && BST->right)
{
tempNode = FindMin(BST->right);
BST->Data = tempNode->Data;
BST->right = Delete(BST->right,BST->Data);
}else
{
tempNode = BST;
if(!BST->left) BST = BST->right;
else BST = BST->left;
free(tempNode);//无论哪种情况,被选择的结点必定最多只有一个孩子,也就是收即使找到的那个结点有左有孩子,但是接下来的操作又会在它的左子树或者右子树进行,最后的结点只有一个孩子
}
}
}
return BST;
}
平衡二叉树:(AVL树)
AVL树的插入,删除,查找操作 均可在O(logN)的时间内完成,AVL树“任一节点左右子树高度差的绝对值不超过1”,也就是说一棵AVL树中任一节点的平衡因子只能在集合{-1,0,1}中取值。
对于平衡二叉树的调整,一共分为两种情况,一个是单旋调整(RR旋转,LL旋转),第二个是双旋调整(LR旋转,RL旋转)
AVL树的插入操作:
typedef struct AVLNode* Position;
typedef Position AVLTree;
struct AVLNode
{
ElementType Data;
AVLTree Left;
AVLTree Right;
int Height;
}
int Max(int a, int b)
{
return a>b?a:b;
}
AVLTree Insert(AVLTree T, ElementType X)
{
if(!T)
{
T = (AVLTree)malloc(sizeof(struct AVLNode));
T->Data = X;
T->Left = T->Right = NULL;
T-> Height = 1;
}
else if(X<T->Data)
{
T->Left = Insert(T->Left, X);
//如果需要左旋
if(GetHeight(T->Left)-GetHeight(T->Right)==2)
{
if(X<T->Left->Data)
T = SingleLeftRotation(T);
else
T = DoubleLeftRightRotation(T);
}
}
else if(X>T->Data)
{
T->Right = Insert(T->Right, X);
if(GetHeight(T->Left)-GetHeight(T->Right) == -2)
{
if(X>T->Right->Data)
T = SingleLeftRotation(T);
else
T = DoubleRightLeftRotation(T);
}
}
//更新树的高度
T->Height = Max(GetHeight(T->Left), GetHeight(T->Right))+1;
return T;
}
左单旋算法:
AVLTree SingleLeftRotation(AVLTree A)
{
AVLTree B = A->Left;
A->Left = B->Right;
B->Right = A;
A->Height = Max(GetHeight(A->Left), GetHeight(B->Right)) +1;
B->Height = Max(GetHeight(B->Left), A->Height)+1;
return B;
}
左-右单旋算法:
AVLTree DoubleLeftRightRotation(AVLTree A)
{
//将B和C做右单旋,C被返回
A->Left = SingleRightRotation(A->Left);
//将A和C做左单旋,C被返回
return SingleLeftRotation(A);
}
树的应用:
堆及其操作:
堆通常也被称作“优先队列(Priority Queue)”,当我们使用数组,有序数组,链表,有序链表来进行删除和插入的操作的时候,最坏的情况的时间复杂度为O(N),而二叉搜索树的时间复杂度为O(logN),所以堆最常用的结构是用二叉树来表示,一般来说是一颗完全二叉树。
用数组表示完全二叉树是堆的第一个特性,称为堆的结构特性,一般来说分为最小堆和最大堆。
操作集:
- MaxHeap CreateHeap(int MaxSize);
- bool IsFull (MaxHeap H);
- bool IsEmpty(MaxHeap H, ElementType X);
- bool Insert(MaxHeap H, ElementType X);
- ElementType Detele(MaxHeap H);
最大堆的创建:(数组下标从1开始,0来设置一个哨兵)
typedef struct HNode * Heap;
struct HNode
{
ElementType * Data;//用数组实现
int Size; //队中当前元素个数
int Capacity;
};
typedef Heap MaxHeap;
typedef Heap MinHeap;
#define MaxData 1000
MaxHeap CreateHeap(int MaxSize)
{
MaxHeap H = (MaxHeap)malloc(sizeof(struct HNode));
H->Data = (ElementType*)malloc((MaxSize+1)*sizeof(ElementType));//下标为1开始
H->Size = 0;
H->Capacity = MaxSize;
H->Data[0] = MaxData;
return H;
}
最大堆的插入:
bool IsFull(MaxHeap H)
{
return (H->Size == H->MaxSize);
}
bool Insert(MaxHeap H, ElementType X)
{
int i;
if(IsFull(H))
{
printf("堆已满");
return false;
}
i = ++H->size;
for(;H->Data[i]<X;i/=2)
H->Data[i] = H->Data[i/2];
H->Data[i] = X;
return true;
}
最大堆的删除:
#define ERROR -1
bool IsEmpty(MaxHeap H)
{
return(H->Size ==0);
}
ElementType DeleteMax(MaxHeap H)
{
int Parent, Child;
ElementType MaxItem, X;
if(IsEmpty(H))
{
printf("堆为空");
return ERROR;
}
MaxItem = H->data[1];
X = H->Data[H->Size--];
for(Parent = 1; Parent*2<=H->Size;Parent = Child)
{
Child = Parent*2;
if((Child!=H->Size)&&(H->Data[Child]<H->Data[Child+1]))
Child++;
if(X>=H->Data[Child]) break;
else H->Data[Parent]=H->Data[Child];
}
H->Data[Parent] = X;
return MaxItem;
}
最大堆的建立:
void PercDown(MaxHeap H, int p)
{
int Parent, Child;
ElementType X;
X = H->Data[p];
for(Parent = p; Parent *2 <= H->Size; Parent = Child)
{
Child = Parent*2;
if((Child!=H->Size)&&(H->Data[Child]<H->Data[Child+1]))
Child++;
if(X>=H->Data[Child]) break;
else H->Data[Parent] = H->Data[Child];
}
H->Data[Parent] = X;
}
void BuildHeap(MaxHeap H)
{
int i;
for(i = H->Size/2;i>0;i--)//从最后一个父节点开始
PercDown(H,i);
}
哈夫曼树:
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
结点的带权路径长度是指从根节点到该结点之间的路径长度与该节点上所带权值的乘积。设一棵树有n个叶节点,每个叶节点带有权值W,从根节点到每个叶结点的长度为L,则每个叶结点的带权路径长度之和就是这棵树的带权路径长度(Weighted Path Length, WPL)
哈夫曼树的构造:
typedef struct HTNode * HuffmanTree;
struct HTNode
{
int Weight;
HuffmanTree Left;
HuffmanTree Right;
};
HuffmanTree Huffman(MinHeap H)
{
int i , N;
HuffmanTree T;
BuildHeap(H);
N = H->Size;
for(i = 1; i<N; i++)
{
T = (HuffmanTree)malloc(sizeof(struct HTNode));
T->Left = DeleteMin(H);
T->Right = DeleteMin(H);
T->Weight = T->Left->Weight+T->Right->Weight;
Insert(H,T);
}
return DeleteMin(H);
}