PAT甲級 二叉樹 相關題_C++題解

二叉樹

PAT (Advanced Level) Practice 二叉樹 相關題node

目錄

  • 《算法筆記》 重點摘要
  • 1020 Tree Traversals (25)
  • 1086 Tree Traversals Again (25)
  • 1102 Invert a Binary Tree (25)
  • 1119 Pre- and Post-order Traversals (30)
  • 1127 ZigZagging on a Tree (30)
  • 1138 Postorder Traversal (25)
  • 1151 LCA in a Binary Tree (30)
  • 附:二叉樹鏈式實現

《算法筆記》 9.2 二叉樹 重點摘要

1. 常考邊界條件

  • 空樹:沒有結點
  • 樹只有一個根節點時,它亦爲葉子結點

2. 徹底二叉樹

  • 創建大小爲 2^k 的數組,其中 k 爲徹底二叉樹的最大高度
  • 若題目規定爲徹底二叉樹,數組大小 n+1 便可
  • 1 號位存放根節點
  • 任一編號爲 x 的結點,左孩子編號 2x,右孩子編號 2x+1
  • 判斷結點是否爲葉結點:2x > n
  • 判斷結點是否爲空:x > n

3. 二叉樹靜態實現 ⭐

(1) 定義
struct Node{
    typename data;
    int level;
    int lchild;
    int rchild;
} node[MAXN];
(2) 新建結點
int index = 0;
int newNode (int value){
    node[index].data = value;
    node->lchild = node->rchild = -1;
    return index++;
}
(3) 插入
void insert (int &root, int value){
    if (root == -1){
        root = newNode(value);
        return;
    }
    if (由二叉樹性質應插入在左子樹) insert(node[root].lchild, value);
    else insert(node[root].rchild, value);
}
(4) 建立
int create(int value[], int n){
    int root = -1;
    for (int i = 0; i < n; i++) insert(root, data[i]);
    return root;
}
(5) 查找&修改
void search (int root, int value, int newvalue){
    if (root == -1) return;
    if (node[root].data == value) node[root].data = newvalue;
    search(node[root].lchild, value, newvalue);
    search(node[root].rchild, value, newvalue);
}
(6) 先序遍歷
void preorder(int root){
    if (root == -1) return;
    printf("%d\n", node[root].data);
    preorder(node[root].lchild);
    preorder(node[root].rchild);
}
(7) 中序遍歷
void inorder(int root){
    if (root == -1) return;
    preorder(node[root].lchild);
    printf("%d\n", node[root].data);
    preorder(node[root].rchild);
}
(8) 後序遍歷
void postorder(int root){
    if (root == -1) return;
    preorder(node[root].lchild);
    preorder(node[root].rchild);
    printf("%d\n", node[root].data);
}
(10) 層序遍歷
void levelorder(int root){
    queue<int> q;
    q.push(root);
    while (!q.empty()){
        int now = q.front();
        q.pop();
        printf("%d", node[now].data);
        if (node[now].lchild != -1) q.push(node[now].lchild);
        if (node[now].rchild != -1) q.push(node[now].rchild);
    }
}
(11) 根據先序+中序重建樹
int create (int preL, int preR, int inL, int inR){
    if (preL > preR) return -1;
    int root = new Node(pre[preL]);
    int k;
    for (k = inL; k <= inR; k++)
        if (in[k] == pre[preL]) break;
    int numLeft = k - inL;  // 左子樹結點個數
    node[root].lchild = create(preL+1, preL+numLeft, inL, k-1);
    node[root].rchild = create(preL+numLeft+1, preR, k+1, inR);
    return root;
}

1020 Tree Traversals (25)

中序後序轉先序

  • 後序的最後一個即爲根節點,遞歸每次先取到根節點再分別遞歸進入左子樹和右子樹
  • 若要得到先序遍歷,只要在遞歸訪問到根節點時存到容器中便可
#include<iostream>
#include<vector>
using namespace std;
int post[31], in[31];
vector<int> pre;
void preorder(int post_root, int inL, int inR)
{
    if (inL > inR) return;
    pre.push_back(post[post_root]);
    int k;
    for (k = inL; k <= inR; k++)
        if (in[k] == post[post_root]) break;
    preorder(post_root-1-inR+k, inL, k-1);
    preorder(post_root-1, k+1, inR);
}
int main()
{
    int n;
    scanf("%d",&n);
    for (int i = 0; i < n; i++) scanf("%d", post+i);
    for (int i = 0; i < n; i++) scanf("%d", in+i);
    preorder(n-1, 0, n-1);
    printf("%d",pre[0]);
    for (int i = 1; i < pre.size(); i++) printf(" %d",pre[i]);
    return 0;
}

