《算法競賽入門經典》6-7 Trees on the level UVA122——二叉樹的層次遍歷(寬度優先遍歷BFS)

Trees on the level       UVA - 122 

Trees are fundamental in many branches of computer science (Pun definitely intended). Current state- of-the art parallel computers such as Thinking Machines’ CM-5 are based on fat trees. Quad- and octal-trees are fundamental to many algorithms in computer graphics.

This problem involves building and traversing binary trees.

Given a sequence of binary trees, you are to write a pro- gram that prints a level-order traversal of each tree. In this problem each node of a binary tree contains a positive integer and all binary trees have have fewer than 256 nodes.

In a level-order traversal of a tree, the data in all nodes at a given level are printed in left-to-right order and all nodes at level k are printed before all nodes at level k + 1.

For example, a level order traversal of the tree on the right is: 5,4,8,11,13,4,7,2,1.

In this problem a binary tree is specified by a sequence
of pairs ‘(n,s)’ where n is the value at the node whose path
from the root is given by the string s. A path is given be
a sequence of ‘L’s and ‘R’s where ‘L’ indicates a left branch and ‘R’ indicates a right branch. In the tree diagrammed above, the node containing 13 is specified by (13,RL), and the node containing 2 is specified by (2,LLR). The root node is specified by (5,) where the empty string indicates the path from the root to itself. A binary tree is considered to be completely specified if every node on all root-to-node paths in the tree is given a value exactly once.

Input

The input is a sequence of binary trees specified as described above. Each tree in a sequence consists of several pairs ‘(n,s)’ as described above separated by whitespace. The last entry in each tree is ‘()’. No whitespace appears between left and right parentheses.

All nodes contain a positive integer. Every tree in the input will consist of at least one node and no more than 256 nodes. Input is terminated by end-of-file.

Output

For each completely specified binary tree in the input file, the level order traversal of that tree should be printed. If a tree is not completely specified, i.e., some node in the tree is NOT given a value or a node is given a value more than once, then the string ‘not complete’ should be printed.

Sample Input

(11,LL) (7,LLL) (8,R)
(5,) (4,L) (13,RL) (2,LLR) (1,RRR) (4,RR) ()
(3,L) (4,R) ()

Sample Output

5 4 8 11 13 4 7 2 1
not complete

解題思路:

首先要解決讀數據問題,根據題意,當輸入爲“()”時,結束該組數據讀入,當沒有字符串時,整個輸入結束。因此可以專門編寫一個read_input()函數,類型設置爲bool型,遇到第一種情況時返回true,遇到第二種情況返回false,主程序中只要發現read_input返回false時就break,結束整個大循環。

接下來要建立二叉樹,首先爲二叉樹編寫一個結構體,然後根據字符串的輸入情況來建樹,如果是‘L’就往左走,走不動時建一顆新樹,同樣的方法處理右子樹,最後讀入結點值。由於輸入可能有誤,因此用一個全局變量failed來記錄是否有輸入錯誤的情況出現,如果在建樹過程中發現該結點已經被賦過值,那麼全局變量failed變爲true。

最後開始BFS找結點值,此時可能出現有結點沒有結點值的情況,因此要把bfs定義爲bool型,只要出現這種非法情況,返回false。最後便不難根據情況進行輸出了。

a)        編寫read_input函數,專門用來讀取數據。函數返回類型爲bool,輸入不合法時返回false。依題意,如果輸入只有(),表示input終止,跳出循環並返回true.

//輸入和主程序
bool read_input(){
    failed=false;
    remove_tree(root);
    root=newnode();                 //創建根結點
    for (; ; ) {
        if(scanf("%s",s)!=1)return false;//整個輸入結束
        if(!strcmp(s, "()")) break;//讀到結束標誌
        int v;
        sscanf(&s[1], "%d",&v);     //讀入結點值
        addnode(v,strchr(s,',')+1); //查找逗號,然後插入結點
        
    }
    return true;
}

可以把任意“指向字符的指針”看成是字符串,從該位置開始,知道字符'\0'。函數strchr(s,',')返回字符串s中從左往右第一個字符','的指針。

sscanf 讀取格式化的字符串中的數據。以固定字符串爲輸入源sscanf函數原型爲int sscanf(const char *str, const char *format, ...)。將參數str的字符串根據參數format字符串來轉換並格式化數據,轉換後的結果存於對應的參數內。具體功能如下:

(1)根據格式從字符串中提取數據。如從字符串中取出整數、浮點數和字符串等。

(2)取指定長度的字符串

(3)取到指定字符爲止的字符串

(4)取僅包含指定字符集的字符串

(5)取到指定字符集爲止的字符串

b)        編寫名爲Node的結構體。結構體中包含input中括號左側的結值、判斷結點是否被賦值的bool類型的值,和類型爲Node *的左右子結點。由於二叉樹是遞歸定義的,其左右子結點類型都是“指向結類型的指針”,因此左右子結點的類型都是Node *.

//二叉樹的定義和操作
struct Node{
    bool have_value;                //是否被賦值過
    int v;//結點值
    Node *left,*right;
    Node():have_value(false),left(NULL),right(NULL){}   //構造函數
};
Node* root;             //二叉樹的根結點
//申請新結點:每次需要一個新的Node時,都需要用new運算符申請內存,並執行構造函數
Node* newnode(){
    return new Node();
}

c)        建立全局變量failed,記錄是否有從根到某個葉結的路徑上有的結沒有在輸入中給出或值給出超過一次的情況

