动态规划法和贪心算法

 

动态规划法和贪心算法

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;
        }
}



 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值