中序後序轉層序

  • 要按層序遍歷,不能夠直接在遍歷時獲得最終值序列的順序,須要進行標號排序
  • 用徹底二叉樹的標號,即根從 1 開始,將 根標號 index 的 2*index 傳給左子樹,賦給左子樹的根;將 2*index+1 給右子樹的根
  • 若左右子樹爲空,直接返回未壓入新節點,至關於層序遍歷結點容器中對應的序號空了出來,不影響後面按序號排序的順序
  • 遞歸完成後,對層序遍歷結點容器按序號排序,再按序輸出結點對應值
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
struct Node{
    int index, value;
};
bool cmp (Node a, Node b){ return a.index < b.index; }
int post[31], in[31];
vector<Node> level;
void levelorder(int post_root, int inL, int inR, int index)
{
    if (inL > inR) return;
    level.push_back({index,post[post_root]});
    int k;
    for (k = inL; k <= inR; k++)
        if (in[k] == post[post_root]) break;
    levelorder(post_root-1-inR+k, inL, k-1, 2 * index);
    levelorder(post_root-1, k+1, inR, 2 * index + 1);
}
int main()
{
    int n;
    scanf("%d",&n);
    for (int i = 0; i < n; i++) scanf("%d", post+i);
    for (int i = 0; i < n; i++) scanf("%d", in+i);
    levelorder(n-1, 0, n-1, 1);
    sort(level.begin(),level.end(),cmp);
    printf("%d",level[0].value);
    for (int i = 1; i < level.size(); i++) printf(" %d",level[i].value);
    return 0;
}

建樹方法

#include<iostream>
#include<queue>
#include<vector>
using namespace std;
struct Node{
    int data, lchild, rchild;
} node[31];
int index = 0;
int post[31], in[31];
int newNode(int value)
{
    node[index].data = value;
    node[index].lchild = node[index].rchild = -1;
    return index++;
}
int build(int postL, int postR, int inL, int inR)
{
    if (postL > postR) return -1;
    int root = newNode(post[postR]);
    int k;
    for (k = inL; k <= inR; k++)
        if (in[k] == post[postR]) break;
    int leftnum = k - inL;
    node[root].lchild = build(postL, postL+leftnum-1, inL, k-1);
    node[root].rchild = build(postL+leftnum, postR-1, k+1, inR);
    return root;
}
void levelorder(int root){
    queue<int> q;
    q.push(root);
    vector<int> result;
    while(!q.empty()){
        int now = q.front();
        q.pop();
        result.push_back(now);
        if (node[now].lchild != -1) q.push(node[now].lchild);
        if (node[now].rchild != -1) q.push(node[now].rchild);
    }
    printf("%d", node[result[0]].data);
    for (int i = 1; i < result.size(); i++) printf(" %d", node[result[i]].data);
} 
int main()
{
    int n;
    scanf("%d",&n);
    for (int i = 0; i < n; i++) scanf("%d", post+i);
    for (int i = 0; i < n; i++) scanf("%d", in+i);
    int root = build(0, n-1, 0, n-1);
    levelorder(root);
    return 0;
}

1086 Tree Traversals Again (25)

題目思路

  • 棧實現的是二叉樹的中序遍歷(左根右)
  • ⭐每次 push 值的順序是二叉樹的前序遍歷(根左右)
  • 前序中序轉後序
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
vector<int> in, pre, post;
void postorder(int preroot, int inL, int inR){
    if (inL > inR) return;
    int k;
    for (k = inL; k <= inR; k++)
        if (in[k] == pre[preroot]) break;
    postorder(preroot+1, inL, k-1);
    postorder(preroot+k-inL+1, k+1, inR);
    post.push_back(pre[preroot]);
}
int main()
{
    int n, num;
    scanf("%d", &n);
    string op;
    stack<int> s;
    for (int i = 0; i < 2 * n; i++){
        cin >> op;
        if (op.length() > 3){
            scanf("%d", &num);
            pre.push_back(num);
            s.push(num);
        } else{
            in.push_back(s.top());
            s.pop();
        }
    }
    postorder(0, 0, n-1);
    for (int i = 0; i < n; i++)
        printf("%d%c", post[i], i == n - 1 ? '\n' : ' ');
    return 0;
}

