考研昨天结束了,专业课我考得不好,是408,算法题13分。今年是求二叉树的带权路径长度,我就写了算法思想,代码空白,因为时间来不及了,慌了,是心态的问题吧,做到最后有种天要塌下来的感觉,回来后很沮丧,因为我觉得自己是可以写出程序的,不服啊!
下面说说这道题目。树的带权路径长度(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;
}