五大经典算法

发布时间:2024-11-21 00:42

阅读经典编程书籍,如《算法导论》理解算法原理 #生活知识# #科技生活# #编程学习#

前言

整篇文章分析整个动态规划算法,什么是动态规划,及动态规划算法在字符串匹配中使用、分治法的差别点、动态规划优点;

概念

什么叫做动态规划(dynamic programming),它是运筹学的一个分支,是求解决策过程最优化的数学方法。20世纪50年代数学家 R.E.Bellman等人 在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理,把多阶段过程转换为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法-动态规划;应用在计算最短路径、库存管理、资源分配、设备更新、排序、装载等问题

特点

分析是由大到小,写代码是从小到大,计算过程中会把结果进行记录,最终结果在记录中找到

怎么去理解这个,从下面的基本思想中理解,我觉得大家就能理解了

基本思想 

动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式。

基本代码的实现

分治法一个简单的实现,之前用栈去解决斐波拉契数列这些问题,现在我们用动态规划去解决问题,用空间复杂度去换时间复杂度

原有栈的方式

public static int f(int n){

if(n==1 || n==2){

return 1;

}else{

return f(n-1)+f(n-2);

}

}

这种方式代码简单,但性能是非常不好的,一旦数据量大了之后这种方式时间消耗相当大,尤其是某些业务场景下,要求效率非常高;因此引入下面的代码动态规划,时间效率提高

public static int sum1(int j) {

int[] array = new int[j];

array[0] = 1;

array[1] = 1;

for (int i = 2; i < array.length; i++) {

array[i] = array[i - 1] + array[i - 2];

}

return array[j - 1];

}

这就是通过动态规划去求最优化的解,这段代码的实现,小问题的结果相关联。分析过程是由大到小,而算法是由小到大的,通过存储空间记录,我们就能得到答案

总结

动态规划是求解具有某种最优性质的问题(子问题不是相互独立),并且和分治法一样都是将问题进行拆分,并且将拆分问题答案进行记录,从计算出最优的结果。明显可以看出动态规划是会多占用内存的,以空间复杂度来换取问题答案。

算法应用

最长公共子序列(Longest Common Subsequence,lcs)

这个应用在生活中应用的非常多,例如当前这个应用,就是对比数据的相似,截取公共部分,百度知道,相似度的比较:计算生物学DNA对比,图形相似处理,媒体流的相似比较,WEB页面中找非法言论等等

定义

一个序列A删除若干个字符得到新的序列B,而B就是A的子序列

 这样一删除字符过后就能获得到子序列

两个序列X和Y的公共子序列中,长度最长的那个,定义为X和Y的最长公共子序列

  例如X={A,B,C,B,D,A,B},Y={B,D,C,A,B,A}则它们的lcs是{B,C,B,A}和{B,D,A,B}。求出一个即可。

这和顺序有关,相同的值的最大长度,并不管位置。怎么用动态规划去求出最长公共子序列,这就是我们动态规划去研究的,除了动态规划,我们还有什么方法,那当然是穷举法,这个最恶心,在于拿着一个字符一个字符,一位一位的去比对,就会找到,但性能糟糕,没法用的。

算法分析

我们用下面例子进行分析

X={B,D,C,A,B,A},Y={A,B,C,B,D,A,B}则它们的lcs是{B,C,B,A}和{B,D,A,B}。求出一个即可。

我们用上面 的来表示 ,比如Y4=B X4=A  通过这样来分析lcs求解 

 字符串X,长度m,从1开始计数字符串Y,长度n,从1开始计数Xi=<x1,x2,...,xi>表示X序列的前i个字符(1<=i<=m)Yj=<y1,y2,...,yj>表示Y序列的前j个字符(1<=j<=n)LCS(X,Y)为字符串X和Y的最长公共子序列,其中的一个解为Z=<z1,z2,....zk>  注意:LCS(X,Y)(最长公共子序列)其实为一个集合,Z为一个解

就像我在刚才分析那样,最长的公共子序列有多个解,所以是一个集合来存储,而Z就是其中的一个解

思路解析

首先取两个字符串的末尾进行取一个字符对比

 如果两个字符串不相等则有两种情况下面

第一种X去除末尾字符进行对比

 Y去掉末尾字符进行对比

 如果最后一位是相同的,那就是下面种情况

 就这样重复往前步骤,就是这样找到了最长子公共序列

 最终得到的结论就是

 这个结论

如果 最后一位相等,上面个公式,最长子公共序列加上相同的xm 或者 ym如果最后一位不相等,取xm-1和yn的公共子序列和 xm和yn-1的公共子序列的最大的公共子序列,这就能找到最长公共子序列 代码分析与编码

我们需要建立一个二维数组,相同的取左上加1,不同取上和左的最大值 ,这也是从上面

 首位都填空字符串,都不填位置,

 

 按照规则不断填,就能填出上面的值,然后就能找到公共子序列的长度。这就是动态规划的算法,从前往后推,记录好之前的数据,不断往前推,而在去取长度。有规律,只要数据加长一位时,则表明相同的字符。

我们继续寻找最长的公共子序列,从后往前推,这样就能找到一个公共子序列,ABCB ,在反过来就是了 和BADB 

 先从建立一个小方法开始

写一个字符长度不同的时求最大值

public static int max(int a,int b){

return (a>b)?a:b;

}

 建立核心方法getLCS 并创建一个二维数组 传入 x 和y字符串,用于后面的操作

char[] s1=x.toCharArray();

char[] s2=y.toCharArray();