1102 Invert a Binary Tree (25)

建樹 + 層序遍歷 + 中序遍歷

  • 只須要樹的結構,沒有數據,用 vector 存儲左右孩子下標便可,遍歷輸出根的下標便可
  • invert tree 即將全部結點左右孩子對調,輸入並存儲時直接對調存儲便可
  • 在左右孩子中沒有出現過的下標即爲根的下標,用 bool 數組記錄是否出現過
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
vector<pair<int,int>> node;
vector<int> level, in;
void levelorder(int root)
{
    queue<int> q;
    q.push(root);
    while (!q.empty()){
        int now = q.front();
        level.push_back(now);
        q.pop();
        if (node[now].first != -1) q.push(node[now].first);
        if (node[now].second != -1) q.push(node[now].second);
    }
}
void inorder(int root)
{
    if (root == -1) return;
    inorder(node[root].first);
    in.push_back(root);
    inorder(node[root].second);
}
int main()
{
    int n;
    scanf("%d",&n);
    char a, b;
    getchar();
    bool appear[n] = {false};
    for (int i = 0; i < n; i++){
        scanf("%c %c", &a, &b);
        getchar();
        if (a != '-') appear[a-'0'] = true;
        if (b != '-') appear[b-'0'] = true;
        a = a == '-' ? -1 : a-'0';
        b = b == '-' ? -1 : b-'0';
        node.push_back({b,a});
    }
    int root = 0;
    while (appear[root]) root++;
    levelorder(root);
    printf("%d", level[0]);
    for (int i = 1; i < level.size(); i++) printf(" %d", level[i]);
    inorder(root);
    printf("\n%d", in[0]);
    for (int i = 1; i < in.size(); i++) printf(" %d", in[i]);
    return 0;
}

1119 Pre- and Post-order Traversals (30)

題意理解

  • 二叉樹的前序和後序沒法惟一肯定一顆二叉樹,由於一個結點多是根的左孩子也有多是根的右孩子,題目要求不惟一時生成其中一個可行解便可
  • 前序遍歷:根左右;後序遍歷:左右根;從遍歷順序可知前序遍歷時根節點後面面緊跟着一個子節點;同理,後序遍歷的根節點前面緊貼着他的一個子節點
  • 這兩個節點比較,若不一樣,說明根的左右節點肯定,若相同,此結點既多是左節點也多是右結點,建樹不惟一

題目思路

  • 用變量 unique 標記是否惟一,默認惟一置 true,比較發現不惟一時再標記爲 false
  • 遞歸時須要知道樹的表示範圍 =》 四個變量:前序左 preL, 前序右 preR, 後序左 postL, 後序右 postR
  • preL 與 postR 對應結點相同,爲根節點
  • 在前序序列中找後序右前一個子結點 (pre[k] == post[postR-1]),默認其爲右子樹結點,則其在先序序列中前面的結點即爲左子樹節點
  • 處理左子樹
    • 左子樹大小爲 numL = k - preL - 1
    • 若左子樹不爲空,則左右子樹肯定,遞歸進入左子樹
    • 若左子樹爲空,說明 post[postR-1] 這一點可能在左也可能在右,應置 unique = false
  • 左子樹處理完畢後將根節點 post[postR] 壓入中序序列
  • 再遞歸進入右子樹進行處理
  • preL == preR 時說明子樹僅有一個結點,則沒必要進行復雜處理,直接壓入中序序列便可
  • 注意:格式錯誤時要檢查全部數據輸出完成後是否須要輸出換行
#include<iostream>
#include<vector>
using namespace std;
vector<int> pre, post, in;
bool unique = true;
void inorder(int preL, int preR, int postL, int postR)
{
    if (preL == preR){
        in.push_back(pre[preL]);
        return;
    }
    int k;
    for (k = preL + 1; k <= preR && pre[k] != post[postR-1]; k++);
    int numL = k - preL - 1;
    if (numL > 0) inorder(preL+1, k-1, postL, postL+numL-1);
    else unique = false;
    in.push_back(post[postR]);
    inorder(k, preR, postL+numL, postR-1);
}
int main()
{
    int n;
    scanf("%d", &n);
    pre.resize(n);
    post.resize(n);
    for (int i = 0; i < n; i++) scanf("%d", &pre[i]);
    for (int i = 0; i < n; i++) scanf("%d", &post[i]);
    inorder(0, n-1, 0, n-1);
    printf("%s\n", unique ? "Yes" : "No");
    for (int i = 0; i < in.size(); i++) printf("%d%c", in[i], i+1 == in.size() ? '\n' : ' ');
    return 0;
}

