1. 程式人生 > >PAT 備考——樹相關演算法

PAT 備考——樹相關演算法

目錄

一、樹與二叉樹

1.樹的儲存

2.樹的基本操作

2.1.節點查詢

2.2.節點插入與刪除(後面二叉搜尋樹與平衡二叉樹會分別講) 

二、樹的遍歷

1.二叉樹的遍歷

1.1.前序遍歷

1.2.中序遍歷

1.3.後序遍歷

1.4.層序遍歷

1.5.已知遍歷順序建樹

2.一般樹的遍歷 

三、二叉搜尋樹

1.定義

2.操作集

2.1.樹的結構與建樹

2.2.查詢

2.3.插入

2.4.刪除

四、平衡二叉樹

1.定義

2.基本操作集

2.1.基本函式

 2.2.查詢

2.3.插入

五、堆

六、哈夫曼樹

七、並查集


一、樹與二叉樹

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;
}

 

五、堆

六、哈夫曼樹

七、並查集