[转载]一个简单问题引起的“血案”(白云黄鹤)

来源:互联网 发布:霍布斯鲍姆 知乎 编辑:程序博客网 时间:2024/06/10 04:49

                 一个简单问题引起的“血案”

排列、组合问题是大家再熟悉不过,通过公式可以轻而易举地求出C(n, m)、P(n, m),本文
将就组合问题介绍一些在时间复杂度和空间复杂度上去对程序进行优化的方法。
地球人都知道C(n, m) = C(n – 1, m – 1)  + C(n – 1, m),根据这个递归性质,用C++
程序递归实现如下:
//1.cpp
#include <iostream>                                                                                                    
using namespace std;                                                                                                   
                                                                                                                       
int com(int n, int m)                                                                                                  
{                                                                                                                      
    if(m == 0) return 1;
    if(n == m) return 1;                                                                                               
    return com(n - 1, m - 1) + com(n - 1, m);                                                                          
}
                                                                                                                       
int main()
{
    int a, b;

    while(cin >> a >> b && (a != 0 || b != 0))
        cout << com(a, b) << endl;
    return 0;
}
对于一个初学编程的人来说,这个程序几乎完美了(至少我第一次写出这个程序的时候,很
有成就感&#61514;)。
值得一提的是,当数据规模很大这样的递归会出现stack overflow的情况,下面我将举例说
明:
//2.cpp
#include <iostream>
using namespace std;                                                                                                   
/*
  初看一下这个程序好象很无聊(确实是很无聊),a, c都没变,没必要作为函数参数,
  实际上这里我只是说明一个问题,当数据规模很大的时候,也就是stack深度很大的时候
,会出现栈溢出的情况,
  解决方案是利用一个全局的stack给出非递归实现(留给你去做吧) 。
*/
                                                                                                                       
void f(int a, int b, int c)
{
    cout << a << " " << b << " " << c << endl;

    if(b != 1)
      f(a, b - 1, c);
}

int main()
{
    f(1, 100000, 2);//由于stack overflow,程序被强迫终止

    while(1);//这句话没什么作用因为程序被中断了
    return 0;
}


下面将从时间和空间复杂度上对程序进行优化:

仔细分析下Recursion tree可以发现,计算机做了很多重复的事情,例如在计算C(7,6) =
C(6, 5) + C(6, 6) 、C(7, 5) = C(6, 5) + C(6, 4)时候,C(6,5)被重复计算了,如何减少
重复计算只需要保存下C(6, 5)的值,下次就不需要再计算了,取出该记录就可以了。。。                                        
。。
程序实现如下
//3.cpp
#include <iostream>
using namespace std;

const int MAX = 100;

int result[MAX][MAX];

int c(int n, int m)
{
    if(result[n][m] > 0) return result[n][m];
    if(m == 0) return result[n][m] = 1;
    if(n == m) return result[n][m] = 1;
    return result[n][m] = c(n-1, m)+ c(n-1, m-1);
}
int main()
{
    int a,b;

    while(cin >> a >> b)
        cout << c(a,b) << endl;
}
这里展现的记忆化搜索的核心思想,1.cpp和3.cpp分别输入30 ,10,运行速度就有明显的差
距(当然这里还可以用递推实现,It is your turn to implement it,后面我会给出一个简
化版本)。
上面的程序3.cpp,空间复杂度似乎还是令人感到头疼(这里不讨论高精度问题),仔细观察
一下递归方程C(n, m) = C(n – 1, m – 1)  + C(n – 1, m),递推的时候,只需要保存
前排的结果就可以。。。。。
//4.cpp
#include <iostream>
#include <algorithm>
using namespace std;

const int MAX = 100;

int record[MAX][2];

bool solve()
{
    int a, b;
    cin >> a >> b;
    if(a == 0 && b == 0 ) return false;

    int V1, V2;
    V1 = 0, V2 = 1;

    record[0][V1] = 1;
    record[1][V1] = 1;

    for(int i = 2; i <= a; i++)
    {
        record[0][V2] = record[i][V2] = 1;
        for(int j = 1; j < i; j++)
        {
            record[j][V2] = record[j-1][V1] + record[j][V1];
        }

        swap(V1, V2);
    }

    cout << record[b][V1] << endl;
    return true;
}

int main()
{
    while(solve());
    return 0;
}
上面的例子实际上展示了动态规划里面所谓的滑动数组(Sliding array),从而大大地降低
了空间复杂度,(当然还会有更好的策略)。
本文纯属个人观点,希望阅读者取其精华,去其糟粕,如有更好的想法一定要拿出来跟大家
一起分享哦:)
ps:本文题目纯属抄作
---------------------------
END
                                        
 

原创粉丝点击