动态规划法和贪心算法

来源:互联网 发布:mac下载的系统在哪 编辑:程序博客网 时间:2024/05/16 23:58

 

动态规划法和贪心算法

csdn第一篇blog

先说说写文章的好处:

第一,写文章是个学习的过程。写的过程中随着自己的思路的进行,会出现理解不清楚的地方,自然就会翻书或者google的搞明白。

第二,写文章也可以锻炼自己的表达能力。用最简单语言描述一个问题,这个能力,我想无论什么工作都是很重要的。

第三,写文章可以对问题的认识提升一个高度。通过写文章可以整理的自己的思路,引发自己的思考,联系以前的问题,也是一个总结的过程。我的老师的老师曾经对我的老师说过一句话智慧在于联系,这句话原来其实是一句很牛X的英文,翻译过来就比较二了。要想成为智者,除了联系外,还有一句更关键的,牛X的顿说的,“standing on the shoulders of giants”

好了,进入正题。只是写下我对这两个算法的认识和理解,如果对这两个算法以前没什么了解,那么读起来可能就费劲了,毕竟算法导论上上万字的讲解,我这只言片语,也讲不清楚。所以只是写了自己的一点理解,可能片面,也可能错误,仅供参考,如有任何见教,谢谢指正

以下引用算法导论P192动态规划(dynamic programming)是通过组合子问题的解而解决整个问题的,与分治法相比,动态规划法的子问题不是独立的,解的过程中,一个子问题的解可能会用到另外一个子问题的解,为了不重复的解这些子问题,自底向上,解出子问题,并把这些子问题的解可先存储下来,以后通过查找而不用重复计算,从而大大降低时间复杂度。可能从指数的级别(2n次方),(ps:n很大的时候指数需要的运算次数是特别不靠谱的)降到了n的次方级别(eg:n3次方)。

典型的问题有求两个序列的最长公共子序列,最短路径,矩阵链乘法,还有我记得通信原理上的一个叫什么维比特解码的解码方法,貌似也是动态规划法,其实思想很简单,与递归相比,只是通过自底向上解子问题,存储子问题的解,就是动态规划了。

判断是不是可以用动态规划解的可以看看这个问题的n+1规模的解是不是用到了n规模的解,(但不是完全的依赖,也可能需要n规模的时候的次优解)。其实判断可不可以用动态规划来解决问题也不难~难的就是贪心算法了。

贪心算法可以解决的问题都可以用动态规划法来解决。(为什么还要研究贪心算法呢?贪心算法时间复杂度很低)贪心算法是通过做一系列的选择来给出某一问题的最优解。对算法中的每一个决策点,做一个当时(看起来是)最佳的选择,算法导论上的原话,这个看起来是就难了。怎么看,怎么判断是还是不是(如果这个问题不可以用贪心算法,用贪心算法解出来的问题就不是最优解)。导论上也没给出什么归纳总结类方法,我觉得,这个也没有什么高级的方法论,必须是具体问题具体分析,如果人家告诉你了可以用贪心算法(概率很小),那么,就简单ok了,如果一个问题没告诉可以用贪心算法,那么证明这个问题,或者想这个问题是不是可以用,可以用贪心算法,有的时候就恶心了。解决方法就是,抓住问题的特征,然后还是那就话,具体问题,具体分析。

贪心算法是自顶向下的,时间复杂度又大大的降低(可以到nlogn),也不用很多存储空间。典型问题有通信原理上的霍夫曼编码,还有像尽可能的多安排一系列时间有冲突的活动的问题。可以用贪心解决的问题还是有一些特征的,给出的问题的中的值是可排序的,例如没个活动的结束时间,霍夫曼编码时候字符出现的次数,而且也满足动态规划法的特征,n+1维(排序后)的解需要n维的解。

动态规划法解决最长公共字串的问题如下:

1)不连续字串