1127 ZigZagging on a Tree (30)

題目思路

  • 不須要建樹,用深搜思想遞歸,傳入參數 level
  • 用 vector 數組保存每一層從左到右的結點內容
  • 每次遞歸根據傳入 level 將所在結點壓入對應層容器中
  • 再遞歸分別進入左右子樹,找左右子樹方法與中後序轉先序相似
  • 遞歸結束後獲得保存了每層內容的 vector 數組
  • 先輸出根,由於根結點一層僅有一個結點
  • 從下一層開始,奇數層從左到右輸出,偶數層從右到左輸出
#include<iostream>
#include<vector>
using namespace std;
vector<int> in, post, leveldata[31];
void leveltrave(int root, int inL, int inR, int level){
    if (inL > inR) return;
    leveldata[level].push_back(post[root]);
    int k;
    for (k = inL; k <= inR; k++)
        if (in[k] == post[root]) break;
    leveltrave(root-(inR-k)-1, inL, k-1, level+1);
    leveltrave(root-1, k+1, inR, level+1);
}
int main()
{
    int n;
    scanf("%d", &n);
    in.resize(n);
    for (int i = 0; i < n; i++) scanf("%d", &in[i]);
    post.resize(n);
    for (int i = 0; i < n; i++) scanf("%d", &post[i]);
    leveltrave(n-1, 0, n-1, 0);
    printf("%d", leveldata[0][0]);
    for (int i = 1; i < 31; i++){
        if (i % 2)
            for (int j = 0; j < leveldata[i].size(); j++)
                printf(" %d",leveldata[i][j]);
        else
            for (int j = leveldata[i].size(); j > 0; j--)
                printf(" %d",leveldata[i][j-1]);
    }
    return 0;
}

1138 Postorder Traversal (25)

前序中序轉後序

#include<iostream>
#include<vector>
using namespace std;
vector<int> pre, in, post;
void postorder(int pre_root, int inL, int inR)
{
    if (inL > inR) return;
    int k;
    for (k = inL; k <= inR; k++)
        if (pre[pre_root] == in[k]) break;
    postorder(pre_root+1, inL, k-1);
    postorder(pre_root+1+k-inL, k+1, inR);
    post.push_back(pre[pre_root]);
}
int main()
{
    int n, value;
    scanf("%d",&n);
    for (int i = 0; i < n; i++){
        scanf("%d", &value);
        pre.push_back(value);
    }
    for (int i = 0; i < n; i++){
        scanf("%d", &value);
        in.push_back(value);
    }
    postorder(0, 0, n-1);
    printf("%d", post[0]);
    return 0;
}

時間優化

  • 因爲只要知道後序第一個便可,可設置標記變量
  • 還未輸出過置爲 false,第一個輸出時改成 true
  • 進入遞歸時檢查變量,若爲true可沒必要再繼續遞歸,可減小運行時間
#include<iostream>
#include<vector>
using namespace std;
vector<int> pre, in;
bool flag = false;
void postorder(int pre_root, int inL, int inR)
{
    if (inL > inR || flag) return;
    int k;
    for (k = inL; k <= inR; k++)
        if (pre[pre_root] == in[k]) break;
    postorder(pre_root+1, inL, k-1);
    postorder(pre_root+1+k-inL, k+1, inR);
    if (!flag){
        flag = true;
        printf("%d",pre[pre_root]);
    }
}
int main()
{
    int n, value;
    scanf("%d",&n);
    for (int i = 0; i < n; i++){
        scanf("%d", &value);
        pre.push_back(value);
    }
    for (int i = 0; i < n; i++){
        scanf("%d", &value);
        in.push_back(value);
    }
    postorder(0, 0, n-1);
    return 0;
}

1151 LCA in a Binary Tree (30)

題目思路

  • 已知某個樹的根結點
  • 若 u 和 v 在根結點的左邊,則 u 和 v 的最近公共祖先在當前子樹根結點的左子樹尋找
  • 若 u 和 v 在當前子樹根結點的右邊,則 u 和 v 的最近公共祖先就在當前子樹的右子樹尋找
  • 若 u 和 v 在當前子樹根結點的兩邊,在當前子樹的根結點就是 u 和 v 的最近公共祖先
  • 若 u 或 v 等於根節點,則其爲另外一個點的祖先
  • 每次遞歸都要從新定位當前根節點與兩個待查結點的左右子樹關係,其實中序遍歷已經給出了這個關係
  • 將結點內容和中序遍歷的序號構成映射,每次遞歸直接取出序號進行比較而不是掃描序列
