Chinaunix首页 | 论坛 | 博客
  • 博客访问: 493481
  • 博文数量: 25
  • 博客积分: 111
  • 博客等级: 民兵
  • 技术积分: 1279
  • 用 户 组: 普通用户
  • 注册时间: 2012-10-26 20:51
文章分类

全部博文(25)

文章存档

2014年(17)

2013年(8)

分类: Python/Ruby

2014-05-13 16:07:22

    该去找工作了:),把以前看过的一些东西总结下。这次主要是对三个字符串问题的总结,即:最长公共子序(LCS)、最长递增子序(LIS)以及编辑距离(CSD)。这三个问题都出在算法导论动态规划一章,同时后两个问题也出现在编程之美中(编程之美中的一些题都是出自算法导论:))。这篇博客的思路是按着动态规划的“标准思路”一步一步的给出解答,最后给出代码。

动态规划的一般思路是分为四步,即:寻找最有子结构、递归定义最有子结构,自底向上求解最有子结构和构造最优解。其中,在第三步时,我们也可以自顶向下进行求解,但此时需要对解需要进行记录。

一、LCS :求两个序列的最长公共子序。

1、寻找最优子结构

假设待求解的两个序列为XnYm,我们用L{XnYm}表示最长公共子序。我们知道,如果我们已经知道了L{Xn-1Ym-1}的最长公共子序,则L{XnYm}=L{Xn-1Ym-1}+1或者L{XnYn}=max{L{Xn-1Ym},L{XnYm-1}}。也就是说,最长公共子序和其子问题有关。

2、递归定义最优子结构

最有子结构的顶一下如下:

3、自底向上求解最优解,代码如下:

点击(此处)折叠或打开

  1. def longest_common_subsequence(lhs,rhs):
  2.     l_len = len(lhs);
  3.     r_len = len(rhs);
  4.     matrix = zeros((l_len,r_len));
  5.     for i in arange(l_len):
  6.         for j in arange(r_len):
  7.             if lhs[i] == rhs[j]:
  8.                 if i != 0 and j != 0:
  9.                     matrix[i][j] = matrix[i-1][j-1]+1;
  10.                 else:
  11.                     matrix[i][j] = 1;
  12.             elif i != 0 and j != 0:
  13.                     matrix[i][j] = max(matrix[i-1][j],matrix[i][j-1]);
  14.     return matrix;

4、构造最优解,如下:

点击(此处)折叠或打开

  1. def print_longest_common_subsequence(lhs,rhs):
  2.     matrix = longest_common_subsequence(lhs,rhs);
  3.     l_len = len(lhs);
  4.     r_len = len(rhs);
  5.     i = l_len-1;j=r_len-1;
  6.     rst = [];
  7.     while j>0 and i >0:
  8.         if matrix[i-1][j] != matrix[i][j] and matrix[i][j-1] != matrix[i][j]:
  9.             rst. insert(0,lhs[i]);
  10.             #print(i,j);
  11.             j = j-1;
  12.             i = i-1;
  13.         elif matrix[i-1][j] == matrix[i][j]:
  14.             i = i-1;
  15.         else:
  16.             j=j-1;
  17.     if j!=0 and i== 0:
  18.         if matrix[i][j-1] != matrix[i][j]:
  19.             rst. insert(0,lhs[i]);
  20.     elif j==0 and 0:
  21.         if matrix[i][j] != matrix[i-1][j]:
  22.             rst. insert(0,lhs[j]);
  23.     return rst;

二、LIS:求序列的最长递增子序。

1、寻找最优子结构

我们设待求的序列为Xn,同样的如果我们已经求得了前n-1个序列的最长递增子序列,那么Xn的最长递增子序列要么等于前n-1个的最长递增子序,要么等于其加1。这里的问题有些复杂,复杂的原因就在于,前n-1个序列的最长递增子序,可能不止一个,设其长度为Ln-1。面对这种情况,我们只需用Xn的最后的元素与递增序列长度为Ln-1的最长序列的最小值进行比较。

2、递归定义最优解结构

我们递归定义最优接结构,如下:

因为序列的每一项,都可以构成长度为1的递增自序列,所以最小值为1.

3、自底向上求解最优解(与算法导论上的方法一样,只是没加记录项),代码如下:

点击(此处)折叠或打开

  1. def longest_increasing_subsequence(tmp_str):
  2.     tmp_len = len(tmp_str);
  3.     LIS = [1 for i in range(tmp_len)];
  4.     for i in range(tmp_len):
  5.         j=0;
  6.         while j <= i:
  7.             if (tmp_str[i] > tmp_str[j]) and (LIS[i] < LIS[j]+1):
  8.                 LIS[i] = LIS[j]+1;
  9.             j = j+1;
  10.     return max(LIS);

