编程基础文章目录:
五大基础算法 | 基础数据结构(栈和队列) | 散列表 |
常见C++知识 | 基础数据结构(数组、串、广义表) | 四大比较排序算法 |
基础数据结构(线性表) | 基础数据结构(树和堆) |
关于基础算法,就是我们设计算法时首先应该想到的几类算法。在本文我主要会介绍的基础算法有:枚举法、递归法、分治法、贪心法、模拟法。
枚举法,本质上就是搜索算法。
例题1、百钱买百鸡问题:有一个人有一百块钱,打算买一百只鸡。到市场一看,公鸡一只3元,母鸡一只5元,小鸡3只1元,试求用100元买100只鸡,各为多少才合适?
根据题意我们可以得到方程组:
3X + 5Y + Z/3 = 100;
X + Y + Z = 100;
(100 > X,Y,Z > 0, Z%3 == 0),根据这两个公式,我们可以写出最为简单的代码,一一列举所有解进行枚举
代码:
int x,y,z; for( x = 0; x < 100; x++ ) for( y = 0; y < 100 ; y++ ) for( z = 0; z < 100; ) { if( x + y + z == 100 && 3 * x + 5 * y + z / 3 == 100 ) { cout << x << " " << y << " " << z << endl; } z += 3; }
然而我们可以根据已知条件来进行优化代码,减少枚举的次数:
三种鸡的和是固定的,我们只要枚举二种鸡(x,y),第三种鸡就可以根据约束条件求得(z = 100 - x - y),这样就缩小了枚举范围。
另外我们根据方程特点,可以消去一个未知数,得到下面
4X + 7Y = 100;
X + Y + Z = 100;
(X,Y,Z > 0, Z%3 == 0),=>> 0 <= x < = 25因此代码可以优化为下面这样子:
for( x = 0; x <= 25; x++ ) { y = 100 - 4 * x; if( y % 7 == 0 && y >= 0 ) { y /= 7; z = 100 - x - y; if( z % 3 == 0 && 3 * x + 5 * y + z / 3 == 100 ) cout << x << " " << y << " " << z << endl; } }
人生来就有三个生理周期,分别为体力、感情和智力周期,它们的周期长度为23天、28天和33天。每一个周期中有一天是高峰。在高峰这天,人会在相应的方面表现出色。例如,智力周期的高峰,人会思维敏捷,精力容易高度集中。因为三个周期的周长不同,所以通常三个周期的高峰不会落在同一天。对于每个人,我们想知道何时三个高峰落在同一天。对于每个周期,我们会给出从当前年份的第一天开始,到出现高峰的天数(不一定是第一次高峰出现的时间)。你的任务是给定一个从当年第一天开始数的天数,输出从给定时间开始(不包括给定时间)下一次三个高峰落在同一天的时间(距给定时间的天数)。例如:给定时间为10,下次出现三个高峰同天的时间是12,则输出2(注意这里不是3)。
Input
输入四个整数:p, e, i和d。 p, e, i分别表示体力、情感和智力高峰出现的时间(时间从当年的第一天开始计算)。d 是给定的时间,可能小于p, e, 或 i。 所有给定时间是非负的并且小于365, 所求的时间小于21252。
当p = e = i = d = -1时,输入数据结束。
Output
从给定时间起,下一次三个高峰同天的时间(距离给定时间的天数)。
#include <iostream> using namespace std; int main() { int p, e ,i, d; int x; int num = 1; while(cin>>p>>e>>i>>d) { if(p!=-1 && e!=-1 && i!=-1 && d!=-1) { for( x=d+1;x<=21252;x++) { if((x-p)%23==0&&(x-e)%28==0&&(x-i)%33==0) { cout <<"Case " << num << ": the next triple peak occurs in " << x - d <<" days." << endl; break; } } num++;} else break; } return 0; }
但是这样做是没有必要的,我们可以进行代码优化,在问题的数学模型中,有多个条件可以满足时(我们要满足3个条件),可以采用逐步减小搜索空间的方法提高计算的效率,依次按照条件一,条件二,。。进行搜索。在最初的搜索空间上,首先按照条件一就行判定,然后将符合条件一的搜索空间,作为下面条件的索索空间。代码可以优化成:
#include<iostream> using namespace std; int main() { int p,e,i,d; int j; int count = 0; while (cin>>p>>e>>i>>d) { count++; if (p==-1&&e==-1&&i==-1&&d==-1) break; for (j=d+1;j<=21252;j++) if ((j-p)%23==0) break; for(;j<=21252;j+=23) if((j-e)%28==0) break; for(;j<=21252;j+=644) if((j-i)%33==0) break; cout<<"Case "<<count<<": the next triple peak occurs in "<<j-d<<" days."<<endl; } return 0; }
题目其实意思很简单,就是给定一个目标值target,再给你一个备选字符串(5~12个字符),要你在这个字符串里选5个出来,满足题中给定的等式,并且你选择的这5个字符组成的字符串必须是所有可能情况中按字典序最大的情况。简单分析下就可以看出,就是一个组合问题,问题解的最大规模就是12取5,就是12*11*10*9*8*7,而最小规模是5取5,所以用枚举法就可以搞定。
#include<stdio.h> #include<string.h> #include<math.h> int vist[1000],flog,len,target,s[10],top,loction[10]; int m[1000]; int cmp() { int v,w,x,y,z;//一定要注意转换成int型的精度问题 v=s[1]; w=(int)(pow(s[2]*1.0,2.0)+0.5); x=(int)(pow(s[3]*1.0,3.0)+0.5); y=(int)(pow(s[4]*1.0,4.0)+0.5); z=(int)(pow(s[5]*1.0,5.0)+0.5); if(v-w+x-y+z==target) return 1;//相等反回1 return 0; } void DFS(int n) { int i; s[++top]=m[n];//放数的栈 loction[top]=n;//记绿m[n]的位置 vist[n]=1;//表示第n个位置被访问了 if(top==5)//相等就可以比较 flog=cmp(); if(flog)//为真,则找到了不用往下 return ; if(top<5)//只有栈中小于5个数才继续可以放数 { for(i=1;i<=len;i++) if(vist[i]==0) { DFS(i); if(flog)//用来结束上层的DFS return ; } } vist[n]=0;top--;//退回 } int main() { char str[1000],tem; int i,j,e; while(scanf("%d %s",&target,str)>0) { if(target==0&&strcmp(str,"END")==0) break; flog=0; len=strlen(str); for(i=0;i<len;i++)//按字典序从大到小排 { e=i; for(j=i+1;j<len;j++) if(str[e]<str[j]) e=j; tem=str[i];str[i]=str[e];str[e]=tem; } for(i=0;i<len;i++)//变成数字 m[i+1]=str[i]-'A'+1; for(i=1;i<=len;i++)//以第i个数为开头 { memset(vist,0,sizeof(vist)); top=0; DFS(i); if(flog) break; } if(flog) { for(i=1;i<=top;i++) printf("%c",str[loction[i]-1]); } else printf("no solution"); printf("\n"); } }二、递归法
递归边界:算法要有一个边界出口,能结束程序递归函数常使用堆栈,算法效率低,费时和费内存
参数收敛:每次调用参数都是收敛于递归边界
自身调用
递归算法适用的一般场合
按递归定义的数据定义形式
例如,Fibonacci数列:1,1,2,3,5,8,13,21,34,55例题1:Hanoi题问题
Hanoi塔是根据一个传说形成的一个问题:
有三根杆子A,B,C。A杆上有N个(N>1)穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至B杆:
每次只能移动一个圆盘;
大盘不能叠在小盘上面。
整数划分问题是算法中的一个经典问题之一,有关这个问题的讲述在讲解到递归时基本都会有涉及。
将整数n表示成一系列正整数之和:n=n1+n2+n3+...+nk,其中n1>=n2>=n3>=...>=nk,k>1。求n的不同划分个数。
例题3:全排列问题
已知集合R={r1,r2,r3,...,rn},请设计一个算法生成R中n个元素的全排列。
全排列:从n个不同元素中任取m(m<=n)个元素,按照一定的顺序排列起来,叫做从n个元素中取出m个元素的一个排列。当m=n时,所有的排列情况叫全排列。
令Rj=R-{rj}
记集合X中元素的全排列记为perm(X)。那么(rj)perm(X)表示在全排列perm(X)的每一种排列前加上前缀rj所得到的排列,所以,R的全排列可归纳定义如下:
当n=1时,perm(R)=(r)
当n>1时,perm(R)由(r1)perm(R1),(r2)perm(R2) ... (rn)perm(Rn)构成
三、分治法分解——将原问题分解成一系列子问题例题解析
解决——递归地解各子问题
合并——将子问题的结果合并成原问题的解
例题1:快速排序
输入数组为a[p:r],通过排序算法使其有序。
划分:以a[q]为基准元素将a[p:r]划分成3个部分:a[p:q-1],a[q],a[q+1:r]。使a[p:q-1]中的任一元素都小于a[q],a[q+1:r]中的任一元素都大于等于a[q],q是在划分过程中确定的。例题2:归并排序
通过递归调用,对a[p:q-1]和a[q+1:r]进行同样的操作
理牌时一种可行的方案是将一副牌一分为二,一人洗一半,然后合并起来,请将该方法用程序表示出来。
例题1 删数问题
键盘输入一个高精度的正整数n(<=240位),去掉任意s个数字后剩下的数字按原左右次序将组成一个新的正整数。编程对给定的n和s,寻找一种方案,使得剩下的数最小。
思路 : 首先必须注意,试题中N的有效位为240位,而计算机中整数有效位充其量也不过11位,无论如何都不能达到试题的数值要求。因此,必须采用可含256个字符的字串来代替整数。
另外,有一些目前尚未解决的经典题目,像是著名的Josephus Problem,由于目前没有好的演算法,所以大家遇到了这种问题,就只能乖乖的照着题目的要求来做。这一类的题目也就变成了Simulation的题目。