1.递归算法
定义:是一种直接或者间接调用自身的算法。
实质:把求解的问题转换为规模缩小了的同类问题的子问题,然后递归调用函数来表示函数的解,通过多次的递归调用,最终可以求出最小问题的解,再返回上层调用,不断地重复,最终得到解的过程。
四个特性:
(1)必须有可最终达到的终止条件,否则程序将陷入无穷循环;
(2)子问题在规模上比原问题小,或更接近终止条件;
(3)子问题可通过再次递归调用求解或因满足终止条件而直接求解;
(4)子问题的解应能组合为整个问题的解。
实例1(求阶乘)
分析图
#include <stdio.h>
#include <stdlib.h>
int fact(int );
int main()
{
int i;
printf("请输入需要求阶乘的一个整数:");
scanf("%d",&i);
printf("求得阶乘的结果为%d\n",fact(i));
return 0;
}
int fact(int n)
{
int num=1;
if(n<=1)
{
return 1;
}
else
{
return n*fact(n-1);
}
}
实例2(数制转换)
通过以上的竖式可以得出两点:
(1)数值转换的过程中是将数据逐步缩小的过程,即构成了递归调用的“将问题化为一个缩小了的子问题”的特点。
(2)在数值转换的过程中有递归结束的条件,即商为0 的时候结束递归过程。
综合评价上述两个条件,我们选用递归算法。
#include <stdio.h>
#include <string.h>
void convto(char *s,int n,int b)
{
char bit[]={"0123456789ABCDEF"};
int len;
if(n==0)
{
strcpy(s,"");
return ;
}
convto(s,n/b,b);//n=n/b;
len=strlen(s);
s[len]=bit[n%b];//余数为几,就将该数赋值给当前位,比如余数为1,那么就取bit[1]赋值给当前长度位置的元素
//这里还隐含了将数组长度扩充
s[len+1]='\0';
}
int main()
{
char s[80];
int i,base,old;
printf("请输入十进制数:");
scanf("%d",&old);
printf("请输入转换的进制:");
scanf("%d",&base);
convto(s,old,base);
printf("%s\n",s);
return 0;
}
2.分治算法
背景:我们求解的某些问题要处理的数据相当的多,求解过程复杂,使得直接求解的时间长,或者无法求出。
算法思路:将该问题分解为几个小问题,找到这几子字问题的解法后,再找合适的方法将他们合并成求整个问题的解法。
满足特征:
(1)求解的问题可以分成若干个规模较小的同类问题。
(2)求解问题的规模分解到一定的程度,就能够容易的求解。
(3)合并子问题的解可以得到求解问题的解。
(4)求解的问题分解的各个子问题相互独立。
求解步骤:
(1)分解。将要求解的问题划分为若干个同类的小问题。
(2)求解。子问题划分的足够小时,用简单的方法去求解。
(3)合并。按照求解问题的要求,将子问题逐层合并,构成最终的解。
实例:乒乓球比赛赛程安排
某学校举行乒乓球比赛,在初赛阶段设置为循环赛,设有n位选手参赛,初赛共进行n-1天,每位选手要与其它的每一个选手进行一场比赛,然后按照积分排名选拔进入决赛的选手。根据学校的作息时间,要求每位选手每天必须比赛一场,不能轮空。按照此要求为比赛安排具体日程,即决定每天各选手对阵的对手。
#include <stdio.h>
#include <conio.h>
#define MAXN 64
int a[MAXN+1][MAXN+1]={0};//方便程序计算,不在使用0行0列,所以定义中都加1
void gameCall(int k,int n) //处理编号k开始的n个选手的日程
{
int i,j;
if(n==2)
{
a[k][1]=k;//参赛选手编号
a[k][2]=k+1;
a[k+1][1]=k+1;
a[k+1][2]=k;
}else
{
//递归实现
gameCall(k,n/2);//处理左上角
gameCall(k+n/2,n/2);//处理左下角
//左下角的数据填充到右上角
for(i=k;i<k+n/2;i++)
{
for(j=n/2+1;j<=n;j++)
{
a[i][j]=a[i+n/2][j-n/2];
}
}
//左上角的数据填充到右下角
for(i=k+n/2;i<k+n;i++)
{
for(j=n/2+1;j<=n;j++)
{
a[i][j]=a[i-n/2][j-n/2];
}
}
}
}
int main()
{
int m,i,j;
printf("输入参赛选手人数:");
scanf("%d",&m);
j=2;
for(i=2;i<8;i++)
{
j*=2;
if(j==m)break;
}
if(i>=8)
{
printf("参赛选手必须为2的整数次幂,且不能超过64!\n");
return -1;
}
gameCall(1,m);//调用上述函数
printf("\n编号 ");
for(i=2;i<=m;i++)
{
printf("%2d天 ",i-1);
}
printf("\n");
for(i=1;i<=m;i++)
{
for(j=1;j<=m;j++)
{
printf("%4d ",a[i][j]);
}
printf("\n");
}
getch();//包含在 conio.h 头文件
return 0;
}
运行结果:
3、贪心算法
简介:贪心算法总是在做出从当前看来最好的选择。也就是说他从来不从整体最优考虑,他所做出的选择只是在某种意义上的局部最优选择。我们也希望贪婪算法得到的结果是总体最优的,确实,他有时候做出的结果能够接近于最优解,甚至就是最优解。
定义:是一种不追求最优解,只希望得到较为满意解的方法。常常以当前的情况为基础,而不考虑各种可能的整体情况,所以贪婪算法不需要回溯。
特点:通过局部最优达到全局最优,实用贪婪算法是,通常采用自顶向下的方法求解,每一步都使用最贪婪的选择,使得原来的问题变成一个相似的、规模更小的问题。
基本思路:从问题的某一初始解出发逐步逼近给定的目标,以尽可能快的求出更好的解。当达到算法中某一步不能再继续前进时,就停止算法,给出近似解。
该算法存在的不足:
(1)不能保证最后的解是最优的
(2)不能用来求最大或者最小解的问题
(3)只能满足某些约束条件的可行解的范围
案例:在超时间购物,收银员找补零钱的问题,目的是使得找回的纸币张数(或者硬币数量)最少,比如找给顾客17元,按照贪心算法,不会给出17张一元的方案,而应该是1张10元、1张5元、2张1元。
程序代码:
#include<stdio.h>
#include<conio.h>
#define MAXN 9
int parValue[MAXN]={10000,5000,2000,1000,500,200,100,50,20,10};
int num[MAXN]={0};
int exchange(int n)
{
int i,j;
for(i=0;i<MAXN;i++)
{
if(n>=parValue[i]) break;
}
while(n>0&&i<MAXN)
{
if(n>=parValue[i])
{
n-=parValue[i];
num[i]++; //统计面值的张数
}
else if(n<10&&n>=5)
{
num[MAXN-1]++;
break;
}
else i++;
}
return 0;
}
int main()
{
int i;
float m;
printf("输入需要找零的金额:");
scanf("%f",&m);
exchange((int)100*m);
printf("\n%.2f元零钱的组成:\n",m);
for(i=0;i<MAXN;i++)
{
if(num[i]>0)
{
printf("%6.2f: %d张\n",(float)parValue[i]/100.0,num[i]);
}
}
getch();
return 0;
}
4、试探算法
基本思想:为了求得问题的解,先选择某一种可能的情况进行试探,在试探过程中,一旦发现原来选择的假设情况是错误的,就退回一部重新选择,继续向前试探,如此反复,直至得到解或者证明无解。
常见的问题有迷宫、下棋等问题。
基本设计:
对解集合中的各个解进行试探
{
if(满足条件)
{
保存结果
if(完成集合中所有的解的试探)
输出解
else
重复本过程进行下一步的试探(递归调用本函数)
}
else
{
恢复至上一步保存结果之前的状态,进行另一步的试探(递归调用本函数)
}
}
例子:试探法 生成彩票
#include<stdio.h>
#define MAXN 7
#define NUM 29
int num[NUM];
int lottery[MAXN];
void combine(int n, int m) {
int i, j;
for (i = n; i >= m; i--) {
lottery[m-1] = num[i-1]; //保存一位数字
if (m > 1)
combine(i-1, m-1);
else
{
for (j = MAXN - 1; j >= 0; j--)
printf("%3d", lottery[j]);
getch();
printf("\n");
}
}
}
int main() {
int i, j;
for (i = 0; i < NUM; i++)
num[i] = i + 1;
for (i = 0; i < MAXN; i++)
lottery[i] = 0;
combine(NUM, MAXN);
getch();
return 0;
}
基本的算法就通过上下两篇文章介绍在这儿啦!下面是综合上述案例来展开对算法的一些评价的描述。
算法评价的原则:
(1)正确性:是指通过设计算法的运算,最后能够得到正确的结果。
(2)高效性:解决同类问题的算法,所执行的时间短,那么这个算法的运行效率必然是相对较高的。
(3)空间性:空间性是指按照指定的算法编写的程序执行时所需要的存储空间。
(4)可读性:考察算法的可读性时,应该使得大多数的程序员能够很好的理解程序。
由此,我们衍生出评价算法好坏的常用指标,时间复杂度和空间复杂度。