算法分析与设计论文

算法分析与设计论文1:递归算法程序直接或间接调用自身的编程技巧称为递归算法(Recursion)。递归算法是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法。它通常把一个大型复杂的问题转化为一个与原问题

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

1:递归算法

程序直接或间接调用自身的编程技巧称为递归算法(Recursion)。

递归算法是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法。它通常把一个大型复杂的问题转化为一个与原问题类似的规模较小的问题来求解。

递归策略只需少量的代码就可描述出解题过程所需要的多次重复计算,大大减少了程序的代码量。递归的优势在于用有限的语句来定义对象的无限集合,用递归思想写出的程序往往十分简洁易懂。

递归需要有边界条件,递进前进段和递归返回段,当边界条件不满足时,递归前进;当边界条件满足时,递归返回(使用递归时,不必须有一个明确的递归出口,否则递归将无限进行下去)。

递归算法解题的运行效率较低,在递归调用过程中,系统为每一层的返回点,局部变量等开辟了堆栈来储存。递归次数过多容易造成堆栈溢出等。

例:Fibonacci数列

“菲波那切数列”是意大利数学家列昂纳多-斐波那契最先研究的一种递归数列,他的每一项都等于前两项制盒次数列的前几项为1,1,2,3,5等。在生物数学中许多生物现象都会出现菲波那切数列的规律,斐波那契数列相邻两项的比例近于黄金分割数,其递归定义为:

算法分析与设计论文

Fibonacci数列的递归算法:

int fib(int n)

{

   if (n<=1) return 1;

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

}

算法效率非常低,重复递归的次数太多,通常采用递推算法:

 

int  fib[50]; //采用数组保存中间结果

void Fibonacci(int n)

{

fib[0] = 1;

fib[1] = 1;

for (int i=2; i<=n; i++)

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

}

采用数组保存之前已求出的数据,减少了递归次数,提高了算效率。

2:分治算法

在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)……

任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排好序。n=3时只要作3次比较即可,…。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。

分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。

如果原问题可分割成k个子问题,1<k≤n,且这些子问题都可解并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。分治与递归经常同时应用在算法设计之中,并由此产生许多高效算法。

  分治法所能解决的问题一般具有以下几个特征:

1)该问题的规模缩小到一定程度就可以容易的解决。

2)该问题可以分解为若干个规模小的相同的问题,即该问题具有最优子结构性质。

3)利用该问题分解出的子问题的解可以合并为该问题的解。

4)该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。

例:二分搜索技术

给定n个元素a[0:n-1],需要在这n个元素中找出一个特定元素x。首先对n个元素进行排序,可以使用C++标准模板库函数sort()。比较容易想到的是用顺序搜索方法,逐个比较a[0:n-1]中的元素,直至找到元素x或搜索遍整个数组后确定x不在其中。

因此在最坏的情况下,顺序搜索方法需要 O(n)次比较。二分搜索技术充分利用了n个元素已排好序的条件,采用分治策略的思想,在最坏情况下用O(log  n) 时间完成搜索任务。

0

1

2

3

4

5

6

7

8

9

10

7

14

17

21

27

31

38

42

46

53

75

二分搜索算法的基本思想是将n个元素分成个数大致相同的两半,取a[n/2]与x作比较。

如果x=a[n/2],则找到x,算法终止。

如果x<a[n/2],则我们只要在数组a的左半部分继续搜索x。

如果x>a[n/2],则我们只要在数组a的右半部分继续搜索x。

二分搜索算法

//数组a[]中有n个元素,已经按升序排序,待查找的元素x

template<class Type>

int BinarySearch(Type a[],const Type& x,int n)

{

int left=0;                        //左边界

int right=n-1;                   //右边界

while(left<=right)

{

int middle=(left+right)/2;               //中点

if (x==a[middle]) return middle;         //找到x,返回数组中的位置

  if (x>a[middle]) left=middle+1;

else right=middle-1;

}

return -1;                       //未找到x

}

3:动态规划

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

设计动态规划算法的步骤:

  1)找出最优解的性质,并刻画出其结构特征。

  2)递归的定义最优值(写出动态规划方程)。

  3)以自底向上的方式计算出最优值。

  4)根据计算最优值时得到的信息,构造一个最优解。

   动态规划算法的有效性依赖于问题本身具有的两个重要性质:最优子结构性质和子问题重叠性质。

(1)最优子结构:当问题的最优解包含其子问题的最优解时,称该问题具有最优子结构性质。

(2)重叠子问题:在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其保存在一个表中,在以后尽可能多的利用这些子问题的解。

例:矩阵连乘积问题

  矩阵链乘问题给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i=1,2…,n-1。如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。
将一系列相乘的矩阵(Ai….Aj)划分为两部分;即(AiAi+1…Ak)(Ak+1Ak+2….Aj),k的位置要保证左边括号和右边括号相乘的消耗最小。

#include<iostream.h>

#include<stdlib.h>

#include<limits.h>

#include<time.h>

