全列举二叉搜索树 - 以太祖长拳打飞江湖第一流高手

Posted 程序员超时空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了全列举二叉搜索树 - 以太祖长拳打飞江湖第一流高手相关的知识,希望对你有一定的参考价值。

全列举二叉搜索树 - 一个有意思的问题

给定一个正整数n,返回所有的二叉搜索树,每棵二叉搜索树都包含了 1-n 这n个数字。

这题一看,比较容易这么想: 给定1-n个数字,先构造出一棵二叉搜索树; 然后再想办法推广这个方法,列举出所有的二叉搜索树。
这是一个简单直接的想法,程序员们往往也很乐意先去思考如何利用 1-n 构造出一棵二叉搜索树。
然而,根据笔者尝试的经验,这么做很费力,而且到最后不仅代码复杂,还很可能遇到设计上难以解决的问题。
因此,这种想法应该就是一个坑了; 它会误导程序员在错误递归的道路上走远,虽然最后可能会发现这原来是个坑。

如果碰壁过,那么会意识到,这里似乎不应该去递归地构造一棵树。
于是重新审题 - “返回所有的二叉搜索树” - 注意,这里返回的是一个集合,集合中的每个元素是一棵树。
于是进一步想到,是否能递归产生所有的左子树和所有的右子树呢?
如果能的话,那么拿到所有左子树和所有右子树之后,能做什么呢?
第二个问题很好回答,当拿到所有左子树和所有右子树之后, 2重for循环自然就列举出了所有的二叉搜索树。
问题是第一个问题,是否能递归产生所有的左子树和所有的右子树呢? 试着写写代码,发现其实递归还是满好写的。

这里最大的一个坎,就是需要想到“递归产生所有的子树”。
因为这一点比较地反常识。常识是,递归操作一棵树,而这里是递归产生一个集合的树。属于一种更高层次的递归。
直觉上会觉得似乎太复杂,于是潜意识里很可能都没有了尝试的意愿。
但是,这里有2个lessons to learn:

  • 递归不用在乎复杂度,直接去尝试就好。
  • 一棵树看起来复杂,但其实会简化为一个root节点;(之后对root的递归,属于低层次递归,比如打印)

那么,究竟是如何用“递归去产生一个树林”呢?
首先要给定树林的范围,比如 1-n, 可泛化为 [beg, end ]
然后是递归的退出条件:

  • beg > end : 这是不可能的情况,可直接退出 (但实际写程序的时候,会发现这里需要返回的一个只包含了一个 nullptr 的数组)
  • beg = end : 这表示只需要为一个数字产生一个树林,于是直接返回一个只有一个元素的数组即可
  • beg < end : 这是比较普遍的情况,此时最终产生的树林是 [beg, end] 里每个元素为root时的所有树,于是递归在此处发生

下面是基本数据结构和核心代码。真正的核心代码只有30行左右。

struct TreeNode 
    int val;
    TreeNode * left;
    TreeNode * right;
    TreeNode() : val(0), left(nullptr), right(nullptr) 
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) 
;

vector<TreeNode *> _genTreesBasedOnNums(int beg, int end) 
    vector<TreeNode *> trees;
    
    // Not possible, add nullptr to tree
    if(beg > end) 
        trees.push_back(nullptr);
        return trees;
    
    // Only one element in the subtree, so just add it
    if(beg == end) 
        trees.push_back(new TreeNode(beg));
        return trees;
    
    
    for(int i=beg; i<=end; i++) 
        // Each element in the 2 vectors stands for one Tree 
        vector<TreeNode *> leftTrees  = _genTreesBasedOnNums(beg, i-1);
        vector<TreeNode *> rightTrees = _genTreesBasedOnNums(i+1, end);
        
        for(int j=0; j<leftTrees.size(); j++) 
            for(int k=0; k<rightTrees.size(); k++) 
                TreeNode * root = new TreeNode(i);
                root->left = leftTrees[j];
                root->right = rightTrees[k];
                trees.push_back(root);
            
        
    
    return trees;


vector<TreeNode *> generateTrees(int n) 
    return _genTreesBasedOnNums(1, n);

下面再给出一个完整版(含测试)的代码。里面还会有一些打印的技巧。

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

#define NO_NODE_VAL_NUM -9999

