算法初识——概念

算法与数据结构是程序的两个重要方面,两者的有机结合构成程序。程序中数据结构和变量用来描述问题的对象,程序结构、函数、语句用来描述问题的算法。对算法分析,通常用执行算法时所占用的空间大小和消耗时间的多少来作为算法优劣的评判条件。

算法的时间复杂度的估算常使用到频度,频度是指某一语句在算法运行过程中执行的次数记为F(n),时间复杂度就是使用频度最大的语句来衡量的,记为T(n)=O(F(n))。简单来说就是哪一个语句(可能多个语句执行的次数一样多)在算法过程被执行的次数最多,就把这个语句执行的次数用来计算时间复杂度,记为O(F(n))。例子如下:

for(i=0,i

在这个例子中,a=1这个语句被执行了n^3次,所以频度为n^3,时间复杂度为O(n^3)。

算法实现所占用的存储空间一般分为三块:1、指令、常数和系统变量所占用的存储空间。

                                                                 2、I/O数据所占用的存储空间

                                                                 3、算法执行过程中所需要的辅助空间

对算法空间复杂度的分析就是对算法执行过程中的所需要的辅助空间的分析。在上个例子中时间复杂度为O(n^3),因为变量a和n无关,所以空间复杂度为O(1)。例子2:

for(i=0,i
在例子2中,变量a的空间分配和n有关,分配的空间为n*n*n,所以空间复杂度为O(n^3)。

算法有5个特性:有穷性,确定性、可行性、输入、输出。有穷性指语句执行的次数是是有限的,确定性指语句只有一个含义没有第二个含义,可行性指可以通过有限的操作实现,输入、输出指算法应该有输入和输出。

1、迭代法

迭代法是通过用于求解方程或者方程近似根的算法设计方法,通过不断的旧值推新值,最终得到需要的答案。

迭代法举例:一个饲养场引进一只刚出生的新品种兔子,这种兔子从出生的下一个月开始,每月新生一只兔子,新生的兔子也如此繁殖。如果所有的兔子都不死去,问到第 12 个月时,该饲养场共有兔子多少只?

首先分析一下兔子产生的规律:第一个月M1 =1;第二个月M2=1+1;第三个月M3=2+2(第二个月新生的兔子在第三个月就能繁殖兔子了);由这个规律可以得出一个规律Mn=2*M(n-1)。

所以要得到具体的12月有多少只,程序代码可以这样写:

int x=1;

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

{

y=x*2

x=y;

}

printf("第12个月的兔子数量为%d",y);

其中x的值不断被y的值迭代,同时y的值也因为x的变化产生新值。

2、递推法

递推法是根据问题本身所具有的的一种递推关系来求问题解的一种方法。同样是上题中求12月兔子数量的问题,用递推方法可以是这样的:

int month[12];

month[0]=1;//第一个月有一只兔子

for(int i=0;i<11;i++)//根据第11个月为month[10]就可以得出第12个月month[11]的兔子数量

{

month[i+1]=2*month[i];

}

printf("第十二个月的兔子数量%d",month[11]);

3、递归法

递归是设计和描述算法的一种有力工具在,在复杂算法中常被使用,一般来说一个对象的部分有自己组成火按自己定义可以称之为递归。一个问题可以使用递归必须符合三个条件:

1、可以把一个问题转化成一个新问题,这个新问题的解决办法与原问题解法相同

2、可以通过转化过程解决问题

3、必定有一个明确的边界条件结束递归

递归都两个阶段,阶段一,不断地将复杂问题递推到简单问题;阶段二,在获得了最简单问题的答案后,一级一级回归解答复杂问题。

依然是求12月兔子的问题,用递归方法的例子如下:

int Month(int n)

{

if(n=1) return 1;

if(n>1) return Month(n-1)*2;

}

printf("第十二个月的兔子数量为%d",Month(12));

当输入12时,最开始进入递推阶段不断调用自身,12月兔子数量等于11月兔子数量的两倍,11月兔子数量等于10月兔子数量的两倍...一直递推到1月的兔子数量为1。得到1月的兔子数量为1后,开始逐级回归解决问题,先得到2月兔子数量,再三月,四月...直到12月兔子数量。

4、穷举法

穷举法的基本思想是根据题目的部分条件确定一个答案的大致范围,并对此范围的所有可能情况逐一验证。

简单举例一,查询数组B中是否存在和变量a相等的值:

int a=1;                                                                                                                            
int b[10]={2,3,4,0,12,3,4,5,2,1};
for(int i=0;i<10;i++)
{
if(b[i]==a)
{
printf("b[%d]和a的值相同",i);
}
}

通过遍历每一种可能来获取答案。

5、分治法

分治法是通过把一个复杂的问题分成多个较小的与原问题类型相同的子问题,通过对子问题的求解,再把子问题的解合并起来从而构造出整个问题的解,即:各个击破,分而治之。分治法能解决的问题一般有4个特征:

1、问题缩小到一定规模可以容易解决

2、问题可以分解成若干较小的相同问题

3、子问题的解可以合并成整体问题的解

4、分解出的各个子问题是相互独立的,及子问题之间没有交集。

二分法就是运用了分治法的思想。通过不断地缩小范围来查询到需要的数字。

int found(int x,int y)
{
    int m=x+(y-x)/2;
    if(x>y)//查找完毕没有找到答案,返回-1
        return -1;
    else
    {
        if(a[m]==k)
            return m;//找到!返回位置.
        else if(a[m]>k)
            return found(x,m-1);//找左边
         else
            return found(m+1,y);//找右边
    }
}

例如从a[10]中顺序存放了0到9,要查询3这个数,首先取中点a[4],发现a[4]比3大,调整查询范围为0到a[4-1],取中点a[1],发现a[1]比3小,调整查询范围为a[1+1]到a[3],取中点a[2],发现a[2]比3小,调整查询范围为a[2+1]到a[3],取得中点a[3]=3。

6、贪心法

贪心法是从给定的集合中选出一个子集,能够满足题目的要求就是一个可行解,这个解可能并不是最优的解。贪心法也不追求最优的解,只希望得到满意的解。贪心在每一阶段都保持最优,但很可能最后的综合结果并不是最优的。贪心法的当前选择可能会依赖已经做出的所有选择。

简单例子:背包问题

背包可装重量:40kg

物品: A 30kg  B 20kg  C 6kg

物品价值  40        25         8

当贪心策略为最开始装价值最大的物品时,最后的总价值为48;

但最优解是只装物品B,最后重价值为50。

7、回溯法

在需要找到一组解或者最优解的时候需要进行大量的比较,如果采用穷举方法来彻底搜索,需要消耗大量的运算,这时可以采用回溯法。回溯法通过先暂时放弃问题规模的大小,候选解通过一种顺序被逐一试探出。如果当前候选解除了不满足问题的规模外满足其他要求就扩大当前解的规模,如果当前解满足所有要求说明是问题的一个解,然后回溯到上一步,调整上一步的候选解,满足时再扩大问题规模。

回溯法就是通过一步一步试探找到当前问题规模下的一个候选解后就扩大问题规模,在当前问题规模下没有找到解就回溯到上一步的问题规模使用下一个候选解。

回溯法的具体应用 ——八皇后在后一篇博客详细撰写学习。

8、动态规划法

动态规划法也是通过将问题划分为子问题,如果各子问题间都是独立的那么使用分治法,如果各子问题间存在公共子子问题,那么应该使用动态规划法。动态规划法的实质是分治思想和解决冗余。对于重复出现的子问题,动态规划化会在第一次求解这个问题将解保存下来,后面再遇到重复子问题就直接引用。动态规划法和贪心法的比较是:贪心法在每一步都会挑选一个局部最优解,直接解原问题。动态规划法通过对一系列的子问题求解,综合多个子问题的解选择最优解。

动态规划有点难,我差不读是条咸鱼了,后面慢慢仔细学习,给个讲解动态规划的博客链接:漫画说算法--动态规划算法一(绝对通俗易懂,非常棒)

以上,祝好!

你可能感兴趣的:(初识算法)