int[][] array=new int[x.length()+1][y.length()+1];

 然后就按照我之前构造的数组,做个数据填充,当然也可以不填充

for (int i = 0; i < array[0].length; i++) {

array[0][i]=0;

}

for (int i = 0; i < array.length; i++) {

array[i][0]=0;

}

使用动态规划的方式填入数据,一格一格判断字符串 这里需要减一次比较 ,s1和s2是原字符,相等因为在数组上数据是从1开始的,所以不能按照数组来。就已经能得到刚才的矩阵了

for (int i = 1; i < array.length; i++) {

for (int j = 1; j < array[i].length; j++) {

if(s1[i-1]==s2[j-1]){//如果相等,左上角加1填入

array[i][j]=array[i-1][j-1]+1;

}else{

array[i][j]=max(array[i-1][j],array[i][j-1]);

}

}

}

然后继续往前找结果,去找到数据,先搞一个栈进行存储,因为是反过来

Stack result=new Stack(); 往前找数据

int i=x.length()-1;

int j=y.length()-1;

while((i>=0) && (j>=0)){

if(s1[i]==s2[j]){

result.push(s1[i]);

i--;

j--;

}else{//注意数组和String中的位置有一位差

if(array[i+1][j+1-1]>array[i+1-1][j+1]){

j--;

}else{

i--;

}

}

}

这样求最长公共字串我们就能找到了,动态规划就能实现了

KMP算法(改进的字符串匹配算法)

kmp算法和lcs算法不同点在于撒,也就是lcs算法,在匹配两个字符串的相似度,而kmp算法也就是我们在java种字符串进行匹配,只有字符串在目标字符串中能找到时,才算是ok的。

是一种改进的字符串匹配算法,由由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特-莫里斯-普拉特操作(简称kmp算法)

他的时间复杂度能达到o(m+n) 是相当快的, KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。

采用穷举法则是下面的情况

 这是大家都能想通的方式,但可以想象,我们jdk肯定不能这么简单的,那效率速度有多慢。

还有一种

  

将目标串先进行循环 对比的方式,最后两位和前面两位相同,然后下次倒退,倒退到前ab然后继续对比,这样也很消耗 性能,效率也很慢的。

接下来介绍kmp算法如何做的

既然利用动态规划的特性,需要用一个数组来帮我们存储帮助我们找到回退的次数的 假设为倒退数组next

规则

第一步 使得next[i]=j

第二步 i++

第三步判断i和j的字符是否相等

i和j的字符相等 j++; 

i和j的字符不同则 j=next[j-1];

下面继续分析字符串

 首先进行复制一个相同模式串,第一步先在next数组中填0,i从1开始,j从0开始,遇到不同,直接j++

第二次遇到数据相同了,先赋值next,在j++

这样以此类推下去 

 j是不断在前三个做循环,然后 这样记录下了循环的动作,而i不断增加;

最后这个数组得到的值的特点,进行对比

 这种情况可以看到几个值相同的情况,这种,我就能发现在对比字符串时,如果对比不成功,就对比到退到哪一个节点,这就能很好的节约时间

 这个算法就是这样不断对比往里面推,一旦遇到不同 j=next[j-1]  这是j的值如果为3就跳到第三位字符上,截取过后与目标字符串进行对比,以达到成功的情况

代码实现

首先匹配串找到next数组建立kmpnext算法

public static int[] kmpNext(String dest){

int[] next=new int[dest.length()];

next[0]=0;

//开始推出next

for(int i=1,j=0;i<dest.length();i++){

//3

while(j>0 && dest.charAt(j) != dest.charAt(i)){

j=next[j-1];

}

//1

if(dest.charAt(i)==dest.charAt(j)){

j++;

}

//2

next[i]=j;

}

return next;

}

这里做的是,先创建next数组

int[] next=new int[dest.length()]; 重复下面的规则

第二步 i++

第三步判断i和j的字符是否相等

i和j的字符相等 j++; 

i和j的字符不同则 j=next[j-1];

第一步 使得next[i]=j

//判断字符是否相同的或者不同

while(j>0 && dest.charAt(j) != dest.charAt(i)){

j=next[j-1];

}

if(dest.charAt(i)==dest.charAt(j)){

j++;

}

//

next[i]=j;

然后实现kmp算法 

for(int i=0,j=0;i<str.length();i++){

while(j>0 && str.charAt(i) != dest.charAt(j)){

j=next[j-1];

}

if(str.charAt(i)==dest.charAt(j)){

j++;

}

if(j==dest.length()){//结束

return i-j+1;

}

}

结束条件,最后通过i-j+1就能找到需要的位置,这个思路是相当精妙的;记录到快速找到左边连续的点。用来做回退的。

总结

动态规划算法应用非常广泛的,学习它的思路,在开发过程中,大家都有一个好的解决方法的思路,不要只是就用什么穷举法呀,大家都能想到,但是效率非常低,在项目中是不能使用的,小数据量还行,一旦数据量增大,时间非常长,希望这篇动态规划,通过原理和应用上讲解,能有成长。

网址:五大经典算法 https://www.yuejiaxmz.com/news/view/164170

相关内容

Python实现经典还钱问题算法:优化财务管理的编程技巧
生活学习计划(经典十五篇)
十大经典数学小故事 数学趣味小故事 关于数学的经典故事
川菜经典蒜泥白肉的做法
刘先银经典点说:《黄帝内经》五十经典经文隐藏着深远的育儿智慧
七大经典高效学习方法
十大古代经典法治故事 中国古代法治典故 古代关于法治的小故事
民法典
国内五条自驾游经典路线
育儿宝典(绝对经典)

随便看看