d)        建立函數addnode:按照移動序列走,目標不存在時調用newnode函數來創造新結點,判斷結點是否已被賦值(如果已經被賦過值,將failed值改爲true),並將結點值賦給對應的結點.

void addnode(int v,char* s){
    unsigned long n=strlen(s);
    Node* u=root;       //從根結點開始往下走
    for (int i=0; i<n; i++) {
        if (s[i]=='L') {
            if(u->left==NULL)u->left=newnode();//左結點不存在,那就建立新結點
            u=u->left; //往左走
        }
        else if(s[i]=='R'){
            if(u->right==NULL)u->right=newnode();
            u=u->right;
        }                   //忽略其他情況,即使最後那個多餘的右括號
    }
    if(u->have_value) failed=true;  //已經賦過值,表明輸入有誤
    u->v=v;
    u->have_value=true;         //別忘記做標記
}

e)        寬度優先遍歷(Breadth-First Search)bfs找結點,使用隊列:初始時只有一個根結點,然後每次取出一個結點,把它的左右子結點(如果存在)放進隊列中,用vector保存結點的值.

//按照層次順序遍歷這棵樹。此處實用隊列來完成任務
bool bfs(vector<int>& ans){
    queue<Node*> q;
    ans.clear();
    q.push(root);                    //初始時只有一個根結點
    while (!q.empty()) {
        Node* u=q.front();q.pop();
        if(!u->have_value)  return false;//有結點沒有被賦值過,比噢名輸入有誤
        ans.push_back(u->v);        //增加到輸出序列尾部
        if(u->left!=NULL)q.push(u->left);//把左子結點(如果有)放到隊列中
        if(u->right!=NULL)q.push(u->right);//把右子結點放進隊列
    }
    return true;
}

f)        如果程序動態申請內存,請注意內存泄漏。釋放二叉樹。在“root=newnode()"之前加一行“remove_tree(tree)":

//釋放二叉樹,對內存泄漏保持警惕
void remove_tree(Node* u){
    if(u==NULL) return; //提前判斷比較穩妥
    remove_tree(u->left);//遞歸釋放左子樹的空間
    remove_tree(u->right);//遞歸釋放右子樹的空間
    delete u;             //調用u的析構函數並釋放u結點本身的內存
}

 

 

#include <algorithm>
#include <cstdio>
#include <string>
#include <iostream>
#include <list>//雙向鏈表
#include <stack>//棧
#include <queue>//隊列
using namespace std;
const int maxn=20;
char s[maxn];                       //保存讀入結點
bool failed;
//二叉樹的定義和操作
struct Node{
    bool have_value;                //是否被賦值過
    int v;//結點值
    Node *left,*right;
    Node():have_value(false),left(NULL),right(NULL){}   //構造函數
};
Node* root;             //二叉樹的根結點
//申請新結點:每次需要一個新的Node時,都需要用new運算符申請內存,並執行構造函數
Node* newnode(){
    return new Node();
}

//釋放二叉樹,對內存泄漏保持警惕
void remove_tree(Node* u){
    if(u==NULL) return; //提前判斷比較穩妥
    remove_tree(u->left);//遞歸釋放左子樹的空間
    remove_tree(u->right);//遞歸釋放右子樹的空間
    delete u;             //調用u的析構函數並釋放u結點本身的內存
}


//按照移動序列行走,目標不存在時用newnode來創建結點
void addnode(int v,char* s){
    unsigned long n=strlen(s);
    Node* u=root;       //從根結點開始往下走
    for (int i=0; i<n; i++) {
        if (s[i]=='L') {
            if(u->left==NULL)u->left=newnode();//左結點不存在,那就建立新結點
            u=u->left; //往左走
        }
        else if(s[i]=='R'){
            if(u->right==NULL)u->right=newnode();
            u=u->right;
        }                   //忽略其他情況,即使最後那個多餘的右括號
    }
    if(u->have_value) failed=true;  //已經賦過值,表明輸入有誤
    u->v=v;
    u->have_value=true;         //別忘記做標記
}

//輸入和主程序
bool read_input(){
    failed=false;
    remove_tree(root);
    root=newnode();                 //創建根結點
    for (; ; ) {
        if(scanf("%s",s)!=1)return false;//整個輸入結束
        if(!strcmp(s, "()")) break;//讀到結束標誌
        int v;
        sscanf(&s[1], "%d",&v);     //讀入結點值
        addnode(v,strchr(s,',')+1); //查找逗號,然後插入結點
    }
    return true;
}

//按照層次順序遍歷這棵樹。此處實用隊列來完成任務
bool bfs(vector<int>& ans){
    queue<Node*> q;
    ans.clear();
    q.push(root);                    //初始時只有一個根結點
    while (!q.empty()) {
        Node* u=q.front();q.pop();
        if(!u->have_value)  return false;//有結點沒有被賦值過,比噢名輸入有誤
        ans.push_back(u->v);        //增加到輸出序列尾部
        if(u->left!=NULL)q.push(u->left);//把左子結點(如果有)放到隊列中
        if(u->right!=NULL)q.push(u->right);//把右子結點放進隊列
    }
    return true;
}
int main(int argc, const char * argv[]) {
    while (1) {
        if(!read_input())break;
        vector<int> ans;
        if (!failed&&bfs(ans)) {
            int len=(int)ans.size();
            for(int i=0;i<len;i++)
                printf("%d%c",ans[i],i==len-1?'\n':' ');
        }
        else printf("not complete");
    }
    return 0;
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章