#include <iostream>#include <assert.h>using namespace std;enum direction { dir_init = 0, dir_left, dir_up, dir_left_up };int LCS (char *pStr1, char *pStr2);void LCS_print (direction ** LCS_direction, const char *pStr1, int length1, int length2);int main (int argc, char *argv[]){  char str1[] = "hello world";  char str2[] = "ehlo ord";  printf ("\"%s\" and \"%s\" common str is\n", str1, str2);  printf ("\nthe length of common str is %d\n", LCS (str1, str2));  return 0;}int LCS (char *pStr1, char *pStr2){  assert ((pStr1 != NULL) && (pStr2 != NULL));  int length1 = strlen (pStr1) + 1; //注意这里,长度矩阵和方向矩阵都是length1+1,length2+1的,方向矩阵有一点冗余,但方便编程  int length2 = strlen (pStr2) + 1;  if (!length1 || !length2)    return 0;  int **LCS_length = new int *[length1];  for (int j = 0; j < length1; j++)    LCS_length[j] = new int[length2] ();  direction **LCS_direction = (direction **) (new direction[length1]);  for (int j = 0; j < length1; j++)    LCS_direction[j] = new direction[length2];  for (int i = 0; i < length1; i++) //初始化时候,也跟整型似的,在后面加个(),貌似也可以    for (int j = 0; j < length2; j++)      LCS_direction[i][j] = dir_init;  for (int i = 0; i < length1; i++)    for (int j = 0; j < length2; j++)      {        if (i == 0 || j == 0)   //这里不要写成多个if, else,写成if, else if, else if 的形式代码整洁                LCS_length[i][j] = 0;        else if (pStr1[i-1] == pStr2[j-1])          {            LCS_length[i][j] = LCS_length[i - 1][j - 1] + 1;            LCS_direction[i][j] = dir_left_up;          }        else if (LCS_length[i - 1][j] > LCS_length[i][j - 1])          {            LCS_length[i][j] = LCS_length[i - 1][j];            LCS_direction[i][j] = dir_up; //这里,left和up非常容易出错          }        else          {            LCS_length[i][j] = LCS_length[i][j - 1];            LCS_direction[i][j] = dir_left;          }      }  for (int i = 0; i < length1; i++) //这个for循环用来查看长度矩阵,可以省略  {    for (int j = 0; j < length2; j++)            printf("%d ", LCS_length[i][j]);    printf("\n");  }  LCS_print (LCS_direction, pStr1, length1, length2);  int ret = LCS_length[length1 - 1][length2 - 1];  for(int i=0; i < length1; i++)  {        delete[] LCS_length[i];        delete[] LCS_direction[i];  }  delete[] LCS_length;  delete[] LCS_direction;  return ret;}void LCS_print (direction ** LCS_direction, const char *pStr1, int length1, int length2){  if (length1 == 1 || length2 == 1)    return;  if (LCS_direction[length1 - 1][length2 - 1] == dir_left_up)    {      LCS_print (LCS_direction, pStr1, length1 - 1, length2 - 1);      cout << pStr1[length1-2]; //注意,这里字符串字符位置和方向矩阵的关系,这里方向矩阵大小为length1+1,length2+1的    }  else if (LCS_direction[length1 - 1][length2 - 1] == dir_up) //注意这里,方向容易出错,写程序最好画出来矩阵图    LCS_print (LCS_direction, pStr1, length1 - 1, length2);  else if (LCS_direction[length1 - 1][length2 - 1] == dir_left)    LCS_print (LCS_direction, pStr1, length1, length2 - 1);}

2)连续字串

#include <stdio.h>#include <stdlib.h>#include <string.h>int LCS(char *pStr1, char *pStr2, char **pCommon);  //找出pStr1,和pStr2的公共连续字串,保存在common中int main(int argc, char *argv[]){        char str1[] = "hello world";        char str2[] = "orl";        char *pCommon = NULL;        int len = LCS(str1, str2, &pCommon);        printf("\"%s\" and \"%s\" continuous common str is %s, len is %d\n", str1, str2, pCommon, len);        return 0;}int LCS(char *pStr1, char *pStr2, char **pCommon){        int LCS_len;        int str1_len;        int str2_len;        int i;        int j;        str1_len = strlen(pStr1);        str2_len = strlen(pStr2);        int *a = (int *)calloc(str1_len, sizeof(int));        for(i=0; i<str2_len; i++)        {                for(j=str1_len-1; j>0; j--) //这里,一定要是从字符串后到前,否则矩阵如果前面被修改,后面用到的结果就不是上一次的了                {                        if(pStr2[i] == pStr1[j])                        {                                if(j == 0)                                        a[j] = 1;                                else                                        a[j] = a[j-1] + 1;                        }                }        }        LCS_len = a[0];        int offset = 0;        for(j =1; j<str1_len; j++)        {                if(a[j] > LCS_len)                {                        LCS_len= a[j];                        offset = j;                }        }        *pCommon = (char *)calloc(LCS_len+1, sizeof(char));        memcpy(*pCommon, &pStr1[offset-LCS_len+1], LCS_len);        (*pCommon)[LCS_len] = '\0'; //如果不加括号,那么可能是先 pCommon[LCS_len]在解引用,会段错误        return LCS_len;}


3) 求字符串最长不含重复字符的子串长度。貌似是趋势科技的一个题目,初步感觉用动态规划法,但是我用的效率不是很高,勉强能做出来。不会算法导论那样严谨的推理,简单描述下。这里分两种情况,一个字符串的最长不含重复字串在这个串的结尾部分和在中间部分,假如用一个变量记录下了长度len和offset,假设此时规模为n,那么这个串的大一个规模的即n+1规模下,最长不含重复字串也是可能在尾部,也可能在中间,那么这个时候算一下从尾部开始的最长不含重复字串,看看是不是比n规模的len大,大于等于的话就把len和offset更新了,否则还是在中间,还是n规模的解。OK~
写算法的时候,可以规模可以从尾部开始,当规模为1的时候,最长不含重复字串为这个字符串中最后的一个字符,然后计算规模为2,即字符串为最后两个字符的时候,最后扩大规模,一直到整个字符串。这样写有个好处,因为结尾为'\0',结束条件容易写。上代码

#include <iostream>#include <assert.h>using namespace std;//----------------字符串最长不含重复字符的子串长度-----------int LNR(char *pStr, char **result);int len_no_repeat_char(char *s); //从s开始到字符串结束,最长不含重复字符长度int main(){        char s[] = "hahello";        char *result = NULL;        LNR(s, &result);        printf("%s\n", result);        return 0;}int LNR(char *pStr, char **result){        assert(pStr != NULL);        int LNR_len = 0;        int offset = 0;        int len = strlen(pStr);        for(int i = len-1; i>=0; i--)        {                int temp;                temp = len_no_repeat_char(&pStr[i]);                if(temp > LNR_len)                {                        offset = i;                        LNR_len = temp;                }        }        *result = new char[LNR_len + 1];        strncpy(*result, &pStr[offset], LNR_len);        return LNR_len;}int len_no_repeat_char(char *s){        int a[128] = {0};        int i = 0;        int len = 0;        while(s[i])        {                if(a[s[i]] == 0)                {                        len++;                        a[s[i]]++;                        i++;                }                else                        return len;        }}



 

 

原创粉丝点击