PAT 備考——樹相關演算法
阿新 • • 發佈:2019-01-10
目錄
2.2.節點插入與刪除(後面二叉搜尋樹與平衡二叉樹會分別講)
一、樹與二叉樹
1.樹的儲存
一般來說,樹採用連結串列來定義:
typedef struct Node *PtrToNode;
typedef struct PtrToNode Tree;
typedef struct PtrToNode Position;
typedef int ElementType;
struct Node{
ElementType Data;
Position Left, Right;
};
當然,在靜態樹中,也可以利用陣列來表示:
const int MAXN = 1000; Typedef int ElementType; struct Node{ ElementType Data; int left, right;//Tree中的下標 }Tree[MAXN];
2.樹的基本操作
這裡以連結串列指標表示的動態二叉樹為例。
2.1.節點查詢
Position Find(Tree T, ElementType X){
if(T==NULL) return NULL;//沒找到
if(T->data==X) return T;
return Find(T->left; X);
return Find(T->right; X);
}
2.2.節點插入與刪除(後面二叉搜尋樹與平衡二叉樹會分別講)
二、樹的遍歷
1.二叉樹的遍歷
1.1.前序遍歷
1)遞迴解法:
/*樹的前序遍歷 *使用:遞迴 *思路:先輸出頭結點,再遍歷左子樹和右子樹 */ void PreOrderTraversal(Tree T){ if(T){ printf("%d\n", T->data); PreOrderTraversal(T->left); PreOrderTraversal(T->right); } }
2)非遞迴解法:注意是或的關係,且進入迴圈前不push
/*樹的前序遍歷
*使用:棧stack,#include<stack>
*思路:先輸出頭結點,再遍歷左子樹和右子樹
*/
void PreOrderTraversal(Tree T){
stack<Node> S;
Tree Tmp=T;
while(Tmp || !S.empty()){//樹非空或堆疊非空
while(Tmp){ //樹非空,列印並尋找左子樹
printf("%d\n", Tmp->data);
S.push(Tmp);
Tmp=Tmp->left;
}
if(!S.empty()){ //堆疊非空,彈出並尋找右子樹
Tmp=S.top();
S.pop();
Tmp=Tmp->right;
}
}
}
1.2.中序遍歷
1)遞迴解法
/*樹的中序遍歷
*使用:遞迴
*思路:先遍歷左子樹,再輸出頭結點和右子樹
*/
void InOrderTraversal(Tree T){
if(T){
InOrderTraversal(T->left);
printf("%d\n", T->data);
InOrderTraversal(T->right);
}
}
2)非遞迴解法
/*樹的前序遍歷
*使用:棧stack,#include<stack>
*思路:先遍歷左子樹,再輸出頭結點和右子樹
*/
void InOrderTraversal(Tree T){
stack<Node> S;
Tree Tmp=T;
while(Tmp || !S.empty()){//樹非空或堆疊非空
while(Tmp){ //樹非空,列印並尋找左子樹
S.push(Tmp);
Tmp=Tmp->left;
}
if(!S.empty()){ //堆疊非空,彈出並尋找右子樹
printf("%d\n", Tmp->data);
Tmp=S.top();
S.pop();
Tmp=Tmp->right;
}
}
}
1.3.後序遍歷
1)遞迴解法
/*樹的後序遍歷
*使用:遞迴
*思路:先遍歷左子樹和右子樹,再輸出頭結點
*/
void PastOrderTraversal(Tree T){
if(T){
PastOrderTraversal(T->left);
printf("%d\n", T->data);
PastOrderTraversal(T->right);
}
}
2)非遞迴解法:需要在節點中加入bool型別的IsFirstTraversal標誌位
void PastOrderTraversal(Tree T){//後續非遞迴
Tree Tmp = T;
stack<Tree> S;
while(Tmp || !S.empty()){
while(Tmp){
S.push(Tmp);
Tmp=Tmp->left;
}
if(!S.empty()){
Tmp=S.top();
S.pop();
if(Tmp->IsFirstTraversal){
Tmp->IsFirstTraversal=false;
S.push(Tmp);
Tmp=Tmp->right;
}
else{
cout<<Tmp->data;
}
}
}
}
1.4.層序遍歷
層序遍歷一般用非遞迴實現:
/*樹的層序遍歷
*使用:佇列
*思路:頭結點加入佇列;之後頭結點彈出佇列並把其兒子入隊;
* 迴圈直至佇列為空*/
void LevelOrderTraversal(Tree T){
queue<Tree> Q;
if(T==NULL) return;//邊界條件
Q.push(T);
while(!Q.empty()){
T=Q.front();
Q.pop();
printf("%d ", T->data);
if(T->left) Q.push(T->left);
if(T->right) Q.push(T->right);
}
}
1.5.已知遍歷順序建樹
例:已知後序遍歷和中序遍歷,建樹並輸出層序遍歷:
#include <iostream>
#include <queue>
//注意點:1.邊界條件;
// 2.後序遍歷開始位置和結束位置的確定不能依靠從中序遍歷計算到的inRoot
using namespace std;
const int maxn = 35;
typedef struct Node *Tree;
struct Node{
int data;
Tree left=NULL, right=NULL;
};
int n, post[maxn], in[maxn];
Tree T;
/*後序遍歷最後一位是根節點rootKey,
*在中序遍歷找到rootKey所在位置root後,
*中序left到root-1是左子樹,root+1到right是右子樹
*後序left到root-1是左子樹,root到right-1是右子樹
*遞迴,找到邊界條件:
*/
Tree GenerateTree(int postL, int postR, int inL, int inR){
if(postR<postL) return NULL;//邊界條件
int inRoot;//中序遍歷根節點的位置
//尋找根節點inRoot
for(inRoot=0; inRoot<inR+1; inRoot++){
if(in[inRoot]==post[postR]) break;
}
int numleft = inRoot-inL;//左子樹長度,用於確定後序遍歷左右子樹範圍
Tree tr = (Tree)malloc(sizeof(struct Node));//新建樹節點,也可以用Tree tr = new Tree;不加括號
tr->data=post[postR];//根節點data賦值
tr->left=GenerateTree(postL, postL+numleft-1, inL, inRoot-1);//遞迴返回左子樹
tr->right=GenerateTree(postL+numleft, postR-1, inRoot+1, inR);//遞迴返回右子樹
return tr;
}
int pointer = 0;//空格控制器
void LevelOrderTraversal(){
queue<Tree> q;
q.push(T);
Tree tmp=T;
while(!q.empty()){
tmp=q.front();
q.pop();
cout<<tmp->data;
pointer++;
if(pointer!=n) cout<<" ";//空格控制
if(tmp->left) q.push(tmp->left);
if(tmp->right) q.push(tmp->right);
}
}
int main()
{
cin>>n;
for(int i=0; i<n; i++) cin>>post[i];//輸入後序遍歷
for(int i=0; i<n; i++) cin>>in[i];//輸入中序遍歷
T = GenerateTree(0, n-1, 0, n-1);
LevelOrderTraversal();
return 0;
}
2.一般樹的遍歷
本節針對節點個數不限且子節點沒有先後次序的樹。方便起見,對於一般樹考試時希望採用靜態寫法:
const int maxn = 10000;
typedef int dataType;
struct Node{
dataType data;
vector<int> child;
}Tree[maxn];
由於子節點個數不定,因此一般樹的遍歷只考慮先序遍歷和層序遍歷。
2.1.先序遍歷(遞迴、不考慮子節點排序)
void PreOrderTraversal(int u){
printf("%d\n", T[u].data);
for(int i=0; i<T[u].child.size(); i++){
PreOrderTraversal(T[u].child[i]);
}
}
2.2.層序遍歷
void LevelOrderTraversal(int u){//非遞迴
queue<int> Q;
Q.push(u);
while(!Q.empty()){
u=Q.front();
Q.pop();
cout<<u<<endl;
for(int i=0; i<Tree[u].child.size(); i++){
Q.push(Tree[u].child[i]);
}
}
}
三、二叉搜尋樹
1.定義
二叉搜尋樹(Binary Search Tree, BST)又稱作二叉查詢樹、排序二叉樹等,是二叉樹的一種特殊形式。BST的每一個節點的左子節點小於(或小於等於)該節點,右節點大於該節點,形成一種從左到右從小到大的儲存形式。
性質:二叉搜尋樹的中序遍歷有序
2.操作集
2.1.樹的結構與建樹
typedef struct Node *BST;
typedef BST Position;
struct Node{
int data;
Position left, right;
};
//建樹
BST Create(int data[], int n){//陣列以及陣列中資料個數
BST T = NULL;//不能新建,最好為空
for(int i=0; i<n; i++){
Insert(data[i], T);
}
return T;
}
2.2.查詢
1)查詢任意元素
//查詢
Position Search(int X, BST T){
if(T==NULL) return NULL;
if(X==T->data) return T;
else if(X<T->data) return Search(X, T->left);
else return Search(X, T->right);
}
2)查詢最值
Position FindMin(BST T){//查詢最小值
Position tmp = T;
if(tmp==NULL) return NULL;
while(tmp->left != NULL){
tmp=tmp->left;
}
return tmp;
}
Position FindMax(BST T){//查詢最大值
Position tmp = T;
if(tmp==NULL) return NULL;
while(tmp->right != NULL){
tmp=tmp->right;
}
return tmp;
}
2.3.插入
//插入
void Insert(int X, BST T){
if(T==NULL){
BST tmp = new BST;
tmp->data=X;
tmp->left=tmp->right=NULL;
T=tmp;
}
if(X==T->data) return;//節點已存在
else if(X<T->data) Insert(X, T->left);
else Insert(X, T->right);
}
2.4.刪除
void Delete(int X, BST T){
Position P = Search(X, T);//查詢被刪除元素位置
if(P->left==NULL && P->right==NULL){//沒有子節點
free(P);//有待商榷?
}
//有兩個子節點,找右子節點最小值替換
else if(P->right!=NULL && P->left!=NULL){
Position minRight = FindMin(P->right);
P->data=minRight->data;//替換data
Delete(minRight->data, P->right);
}
else if(P->left!=NULL){//左不為空,右為空
Position tmp = P->left;
P->data=tmp->data;
P->left=tmp->left;
P->right=tmp->right;
free(tmp);
}
else if(P->right!=NULL){//右不為空
Position tmp = P->right;
P->data=tmp->data;
P->left=tmp->left;
P->right=tmp->right;
free(tmp);
}
}
四、平衡二叉樹
1.定義
所謂二叉平衡樹,是在保持二叉搜尋樹性質的,且左右子樹高度差的絕對值不超過1的二叉樹。
性質:查詢時間複雜度O(logn)
結構:僅增加了height即該節點的高度,葉節點為1,其上每一層父節點的高度是其左右子樹最大值+1
typedef struct Node* Position;
typedef Position Tree;
struct Node{
int data, height;
Position left, right;
};
2.基本操作集
2.1.基本函式
Position NewNode(int v){//新建data是v的點
Position T = (Position)malloc(sizeof(struct Node));
T->data=v;
T->height=1;
T->left=T->right=NULL;
return T;
}
int GetHeight(Position P){
if(P==NULL) return 0;//空節點高度為0
else return P->height;
}
int GetBalanceFactor(Position P){
return GetHeight(P->left) - GetHeight(P->right);
}
void UpdateHeight(Position P){
P->height = max(GetHeight(P->left), GetHeight(P->right)) + 1;//左右兒子最大值+1
}
2.2.查詢
//同二叉搜尋樹
Tree Search(int X, Tree T){
if(T==NULL) return NULL;
if(X==T->data) return T;
if(X<T->data) return T->left;
else(X<T->data) return T->right;
}
2.3.插入
//插入
void Insert(int X, Tree T){
if(T==NULL){
Position P = NewNode(X);
return;
}
if(X<T->data){
Insert(X, T->left);
UpdateHeight(T);//從低往上重置樹各節點高度
if(GetBalanceFactor(T)==2){//判斷本節點平衡因子是否為2,左大右小為正
if(GetBalanceFactor(T->left)==1){//LL型
T = RightRotate(T);
}
else if(GetBalanceFactor(T->right)==-1){//LR型
T->left = LeftRotate(T->left);
T = RightRotate(T);
}
}
}
else{
Insert(X, T->right);
UpdateHeight(T);
if(GetBalanceFactor(T)==-2){
if(GetBalanceFactor(T->right==-1)){//RR型
LeftRotate(T);
}
else if(GetBalanceFactor(T->right==1)){//RL型
RightRoTate(T->right);
LeftRotate(T);
}
}
}
}
其中左旋右旋分別是:
//左旋
Position LeftRotate(Tree T){
Tree tmp = T->right;//左旋就找右兒子
T->right=tmp->left;//第一步
tmp->left=T;//第二步
UpdateHeight(T);//先更新考下的節點的高度
UpdateHeight(tmp);//在更新考上節點的高度
return tmp;//第三步
}
//右旋
Position RightRotate(Tree T){
Tree tmp = T->left;
T->left=tmp->right;
tmp->right=T;
UpdateHeight(T);
UpdateHeight(tmp);
return tmp;
}