#define MAX_VALUE 100

#define N 201                         //连乘矩阵的个数(n-1)

#define random() rand()%MAX_VALUE   //控制矩阵的行和列的大小

int c[N][N], s[N][N], p[N];

int matrixchain(int n)    //3个for循环实现

{    for(int k=1;k<=n;k++)

        c[k][k]=0;

    for(int d=1;d<n;d++)

        for(int i=1;i<=n-d;i++)

        {    int j=i+d;

            c[i][j]=INT_MAX;

            for(int m=i;m<j;m++)

            {    int t=c[i][m]+c[m+1][j]+p[i-1]*p[m]*p[j];

                if(t<c[i][j])

                {

                    c[i][j]=t;

                    s[i][j]=m;

                }

            }

        }

    return c[1][n];

}

void Print(int s[][N],int i,int j)     //  输出矩阵连乘积的计算次序

{    if(i==j)

        cout<<“A”<<i;

    else

    {

        cout<<“(“;

        Print(s,i,s[i][j]);    // 左半部子矩阵连乘

        Print(s,s[i][j]+1,j);  //左半部子矩阵连乘

        cout<<“)”;

    }

}

int lookupchain(int i,int j)         //备忘录方法

{

    if(c[i][j]>0)

        return c[i][j];

    if(i==j)

        return 0;

    int u=lookupchain(i,i)+lookupchain(i+1,j)+p[i-1]*p[i]*p[j];

    s[i][j]=i;

    for(int k=i+1;k<j;k++)

    {

        int t=lookupchain(i,k)+lookupchain(k+1,j)+p[i-1]*p[k]*p[j];

        if(t<u)

        {

            u=t;

            s[i][j]=k;

        }

    }

    c[i][j]=u;

    return u;

}

void main()

{

    srand((int)time(NULL));

    for(int i=0;i<N;i++)   //  随机生成数组p[],各个元素的值的范1~MAX_VALUE 

        p[i]=random()+1;

    clock_t start,end;  

    double elapsed;

    start=clock();

    //cout<<“Count: “<<matrixchain(N-1)<<endl;   //3重for循环实现

    cout<<“Count: “<<lookupchain(1,N-1)<<endl;   //备忘录方法

    end=clock();

    elapsed=((double)(end-start));///CLOCKS_PER_SEC;

    cout<<“Time: “<<elapsed<<endl;

    Print(s,1,N-1);  //输出矩阵连乘积的计算次序

    cout<<endl;

}

两种算法的时间复杂度均为o(n3),,随着数据量的增多,备忘录方法消耗的时间越长;我觉得是由于递归算法,随着数据量增大,调用函数的次数也增大,语句被执行的时间也越多,因此调用函数消耗的时间也增多。

4:贪心算法
贪心算法是指,在对 问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部 最优解。

贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。

贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,他的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止。

贪婪算法可解决的问题通常大部分都有如下的特性:

随着算法的进行,将积累起其它两个集合:一个包含已经被考虑过并被选出的候选对象,另一个包含已经被考虑过但被丢弃的候选对象。

有一个函数来检查一个候选对象的集合是否提供了问题的解答。该函数不考虑此时的解决方法是否最优。

还有一个函数检查是否一个候选对象的集合是可行的,也即是否可能往该集合上添加更多的候选对象以获得一个解。和上一个函数一样,此时不考虑解决方法的最优性。

选择函数可以指出哪一个剩余的候选对象最有希望构成问题的解。 最后,目标函数给出解的值。

为了解决问题,需要寻找一个构成解的候选对象集合,它可以优化目标函数,贪婪算法一步一步的进行。起初,算法选出的候选对象的集合为空。接下来的每一步中,根据选择函数,算法从剩余候选对象中选出最有希望构成解的对象。如果集合中加上该对象后不可行,那么该对象就被丢弃并不再考虑;否则就加到集合里。每一次都扩充集合,并检查该集合是否构成解。如果贪婪算法正确工作,那么找到的第一个解通常是最优的。

使用贪心算法求解问题应该考虑如下几个方面

1)候选集合A:为了构造问题的解决方案,有一个候选集合A作为问题的可能解,即问题的最终解均取自于候选集合A。

2)解集合S:随着贪心选择的进行,解集合S不断扩展,直到构成满足问题的完整解。

3)解决函数solution:检查解集合S是否构成问题的完整解。

4)选择函数select:即贪心策略,这是贪心法的关键,它指出哪个候选对象最有希望构成问题的解,选择函数通常和目标函数有关。

5)可行函数feasible:检查解集合中加入一个候选对象是否可行,即解集合扩展后是否满足约束条件。

4.7//A是问题的输入集合即候选集合

Greedy(A)

{

  S={ };           //初始解集合为空集

  while (not solution(S))  //集合S没有构成问题的一个解

  {

    x = select(A);     //在候选集合A中做贪心选择

    if feasible(S, x)    //判断集合S中加入x后的解是否可行

      S = S+{x};

      A = A-{x};

  }

  return S;

}贪心算法的一般流程

