算法与分析课程复习之分治法
一、基础知识
1.递归简单定义:
将一个问题分割成一个或多个的子问题,这些子问题与原问题具有相同的结构,然后组合这些子问题的解决方案,以获得原来的问题的解决方案的过程。
2.递归本质定义:
从本质上说,给出一个带有参数n的问题,用归纳法设计一个算法是基于这样一个事实,如果我们知道如何求解带有参数小于n的同样问题(它被称为归纳假设),那么我们的任务就化为如何把解法扩展到带有参数n的实例。
3.分治法的优点:
算法的正确性证明已自然地嵌入了算法的描述中了
4.归纳法的基本思想:
将规模为n的问题递减为规模为n-1或n/2的子问题,反复递减后对子问题分别求解,再建立子问题的解与原问题的解的关系。
5.归纳法的两个变形:
- 减常数(如1) :每次迭代规模减1,即n→n-1
- 减因子(如1/2):每次迭代规模减半,即n→ n/2
二、经典问题
1.阶乘函数
当n=1时,n!= 1
当n>1时,n!= n*(n-1)!
int factorial(int n)
{
if(n==1)return 1;
else return n * factorial(n-1);
}
2.Fibonacci数列
当n=0,1时,f = 1
当n>1时,f = f(n-1)+f(n-1)
int fibonacci(int n)
{
if (n <= 1) return 1;
return fibonacci(n-1)+fibonacci(n-2);
}
3.选择排序
设 A[1…n]为包含n个元素的数组
首先,我们找到数组中的最小元素,将它存在A[1].
接着,我们从剩下的n-1个元素中找到最小的元素,将它存在 A[2].
重复以上过程,知道整个数组中第二大的元素存在A[n-1],则算法停止.
时间复杂度为:O(n^2)
void SelectSort(int a[],int i,int n) {
//递归的方法简单选择排序
//i n排序数组的起始和终止端
int j,k;
if(i==n-1)//递归结束条件
return ;
else {
k=i;//记录当前a[i]~a[n-1]最小值的下标
for(j=i+1; j<n; j++)
if(a[j]<a[k])//在a[i]~a[n-1]中找到最小值
k=j;
if(k!=i)
{
//如果最小值不是a[i]交换
swap(a[i),a[k]);
}
SelectSort(a,i+1,n);
}
}
4.插入排序
假设我们知道如何对前n-1个元素,也就是数组A[1…n-1]排序。
那么在对A[1…n-1]排序后,我们只要把A[n]插入它的适当位置,假设是位置j,1≤j ≤n。
这个插入可能导致A[j+1], A[j+2] ,… ,A[n-1]移到位置j+2,j+3,…,n.
时间复杂度为:O(n^2)
void rec_insertion(int arr[], int n)
{
if (n <= 1) return;
//对剩余的n-1个元素排序
rec_insertion(arr, n - 1);//此语句位置不可更改
int last = arr[n - 1];
int j = n - 2;
while (j >= 0 && last < arr[j]) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = last;
}
//举例arr[] = { 10, 14, 3, 8, 5, 12 };
//排列顺序为
//10 14
//3 10 14
//3 8 10 14
//3 5 8 10 14
//3 5 8 10 12 14
注意递归的层次与顺序
5.合并排序
int temp[];//辅助空间
void merge_sort(int q[],int l,int r)
{
if(l>=r)return;
int mid=(l+r)>>1;
merge_sort(q, l, mid);
merge_sort(q,mid+1,r);
int i=l,j=mid+1,k=0;
while(i<=mid&&j<=r)
{
//将小的数放在temp左边
if(q[i]<=q[j]) temp[k++]=q[i++];
else temp[k++]=q[j++];
}
while(i<=mid)temp[k++]=q[i++];
while(j<=r)temp[k++]=q[j++];
for(i=l,j=0;i<=r;i++,j++)//把辅助空间的数据放到原数据
q[i]=temp[j];
}
排序算法的递归关系式都大同小异,即Sort(low,mid),Sort(mid+1,high)
重点掌握不同排序算法的思想即可
6.多项式求值(Homer规则)
double Horner(double a[], int n, double x)
{
double res = 0.0;
for(int i=n; i>=0; --i)
res = x*res + a[i];
return res;
}
7.输出n个整数的全排列
void permutations(int array[], int k, int m){
if(k == m){
for(int i=0; i<=m; i++){
cout <<array[i]<<" ";
}
cout <<endl;
}
else{
for(int i=k; i<=m; i++){
swap(array[k], array[i]);
permutations(array, k+1, m);
swap(array[k], array[i]);
}
}
}
8.输出n个整数的所有子集
//tag数组标记数字是否使用过
void subset(int array[], int tag[], int k, int n){
if(k == n+1){
cout <<"{"<<" ";
for(int i=0; i<=n; i++){
if(tag[i] == 1){
cout <<array[i]<<",";
}
}
cout <<"}"<<endl;
}
else{
tag[k] = 0;
subset(array, tag, k+1, n);
tag[k] = 1;//类似回溯
subset(array, tag, k+1, n);
}
}
9.假币问题
int falseCoin(int weight[], int lhs, int rhs)
{
if (lhs == rhs)
return lhs + 1;
//如果只剩下两个银币,则较轻的那个便是假币
else if (lhs == (rhs - 1))
{
return weight[lhs] < weight[rhs] ? lhs + 1 : rhs + 1;
}
int lsum = 0, rsum = 0;
//如果偶数个银币,则比较两等份
if ((rhs - lhs + 1) % 2 == 0)
{
for (int i = lhs; i < (lhs + (rhs - lhs + 1) / 2); i++)
{
lsum += weight[i];
}
for (int j = lhs + (rhs - lhs + 1) / 2; j <= rhs; j++)
{
rsum += weight[j];
}
//左右两份等重,则无假币
if (lsum == rsum)
return -1;
else
return (lsum < rsum) ? falseCoin(weight, lhs, lhs + (rhs - lhs) / 2) : falseCoin(weight, lhs + (rhs - lhs) / 2 + 1, rhs);
}
//如果奇数个银币,则比较除中间银币外的两等份
else if ((rhs - lhs + 1) % 2 != 0)
{
for (int i = lhs; i < (lhs + (rhs - lhs) / 2); i++)
{
lsum += weight[i];
}
for (int j = (lhs + (rhs - lhs) / 2 + 1); j <= rhs; j++)
{
rsum += weight[j];
}
//左右两份等重,则无假币
if (lsum == rsum && weight[lhs] == weight[lhs + (rhs - lhs) / 2])
return -1;
//如果两份等重,中间银币较轻,则中间银币为假币
else if (lsum == rsum && weight[lhs] > weight[lhs + (rhs - lhs) / 2])
return lhs + (rhs - lhs) / 2 + 1;
//否则,返回较轻那份中的假币
else
return (lsum < rsum) ? falseCoin(weight, lhs, lhs + (rhs - lhs) / 2 - 1) : falseCoin(weight, lhs + (rhs - lhs) / 2 + 1, rhs);
}
}
10.寻找最大值(最小值)
将数组分割成两半, A[1…n/2] 和A[(n/2) + 1…n];
在每一半中找到最大值;
int findMax(int a[],int low,int high)
{
if(high==low){
return a[high];
}
else
{
int mid = (low+high)/2;
int left = findMax(a, low, mid);//找到左边的最大值
int right = findMax(a, mid+1, high);//找到右边的最大值
return max(left,right);
}
}
11.二分搜索
将一个给定的元素x与一个已排序数组A[low…high].的中间元素做比较
如果x < A[mid],这里mid =(low + high)/2,则不考虑 A[mid…high] ,而对A[low…mid-1]重复实施相同的方法。
类似地,如果 x > A[mid],则放弃A[low…mid],而对A[mid+1…high]重复实施相同的方法.
int binarySearch(int a[],int low,int high,int x)
{
if(low>=high)return 0;
else
{
int mid = (low+high)/2;
if(x==a[mid])return mid;
else if(x<a[mid])return binarySearch(a, low, mid-1, x);
else return binarySearch(a, mid+1, high, x);
}
}
12.拓扑排序
对给定的无环有向图,要求按照某种顺序列出它的顶点序列,使图的每一条边的起点总在结束顶点之前。
bool topsort()
{
int hh = 0, tt = -1;
// d[i] 存储点i的入度
for (int i = 1; i <= n; i ++ )
if (!d[i])
q[ ++ tt] = i;
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (-- d[j] == 0)
q[ ++ tt] = j;
}
}
// 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。
return tt == n - 1;
}
13.求整数幂问题(减常因子法)
对求实数x的n次幂设计一个有效的算法
int power(int x,int n)
{
int y;
if(n==0)return 1;
else
{
y = power(x, n/2);
y = y*y;
if(n%2!=0)
y=y*x;
}
return y;
}
14.STRASSEN算法(解决矩阵乘法问题)
增加加减法的次数来减少乘法次数
15.最近点对问题
设S是平面上n个点的集合,在这一节中,我们考虑在S中找到一个点对p和q的问题,使其相互距离最短。
16.快速排序
void Quick_Sort(int arr[], int begin, int end){
if(begin > end)
return;
int tmp = arr[begin];
int i = begin;
int j = end;
while(i != j){
while(arr[j] >= tmp && j > i)
j--;
while(arr[i] <= tmp && j > i)
i++;
if(j > i){
swap(arr[i],arr[j]);
}
}
swap(arr[begin],arr[i]);
Quick_Sort(arr, begin, i-1);
Quick_Sort(arr, i+1, end);
}
17.众数问题
问题描述:
给定含有n 个元素的多重集合S,每个元素在S 中出现的次数称为该元素的重数。多重集S 中重数最大的元素称为众数。
例如,S={1,2,2,2,3,5}。
多重集S 的众数是2,其重数为3。
编程任务:
对于给定的由n 个自然数组成的多重集S,编程计算S 的众数及其重数。
#include <iostream>
#include <algorithm>
using namespace std;
const int N =1e5+10;
int MaxNum;//记录众数
int Num;//记录重数
int n;
int a[N];
void solve(int low,int high)
{
if(low>=high)return;
int mid = (low+high)/2;
int i = mid,j = mid;
//首先确定中间数的个数
while(a[i]==a[mid]&&i>=1)
i--;
while(a[j]==a[mid]&&j<=n)
j++;
if(j-i-1>=Num)//更新
{
Num = j-i-1;
MaxNum = a[mid];
}
if(i-low+1>=Num)//左边的数大于Num,则众数可能出现在左边
{
solve(low, i);
}
if(high-j+1>=Num)//右边的数大于Num,则众数可能出现在右边
{
solve(j, high);
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
sort(a+1,a+n+1);//排序
solve(1, n);
cout<<MaxNum<<endl<<Num<<endl;
return 0;
}