二叉樹的基本操作(二)——二叉樹的恢復

前一篇文章描述了二叉樹的遍歷,這一章解決的問題是:當我們僅僅知道遍歷結果的時候,如何恢復二叉樹?

一、什麼樣的遍歷結果可以恢復出唯一的二叉樹?
通過前一章我們知道:二叉樹前序遍歷的第一個節點和後序遍歷的最後一個節點一定是二叉樹的根,二叉樹中序遍歷的情況下,根節點的左側節點全部在左子樹上,右節點全部在右子樹上。因此我們可以用這樣的遞歸邏輯寫代碼:

尋找並插入根節點(樹的指針,中序結果,前序/後序結果)
{
    根據前序的第一個節點/後序的最後一個節點找到當前樹的根
    插入到樹的當前位置
    根據根在中序結果的位置分離左右子樹
    在前序/後序結果中找出左右子樹
    將樹的當前位置更改到左子節點
    尋找並插入根節點(樹的當前位置,中序結果(左子樹),前序/後序結果(左子樹))
    將樹的當前位置更改到右子節點
    尋找並插入根節點(樹的當前位置,中序結果(右子樹),前序/後序結果(右子樹))
}

因此,針對中序+前序,或者針對後序+中序,我們都可以恢復出唯一的二叉樹
而中序+後序的情況呢?
觀察下面的兩棵樹:

A
  \
    B
      \
        C
  A
 /
B 
 \
  C

這兩棵樹的前序結果都是A B C 後序結果都是C B A
綜上所述,前序+後序不一定能恢復出唯一的二叉樹
那麼,有沒有前序+後序可以恢復出唯一的二叉樹的情況呢?答案是:
當且僅當二叉樹中所有節點的葉子節點爲0個或2個時,前序+後序可以恢復出唯一的二叉樹。
下面進行分析:
假設某棵樹的左子樹右子樹均存在:
對於前序有:根節點 左子樹的根節點 左子樹的其他部分 右子樹
對於後序有:左子樹 右子樹的其他部分 右子樹的根節點 根節點
斜體部分是我們可以確定的信息。
這時,我們事實上可以同時知道根節點和左右子樹的根節點,同時將左右子樹分割出來。但是,如果某棵樹只有左子樹或只有右子樹比如:
對於前序有:根節點 子樹的根節點 子樹的其他部分
對於後序有:子樹的其他部分 子樹的根節點 根節點
斜體部分是可以確定的信息。這時,我們雖然知道根節點和子樹的根節點,並且可以分割出子樹,但是我們不知道子樹究竟是左子樹還是右子樹,這纔是前序+後序不能恢復出唯一一顆二叉樹的根本原因。

但是,我們這裏給出的例子其實並不是二叉查找樹(Binary Search Tree)
二叉查找樹有以下特點:
1 所有左子樹上的節點都小於根節點;
2 所有右子樹上的節點都大於或等於根節點;
3 左子樹和右子樹也是二叉查找樹。

如果一棵樹是二叉查找樹,前序和後序任意給出一種,就可以唯一確定地復原出來。原因在於,它不需要中序遍歷的協助,只需要通過分析節點的大小關係,就能分割左右子樹。如果只給出中序遍歷,不一定能唯一確定地恢復出二叉查找樹。原因在於,僅有中序遍歷無法確定根節點,比如如果二叉查找樹的中序遍歷爲:1 2 3 4 5 6,我們根本無法通過大小關係知道哪個節點是根節點,它們任何一個節點作爲根節點都可以構建出二叉查找樹。

二、恢復二叉樹的代碼實現
1 中序+後序恢復二叉樹的實現:
這裏直接貼出一道題的代碼,該題的任務要求:給出二叉樹的中序和後序遍歷,要求輸出層序遍歷。

#include<iostream>
#include<queue>
using namespace std;

struct tree_node //二叉樹的定義
{
    int data = 0;
    tree_node* left_child = NULL;
    tree_node* right_child = NULL;
};