1)候选集合A:问题的最终解均取自于候选集合A。

2)解集合S:解集合S不断扩展,直到构成满足问题的完整解。

3)解决函数solution:检查解集合S是否构成问题的完整解。

4)选择函数select:贪心策略,这是贪心算法的关键。

5)可行函数feasible:解集合扩展后是否满足约束条件。

5:回溯法

回溯法又称为试探法,是一种选优搜索法,按选优条件向前搜索,以达到目标。回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。

在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。 若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。 而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束

可用回溯法求解的问题P,通常要能表达为:对于已知的由n元组(x1,x2,…,xn)组成的一个状态空间E={(x1,x2,…,xn)∣xi∈Si ,i=1,2,…,n},给定关于n元组中的一个分量的一个约束集D,要求E中满足D的全部约束条件的所有n元组。其中Si是分量xi的定义域,且 |Si| 有限,i=1,2,…,n。我们称E中满足D的全部约束条件的任一n元组为问题P的一个解。

解问题P的最朴素的方法就是枚举法,即对E中的所有n元组逐一地检测其是否满足D的全部约束,若满足,则为问题P的一个解。但显然,其计算量是相当大的。

6. 分支限界算法

分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。

分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。

在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。

此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。

常见的两种分支限界法

1)队列式(FIFO)分支限界法

按照队列先进先出(FIFO)原则选取下一个节点为扩展节点。

2)优先队列式分支限界法

按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。

分支限界法与回溯法的不同

1)求解目标:回溯法的求解目标是找出解空间树中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出在某种意义下的最优解。

2)搜索方式的不同:回溯法以深度优先的方式搜索解空间树,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/167399.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 5款优秀的文档管理系统「建议收藏」

    5款优秀的文档管理系统「建议收藏」1.easyopen一个简单易用的接口开放平台,平台封装了常用的参数校验、结果返回等功能,开发者只需实现业务代码即可。技术点加密算法(MD5、AES、RSA) Netty(编解码、长连接、断开重连) 限流(漏桶策略、令牌桶策略) 权限(RBAC、校验) session(单机、分布式) 注解(文档生成) token(jwt、accessToken) SDK(Java、C#…

  • 在我一生中最猥琐的时候遇见你(1)

    在我一生中最猥琐的时候遇见你(1)【书名】在我一生最猥琐的时候遇见你【作者】无良某鸡【文案】当我还是一个光屁股天使的时候,有一天,我在凡间晃悠。  突然我看到前面那个头上长着红色山羊角的男人从他的黑色斗篷里落下了一个东西。  “嗨,先生,”我朝他喊,“您掉东西了!”  那人不仅没有回头,反而越走越快,转眼就消失在街角。  我走上前去捡起落在地上的东西,那是一本小说.  小说的封面,一男一女紧密相拥着。  …

  • oracle里面concat函数用法,oracle wm_concat函数用法-Oracle

    oracle里面concat函数用法,oracle wm_concat函数用法-Oraclewmsys.wm_concat函数,它的作用是以’,’链接字符例子如下:SQL>createtableidtable(idnumber,namevarchar2(30));TablecreatedSQL>insertintoidtablevalues(10,’ab’);1rowinsertedSQL>insertintoidtablevalues…

  • wptx64能卸载吗_电脑可以卸载bonjour软件吗?详细介绍bonjour软件

    wptx64能卸载吗_电脑可以卸载bonjour软件吗?详细介绍bonjour软件细心的用户发现电脑里面忽然多了一个叫bonjour的程序,以前都没有见过的,bonjour是什么?它是苹果电脑公司在其开发的操作系统MacOSX10.2版本之后引入的服务器搜索协议所使用的一个商标名。为什么电脑会多出bonjour软件?那么bonjour软件可以卸载吗?带着一系列疑问,我们一起阅读下文教程吧。bonjour是否可以卸载bonjour软件为什么会突然出现,那是因为你在电脑上安装了…

  • JAVA葵花宝典(基础版)

    JAVA葵花宝典(基础版)JAVA葵花宝典(基础版)20201209response.getWriter().write()和response.getWriter().print()的区别newObjectMapper().writeValueAsString();JSON.stringify()常量命名【idea技巧】IDEA给方法添加注释模板newJdbcTemplate(JDBCUtil.getDataSource()).queryForObject()20201212【idea快捷键】显示方法参数ctrl+p获取验证

  • Intellij IDEA打开Java项目并启动「建议收藏」

    Intellij IDEA打开Java项目并启动「建议收藏」最近有很多同学,竟然不知道如何使用IntellijIDEA打开Java项目并启动目录Maven项目2、Maven项目是以pom文件引入各项jar包的在点击lmportProject,然后在点击pom.xml,再点击Open​3、再点击Next​4、如果是公司的私服,还需要勾选nexus​5、继续点击下一步​6、选择Jdk版本,点击下一步​7、点击Finish​……

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号