1. 程式人生 > >求二叉樹的帶權路徑長度(深搜或廣搜)

求二叉樹的帶權路徑長度(深搜或廣搜)

下面說說這道題目。樹的帶權路徑長度(Weighted Path Length)定義:樹中所有葉子的帶權路徑長度之和。比如下面這棵樹,WPL就是3*2+7*1 = 13。

                         2
                        / \
                       /   \
                      4     7
                       \  
                        \
                         3

1、深搜(前序遍歷)的演算法可以用遞迴,程式簡潔,先判斷當前的根是不是葉子,若是則加上該葉子的帶權路徑長度,葉子的深度可以作為引數傳遞得到,求深度是個必須解決的問題。然後對左子樹、右子樹遞迴進行。因為是遞迴,所以我用了全域性變數來記錄部分和,不知道有沒有不用全域性變數的方法。

2、廣搜(層次遍歷)中的難題是求每層的深度,我參考了王道書上的演算法,用了一個 last 變數來記錄每層的最右結點,我覺得這個方法很巧,有點難想。

兩個演算法的實現如下(測試資料建的是上面那棵樹,假設樹根的深度為0)

/********************************************************************
created:    2014/01/06
created:    14:42
file base:  main
file ext:   cpp
author:     Justme0 (http://blog.csdn.net/Justme0)

purpose:    求二叉樹的帶權路徑長度
*********************************************************************/

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <queue>
#include <cassert>
using namespace std;

/*
** 二叉連結串列的結點定義
*/
struct Node {
    Node *left;
    Node *right;
    int weight;

    Node() : left(NULL), right(NULL), weight(0) {}

    bool has_left() const {
        return NULL != left;
    }

    bool has_right() const {
        return NULL != right;
    }

    bool is_leaf() const {
        return !has_left() && !has_right();
    }
};

#pragma region 深搜

/*
** 前序遍歷求樹的帶權路徑長度(weighted path length)
*/
int wpl_dfs = 0;    // wpl_dfs 為加權的部分和
void DFS(Node *root, int depth) {
    if (root == NULL) { // root 為空樹
        return ;
    }

    if (root->is_leaf()) {
        wpl_dfs += root->weight * depth;
    } else {
        DFS(root->left, depth + 1);
        DFS(root->right, depth + 1);
    }
}

int get_WPL_dfs(Node *root) {
    DFS(root, 0);
    return wpl_dfs;
}

#pragma endregion 深搜


#pragma region 廣搜

/*
** 層次遍歷求樹的帶權路徑長度(weighted path length)
*/
int get_WPL_bfs(Node *root) {
    if (root == NULL) { // root 是空樹
        return 0;
    }

    int wpl_bfs = 0;
    int depth = 0;
    Node *last = root;  // last 為每層最右結點的地址,用於確定高度
    queue<Node *> Q;
    Q.push(root);
    do {
        Node *p = Q.front();
        Q.pop();

        if (p->is_leaf()) {
            wpl_bfs += p->weight * depth;
        } else {
            if (p->has_left()) {
                Q.push(p->left);
            }
            if (p->has_right()) {
                Q.push(p->right);
            }
        }

        if (p == last && !Q.empty()) {  // 此時處理到該層的最右結點
            last = Q.back();    // 隊尾恰好是下一層的最右結點的地址
            ++depth;
        }
    } while (!Q.empty());

    return wpl_bfs;     
}

#pragma endregion 廣搜

/*
** 輸入前序序列動態生成樹
*/
void creat_tree(Node *&root) {
    int info;
    cin >> info;
    if (info == -1) {
        root = NULL;
    } else {
        root = new Node;
        root->weight = info;
        creat_tree(root->left);
        creat_tree(root->right);
    }
}

/*
** 後序遍歷釋放樹
*/
void free_tree(Node *&root) {
    if (root == NULL) {
        return ;
    }

    free_tree(root->left);
    free_tree(root->right);
    delete root;
    root = NULL;
}

int main(int argc, char **argv) {
    freopen("cin.txt", "r", stdin);

    Node *tree = NULL;
    creat_tree(tree);

    int dfs_wpl = get_WPL_dfs(tree);
    int bfs_wpl = get_WPL_bfs(tree);
    assert(dfs_wpl == bfs_wpl);
    cout << "深搜:WPL=" << dfs_wpl << endl;    // 11
    cout << "廣搜:WPL=" << bfs_wpl << endl;    // 11

    free_tree(tree);

    return 0;
}

/*cin.txt
1 2 -1 4 -1 -1 3 -1 -1
*/

我考試時寫的演算法思想是廣搜,然後一想到程式中得用佇列,還得考慮深度,我就蒙了,只寫了個函式頭,希望能施捨一點分。。。我覺得這題是個典型的題目,不偏,出得挺好的。

20140110 
1、下面這種方法基於的原理是 WPL 為所有分支結點的權的和(這裡的分支結點的權定義為它的左兒子和右兒子的權的和,同 Huffman 樹)。

/*
** 後序遍歷將分支結點的左兒子和右兒子的權相加作為該分支結點的權(覆蓋原來無用的權)
** 再將所有的分支結點的權相加即 WPL
*/
int wpl_postorder = 0;
int get_WPL_postorder(Node *root) {
    if (root == NULL) {
        return 0;
    }

    get_WPL_postorder(root->left);
    get_WPL_postorder(root->right);
    if (!root->is_leaf()) {
        root->weight = 0;
        if (root->has_left()) {
            root->weight += root->left->weight;
        }
        if (root->has_right()) {
            root->weight += root->right->weight;
        }
        wpl_postorder += root->weight;
    }
    return wpl_postorder;
}
2、下面的方法有點偷懶,在結點定義時加了一個深度欄位,用以解決求深度的問題。這種方法可以任何一種方式遍歷樹,當用前序時原理同開始的深搜演算法,只不過那裡的深搜將深度作為引數求出,這個乾脆為它增加一個欄位。
/*
** 在結點中加深度欄位
*/
int wpl_depth = 0;

void visit(Node *root) {
    if (NULL == root) {
        return ;
    }

    if (root->is_leaf()) {
        wpl_depth += root->weight * root->depth;
    } else {
        if (root->has_left()) {
            root->left->depth = root->depth + 1;
        }
        if (root->has_right()) {
            root->right->depth = root->depth + 1;
        }
        visit(root->left);
        visit(root->right);
    }
}

int get_WPL_depth(Node *root) {
    root->depth = 0;
    visit(root);
    return wpl_depth;
}