注意:这个简单实现,其复杂度为O(n2),我们对其进行改进,使其时间复杂度为 O(nlog(n)),如下:

点击(此处)折叠或打开

  1. def binary_search(tmp_str,begin,end,key):
  2.     if begin == end:
  3.         return begin;
  4.     middle = (begin+end)/2;
  5.     if tmp_str[middle]==key:
  6.         return middle;
  7.     elif tmp_str[middle]>key:
  8.         return binary_search(tmp_str,begin,middle,key);
  9.     else:
  10.         return binary_search(tmp_str,middle+1,end,key);
  11. def improved_LIS(tmp_str):
  12.     tmp_len = len(tmp_str);
  13.     LIS = [1 for i in range(tmp_len)];
  14.     MaxV = [0 for i in range(tmp_len)];
  15.     nMaxLIS = 0;
  16.     for i in range(tmp_len):
  17.         j = binary_search(MaxV,0,nMaxLIS,tmp_str[i]);
  18.         if tmp_str[i] > MaxV[j]:
  19.             LIS[i] = j+1;
  20.         elif j>1:
  21.             LIS[i] = j;
  22.         if LIS[i] > nMaxLIS:
  23.             nMaxLIS = LIS[i];
  24.             MaxV[LIS[i]]=tmp_str[i];
  25.         elif tmp_str[i] > MaxV[j] and tmp_str[i] < MaxV[j+1]:
  26.             MaxV[j+1] = tmp_str[i];
  27.     return max(LIS),LIS;

4、构造最优解,如下:

点击(此处)折叠或打开

  1. def print_longest_increasing_subsequence(tmp_str):
  2.     nMax,tmp_list = improved_LIS(tmp_str);
  3.     mIndex = index = tmp_list.index(nMax);
  4.     rst = [0 for i in range(nMax)];
  5.     rst[0] = tmp_str[mIndex];
  6.     j = 1;
  7.     for i in range(mIndex):
  8.         if tmp_list[mIndex-i] == nMax-1 and \
  9.            tmp_str[mIndex-i]<tmp_str[index]:
  10.             index = mIndex-i;
  11.             rst[j] = tmp_str[index];
  12.             j=j+1;
  13.             nMax = nMax-1;
  14.             if nMax == 1:
  15.                 return rst;
  16.     return rst;

三、CSD:编辑距离

1、寻找最优子结构

设两个序列为:XnYm,编辑距离为:L{XnYm},为了简单起见,我们假设每个操作的代价都是相同的。在进行操作的第一步时,我们可以进行如下操作,即:

1)删除Xn的一个字符,此时需要求解的是L{Xn-1Ym}

2)删除Ym的一个字符时需要求解的是L{XnYm-1}

3)Xn前面添加一个和Ym第一个相同的字符,此时需要求解的是L{XnYm-1}

4)Ym前面添加一个和Xn第一个相同的字符,此时需要求解的是L{Xn-1Ym}

5)修改Xn的第一个字符为Ym的第一个字符,此时需要求解的是Ln{Xn-1Ym-1}

6)修改Ym的第一个字符为Xn的第一个字符,此时需要求解的是Ln{Xn-1Ym-1}

从上面的分析中,我们可以看到,第一步之后只会存在三种情况,即:L{XnYm-1}L{Xn-1Ym}L{Xn-1Ym-1}

2、递归定义最优解结构

3、我们这次不再用自顶向上的求解法,而是采用带备忘录的解法,如下:


点击(此处)折叠或打开

  1. def longest_common_subsequence(lhs,rhs):
  2.     l_len = len(lhs);
  3.     r_len = len(rhs);
  4.     matrix = zeros((l_len,r_len));
  5.     for i in arange(l_len):
  6.         for j in arange(r_len):
  7.             if lhs[i] == rhs[j]:
  8.                 if i != 0 and j != 0:
  9.                     matrix[i][j] = matrix[i-1][j-1]+1;
  10.                 else:
  11.                     matrix[i][j] = 1;
  12.             elif i != 0 and j != 0:
  13.                     matrix[i][j] = max(matrix[i-1][j],matrix[i][j-1]);
  14.     return matrix;


通过寻找最优子结构我们知道,有些操作会得到相同结果,所以具体的求出怎么操作的比较困难,如果每个操作所花费的代价不同的话,求解具体的操作会容易些。
本文出自:http://blog.chinaunix.net/uid-28311809-id-4251024.html

阅读(16250) | 评论(1) | 转发(1) |
给主人留下些什么吧!~~

CU博客助理2014-07-11 15:38:43

专家点评:赞博主汇总整理了扎实的理论基础, 期待来日能有机的利用起来,期待通俗实用的博文发布。(感谢参与原创评选活动,结果即将公布)