void find_insert_node(tree_node * &this_node_formal, int node_num, int* poststart, int* instart) //之所以使用指針引用,是因爲分配空間時需要修改指針指向的位置,node_num表示當前(子)樹中包含節點的總數。
{
    if (node_num == 0)
    {
        return;
    }
    this_node_formal = new tree_node;
    tree_node* this_node = this_node_formal;
    int data_now = *(poststart + node_num - 1);
    (*this_node).data = data_now;
    int divide_place;
    for (int i = 0; i < node_num; ++i)//找出當前(子)樹根節點在中序遍歷中的位置
    {
        if (*(instart + i) == data_now)
        {
            divide_place = i;
            break;
        }
    }
    find_insert_node((*this_node).left_child, divide_place, poststart, instart);
    find_insert_node((*this_node).right_child, node_num - divide_place - 1, poststart + divide_place, instart + divide_place + 1);
}

queue<tree_node*> node_queue;
queue<int> data_save;
void level_out() //層序遍歷
{
    tree_node* this_node;
    if (!node_queue.empty())
    {
        this_node = node_queue.front();
        node_queue.pop();
        if ( this_node == NULL)
        {
            level_out();
            return;
        }
    }
    else return;
    data_save.push((*this_node).data);
    node_queue.push((*this_node).left_child);
    node_queue.push((*this_node).right_child);
    level_out();
}

int main()
{
    int postorder[30];
    int inorder[30];
    int num; cin >> num;
    for (int i = 0; i < num; ++i) cin >> postorder[i];
    for (int i = 0; i < num; ++i) cin >> inorder[i];
    tree_node* tree_root = NULL;
    find_insert_node(tree_root, num, postorder, inorder);
    node_queue.push(tree_root);
    level_out();
    for (int i = 0; i < num; ++i)
    {
        cout << data_save.front();
        data_save.pop();
        if (i != num - 1) cout << " ";
    }
    getchar(); getchar();
    return 0;
}

2 前序+中序恢復二叉樹的實現
替換前面代碼段中的find_insert_node函數就可以

void find_insert_node(tree_node * &this_node_formal, int node_num, int* prestart, int* instart)
{
    if (node_num == 0)
    {
        return;
    }
    this_node_formal = new tree_node;
    tree_node* this_node = this_node_formal;
    int data_now = *prestart;
    (*this_node).data = data_now;
    int divide_place;
    for (int i = 0; i < node_num; ++i)
    {
        if (*(instart + i) == data_now)
        {
            divide_place = i;
            break;
        }
    }
    find_insert_node((*this_node).left_child, divide_place, prestart + 1, instart);
    find_insert_node((*this_node).right_child, node_num - divide_place - 1, prestart + divide_place + 1, instart + divide_place + 1);
}

3 前序遍歷恢復二叉查找樹的實現

void preorder_find_insert_node(tree_node* &this_node, int node_num, int* prestart)
{
    if (node_num == 0) return;
    this_node = new tree_node;
    (*this_node).data = *prestart;
    int divide;
    for (divide = 1; divide < node_num; ++divide)
    {
        if (*(prestart + divide) >= *prestart) break;
    }
    preorder_find_insert_node((*this_node).left_child, divide-1, prestart + 1);
    preorder_find_insert_node((*this_node).right_child, node_num - divide, prestart + divide);
}

4 後序遍歷恢復二叉查找樹的實現

void postorder_find_insert_node(tree_node* &this_node, int node_num, int* poststart)
{
    if (node_num == 0) return;
    this_node = new tree_node;
    (*this_node).data = *(poststart + node_num - 1);
    int divide;
    for (divide = 0; divide < node_num; ++divide)
    {
        if (*(poststart + divide) >= *(poststart + node_num - 1)) break;
    }
    postorder_find_insert_node((*this_node).left_child, divide, poststart);
    postorder_find_insert_node((*this_node).right_child, node_num - divide - 1, poststart + divide);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章