递归算法
程序直接或间接调用自身的编程技巧称为递归算法(Recursion)。
一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归需要有边界条件、递归前进段和递归返回段。
当边界条件不满足时,递归前进;
当边界条件满足时,递归返回。
注意:在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口,否则将无限进行下去(死锁)。
递归的缺点:
递归算法解题的运行效率较低。
在递归调用过程中,系统为每一层的返回点、局部变量等开辟了堆栈来存储。递归次数过多容易造成堆栈溢出等。
Fibonacci数列
F(n)=1 n=0,n=1
F(n)=F(n-1)+F(n-2) n>1
Fibonacci数列的递归算法:
int fib(int n)
{
if (n<=1) return 1;
return fib(n-1)+fib(n-2);
}
该算法的效率非常低,因为重复递归的次数太多。
Fibonacci数列的递推算法
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];
}
集合的全排列问题
//产生从元素k~m的全排列,作为前k—1个元素的后缀
void Perm(int list[], int k, int m)
{
//构成了一次全排列,输出结果
if(k==m)
{
for(int i=0;i<=m;i++)
cout<<list[i]<<" ";
cout<<endl;
}
else
//在数组list中,产生从元素k~m的全排列
for(int j=k;j<=m;j++)
{
swap(list[k],list[j]);
Perm(list,k+1,m);
swap(list[k],list[j]);
}
}
//产生从元素k~m的全排列,作为前k—1个元素的后缀
void Perm(int list[], int k, int m)
{
if(k==m) //构成了一次全排列,输出结果
{
for(int i=0;i<=m;i++)
cout<<list[i]<<" ";
cout<<endl;
}
else
//在数组list中,产生从元素k~m的全排列
for(int j=k;j<=m;j++)
{
swap(list[k],list[j]);
Perm(list,k+1,m);
swap(list[k],list[j]);
}
}
整数划分问题
分治策略
- 分治策略是对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同。
- 递归地解这些子问题,然后将各子问题的解合并得到原问题的解。
分治法在每一层递归上都有三个步骤:
- 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
- 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
- 合并:将各个子问题的解合并为原问题的解。
算法3.5 分治策略的算法设计模式
Divide_and_Conquer(P)
{
if (|P|<=n0 ) return adhoc(P);
divide P into smaller substances P1,P2,…,Pk;
for (i=1; i<=k; k++)
yi=Divide-and-Conquer(Pi) //递归解决Pi
Return merge(y1,y2,…,yk) //合并子问题
}
分治法所能解决的问题一般具有以下几个特征:
- 该问题的规模缩小到一定的程度就可以容易地解决;
- 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;
- 利用该问题分解出的子问题的解可以合并为该问题的解;
- 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题
二分搜索技术
算法3.6 二分搜索算法
//数组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;
if (x>a[middle]) left=middle+1;
else right=middle-1;
} return -1; //未找到x
}
循环赛日程表
输油管道问题
算法1:对数组a排序(一般是升序),取中间的元素
int n; //油井的数量
int x; //x坐标,读取后丢弃
int a[1000]; //y坐标
cin>>n;
for(int k=0;k<n;k++)
cin>>x>>a[k];
sort(a,a+n); //按升序排序
//计算各油井到主管道之间的输油管道最小长度总和
int min=0;
for(int i=0;i<n;i++)
min += (int)fabs(a[i]-a[n/2]);
cout<<min<<endl;
算法2:采用分治策略求中位数
int n; //油井的数量
int x; //x坐标,读取后丢弃
int a[1000]; //y坐标
cin>>n;
for (int i=0; i<n; i++)
cin>>x>>a[i];
int y = select(0, n-1, n/2); //采用分治算法计算中位数
//计算各油井到主管道之间的输油管道最小长度总和
int min=0;
for(int i=0;i<n;i++)
min += (int)fabs(a[i]-y);
cout<<min<<endl;
半数集问题