#include<iostream>
#include<vector>
#include<unordered_map>
using namespace std;
vector<int> pre, in;
unordered_map<int,int> inidx;
int u, v;
void LCA(int root, int inL, int inR){
    if (inL > inR) return;
    int k = inidx[pre[root]], uin = inidx[u], vin = inidx[v];
    if (k == uin) printf("%d is an ancestor of %d.\n", u, v);
    else if (k == vin) printf("%d is an ancestor of %d.\n", v, u);
    else if (uin < k && vin < k) LCA(root+1, inL, k-1);
    else if (uin > k && vin > k) LCA(root+k-inL+1, k+1, inR);
    else printf("LCA of %d and %d is %d.\n", u, v, pre[root]);
}
int main()
{
    int m, n;
    scanf("%d%d", &m, &n);
    in.resize(n);
    for (int i = 0; i < n; i++){
        scanf("%d", &in[i]);
        inidx[in[i]] = i;
    }
    pre.resize(n);
    for (int i = 0; i < n; i++) scanf("%d", &pre[i]);
    for (int i = 0; i < m; i++){
        scanf("%d%d", &u, &v);
        if (inidx.find(u) == inidx.end() && inidx.find(v) == inidx.end())
            printf("ERROR: %d and %d are not found.\n", u, v);
        else if (inidx.find(u) == inidx.end()) printf("ERROR: %d is not found.\n", u);
        else if (inidx.find(v) == inidx.end()) printf("ERROR: %d is not found.\n", v);
        else LCA(0, 0, n-1);
    }
    return 0;
}

附:

二叉樹鏈式實現

(1) 定義
struct Node{
    typename data;
    int level
    Node* lchild;
    Node* rchild;
}
(2) 新建結點
Node* newNode (int value){
    Node* node = new Node;
    node->data = value;
    node->lchild = node->rchild = NULL;
    return node;
}
(3) 插入
void insert (Node* &root, int value){
    if (root == NULL){
        root = newNode(value);
        return;
    }
    if (由二叉樹性質應插入在左子樹) insert(root->lchild, value);
    else insert(root->rchild, value);
}
(4) 建立
Node* create(int value[], int n){
    node* root = NULL;
    for (int i = 0; i < n; i++) insert(root, data[i]);
    return root;
}
(5) 查找&修改
void search (Node* root, int value, int newvalue){
    if (root == NULL) return;
    if (root->data == value) root->data = newvalue;
    search(root->lchild, value, newvalue);
    search(root->rchild, value, newvalue);
}
(6) 先序遍歷
void preorder(Node* root){
    if (root == NULL) return;
    printf("%d\n", root->data);
    preorder(root->lchild);
    preorder(root->rchild);
}
(7) 中序遍歷
void inorder(Node* root){
    if (root == NULL) return;
    preorder(root->lchild);
    printf("%d\n", root->data);
    preorder(root->rchild);
}
(8) 後序遍歷
void postorder(Node* root){
    if (root == NULL) return;
    preorder(root->lchild);
    preorder(root->rchild);
    printf("%d\n", root->data);
}
(10) 層序遍歷
void levelorder(Node* root){
    queue<Node*> q;
    root->level = 1;
    q.push(root);
    while (!q.empty()){
        Node* now = q.front();
        q.pop();
        printf("%d", now->data);
        if (now->lchild != NULL){
            now->lchild->level = now->level + 1;
            q.push(now->lchild);
        }
        if (now->rchild != NULL){
            now->rchild->level = now->level + 1;
            q.push(now->rchild);
        }
    }
}
(11) 根據先序+中序重建樹
Node* create (int preL, int preR, int inL, int inR){
    if (preL > preR) return NULL;
    Node* root = new Node;
    root->data = pre[preL];
    int k;
    for (k = inL; k <= inR; k++)
        if (in[k] == pre[preL]) break;
    int numLeft = k - inL;  // 左子樹結點個數
    root->lchild = create(preL+1, preL+numLeft, inL, k-1);
    root->rchild = create(preL+numLeft+1, preR, k+1, inR);
    return root;
}
相關文章
相關標籤/搜索