【1.2】算法基本举例(下篇)

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)可读性:考察算法的可读性时,应该使得大多数的程序员能够很好的理解程序。

由此,我们衍生出评价算法好坏的常用指标,时间复杂度和空间复杂度。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值