struct TreeNode 
    int val;
    TreeNode * left;
    TreeNode * right;
    TreeNode() : val(0), left(nullptr), right(nullptr) 
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) 
;


void layered_print_tree(TreeNode * root) 
    if (root == nullptr) return;
    
    vector<int> result;
    
    queue<TreeNode *> nodeQueue;
    nodeQueue.push(root);
    
    while (!nodeQueue.empty()) 
        TreeNode * tmp = nodeQueue.front();
        nodeQueue.pop();
        
        if (tmp == nullptr) 
            result.push_back(NO_NODE_VAL_NUM);
        
        else 
            result.push_back(tmp->val);
            
            if (tmp->left == nullptr && tmp->right == nullptr) continue; 
            
            nodeQueue.push(tmp->left);
            nodeQueue.push(tmp->right);
        
    
    
    while (!result.empty() and result.back() == NO_NODE_VAL_NUM) 
        result.pop_back();
    
    
    cout << "[";
    
    for(int i=0; i<result.size(); i++) 
        if (result[i] != NO_NODE_VAL_NUM) cout << result[i];
        else cout << "null"; 
        if (i != result.size()-1 ) cout << ", "; 
    
    
    cout << "]";


void print_trees(const vector<TreeNode *> & vec) 
    cout << "There are " << vec.size() << " trees generated" << endl;
    
    cout << "["; 
    for (int i=0; i<vec.size(); i++) 
        layered_print_tree(vec[i]);
        if (i < vec.size()-1) cout << ", ";
    
    cout << "]" << endl;


void destruct_tree(TreeNode * root) 
    if (root == nullptr) return;
    destruct_tree(root->left);
    destruct_tree(root->right);
    delete root;


//
// This function generates trees based on the given consequencial numbers
// 
// Parameters:
//      int beg       the begin number 
//      int end       the end number 
// Return:
//      A vector of TreeNode *
//      Each element is one tree's root 
//      Each tree contains all the numbers from beg to end 
//      Each number in [beg, end] could be root 
//
vector<TreeNode *> _genTreesBasedOnNums(int beg, int end) 
    vector<TreeNode *> trees;
    
    // Not possible, add nullptr to tree
    if(beg > end) 
        trees.push_back(nullptr);
        return trees;
    
    // Only one element in the subtree, so just add it
    if(beg == end) 
        trees.push_back(new TreeNode(beg));
        return trees;
    
    
    for(int i=beg; i<=end; i++) 
        // Each element in the 2 vectors stands for one Tree 
        vector<TreeNode *> leftTrees  = _genTreesBasedOnNums(beg, i-1);
        vector<TreeNode *> rightTrees = _genTreesBasedOnNums(i+1, end);
        
        for(int j=0; j<leftTrees.size(); j++) 
            for(int k=0; k<rightTrees.size(); k++) 
                TreeNode * root = new TreeNode(i);
                root->left = leftTrees[j];
                root->right = rightTrees[k];
                trees.push_back(root);
            
        
    
    return trees;


vector<TreeNode *> generateTrees(int n) 
    return _genTreesBasedOnNums(1, n);



int main()

    vector<TreeNode *> trees = generateTrees(3);
    print_trees(trees);
    
    for (auto & pTreeNode : trees) 
        destruct_tree(pTreeNode);
    
    
    return 0;

运行效果如下:

$ ./a.exe
There are 5 trees generated
[[1, null, 2, null, 3], [1, null, 3, 2], [2, 1, 3], [3, 1, null, null, 2], [3, 2, null, 1]]

本题的解法还是有点惊艳的。
普普通通的一套太祖长拳,直接打飞了江湖第一流的高手。
太祖长拳的取胜,在于更深的内力与从容的法度;
本题的解法,在于对更高层次递归的理解与运用。

(END)

以上是关于全列举二叉搜索树 - 以太祖长拳打飞江湖第一流高手的主要内容,如果未能解决你的问题,请参考以下文章

恋上数据结构与算法第一季笔记——二叉搜索树

Leetcode刷题100天—700. 二叉搜索树中的搜索( 二叉树)—day34

Leetcode刷题100天—700. 二叉搜索树中的搜索( 二叉树)—day34

寻找二叉搜索树错误的节点

LeetCode Algorithm 530. 二叉搜索树的最小绝对差

乱序版 ● 剑指offer每日算法题打卡题解—— 二叉搜索树 (题号36,54)