算法设计与分析之分治策略练习
分治策略解决问题一:折半查找
问题描述
给定已按升序排好序的n个元素a[0:n-1],现要在这n个元素中找出一特定元素x。
问题分析
从元素中间开始寻找,不断的折半进行查找x,每次折半查找的子问题相互独立。
算法实现
//折半查找递归实现
int binarySearch(int a[],int left,int right,int x){
if(left<=right){
int middle = (left + right) / 2;
if(x == a[middle]) return middle;
else if(x < a[middle]) return binarySearch(a,left,middle-1,x);
else return binarySearch(a,middle+1,right,x);
}
return -1; //没找到返回-1
}
分治策略解决问题二:二分查找技术应用一
问题描述
给出若干个整数,询问其中是否有一对数的和等于给定的数.
输入:
4
2 5 1 4
6
输出
1 5
说明: 如果有多对数符合要求,输出最小数最小的一对
问题分析
- 先将数组从小到大排序
- 使用折半查找寻找与前者对应的一个值
- 找到即停止查找,否则继续
算法实现
//折半查找递归实现
int binarySearch(int a[],int left,int right,int x){
if(left<=right){
int middle = (left + right) / 2;
if(x == a[middle]) return middle;
else if(x < a[middle]) return binarySearch(a,left,middle-1,x);
else return binarySearch(a,middle+1,right,x);
}
return -1; //没找到返回-1
}
void SumNumberSearch(int a[],int length,int sum){
for(int i=0;i<length;i++){
if(binarySearch(a,i+1,length-1,sum-a[i])){
cout<<a[i]<<" "<<sum-a[i];
break;
}
}
}
分治策略解决问题三:二分查找技术应用二
问题描述
输入n个数,从小到大将他们输出,重复的数只输出一次
输入:
5
2 4 4 5 1
输出:
1 2 4 5
问题分析
最简单的方法是使用set,利用set的性质,这里我们不使用此方法,采用二分查找技术
- 首先将数组从小到大排序
- 从中间的数开始,分别记录该数第一次出现的位置和最后一次出现的位置
- 同方法分析左侧的子序列
- 输出该数
- 同方法分析右侧子序列
算法实现
void lineSearch(int a[],int left,int right){
int number,middle,l,r,i;
if(left>right) return;
middle = (left + right)/2;
number = a[middle];
i = middle - 1;
while(a[i]==number && i>=left) i--;
l = i;
i = middle + 1;
while(a[i]==number && i<=right) i++;
r = i;
lineSearch(a,left,l);
cout<<number<<" ";
lineSearch(a,r,right);
}
分治策略解决问题四:众数和重数
问题描述
对于给定的由n个自然数组成的多重数集S,编程计算S的众数及其重数
问题分析
- 用数组存储
- 先将数组排序
- 记录中间数的下标,中间数出现的数量,中间数第一次出现的位置
- 把中间数的重数和之前记录的重数比较,如果大于之前记录的重数,则更新num和sum
- 如果最大的重数比中间数的右边的数字数量小,则向右递归分析
- 如果最大的重数比中间数的左边的数字数量小,则向左递归分析
算法实现
#include <iostream>
#include <algorithm>
#define N 100
using namespace std;
int num = 0; //存储众数
int sum = 0; //存储重数
/*
统计中间数出现的数量
*/
int count(int[],int,int);
/*
找到中间数第一次出现的位置
*/
int start(int[],int,int);
/*
找众数和其重数
*/
void modeAndMultiplicity(int[],int,int);
int main()
{
int a[N],n;
cout<<"请输入数组元素数量:";
cin>>n;
cout<<"请输入数组元素:";
for(int i=0;i<n;i++)
cin>>a[i];
sort(a,a+n);
modeAndMultiplicity(a,0,n-1);
cout<<"众数"<<num<<"的重数为"<<sum<<endl;
return 0;
}
/*
统计中间数出现的数量 实现
*/
int count(int a[],int front,int rear){
int i = 0; //计数器
int mid = a[(front+rear)/2];
for(int j=front;j<=rear;j++){
if(a[j] == mid){
i++;
}
}
return i;
}
/*
找到中间数第一次出现的位置 实现
*/
int start(int a[],int front,int rear){
int x = 0;
int mid = a[(front+rear)/2];
for(int i=front;i <= rear;i++){
if(a[i] == mid){
x = i;
break;
}
}
return x;
}
/*
找众数和其重数 实现
*/
void modeAndMultiplicity(int a[],int front,int rear){
int mNum = (front+rear)/2; //当前中间数的下标
int mSum = count(a,front,rear); //当前中间数的重数
int mLeft = start(a,front,rear); //当前中间数第一次出现的位置
if(mSum > sum) { //重数大则替换众数和其重数
sum = mSum;
num = a[mNum];
}
if(rear-(mLeft+mSum)+1 > sum){ //右边数量大于重数 则向右找
modeAndMultiplicity(a,mLeft+mSum,rear);
}
if(mLeft > sum){ //左边数量大于重数 则向左找
modeAndMultiplicity(a,front,mLeft-1);
}
}
分治策略解决问题五:区间合并
问题描述
给定 n 个闭区间 [ai; bi],其中i=1,2,…,n。任意两个相邻或相交的闭区间可以合并为一个闭区间。例如,[1;2] 和 [2;3] 可以合并为 [1;3],[1;3] 和 [2;4] 可以合并为 [1;4],是[1;2] 和 [3;4] 不可以合并。
判断这些区间是否可以最终合并为一个闭区间,如果可以,将这个闭区间输出,否则输出no。
问题分析
- 定义区间结构体进行存储并定义比较函数
- 将输入的区间组排序后,左右两边进行分治,直至缩小到两个小区间合并问题
- 如果不能合并,程序输出no并直接退出
- 如果可以,分治结束后结果合并即可得到最终区间
算法实现
#include <iostream>
#include <algorithm>
//定义区间数量
#define N 100
using namespace std;
//定义区间结构体
struct Interval{
int left,right;
};
//定义区间比较函数
bool compareInterval(Interval,Interval);
//区间合并
Interval intervalMerge(Interval[],int,int);
int main()
{
Interval interval[N];
int n;
cout<<"请输入区间数量:";
cin>>n;
for(int i=0;i<n;i++)
cin>>interval[i].left>>interval[i].right;
sort(interval,interval+n,compareInterval);
Interval tempInterval = intervalMerge(interval,0,n-1);
cout<<tempInterval.left<<" "<<tempInterval.right<<endl;
return 0;
}
bool compareInterval(Interval mLeft,Interval mRight){
if(mLeft.left < mRight.left)
return true;
return false;
}
Interval intervalMerge(Interval internal[],int left,int right){
if(left == right)
return internal[left];
Interval tempInterval1 = intervalMerge(internal,left,(left+right)/2),
tempInterval2 = intervalMerge(internal,(left+right)/2+1,right);
if(tempInterval1.right >= tempInterval2.left){ //符合合并条件 进行合并
Interval tempInterval;
tempInterval.left = tempInterval1.left;
tempInterval.right = tempInterval1.right>=tempInterval2.right
?tempInterval1.right
:tempInterval2.right;
return tempInterval;
}else{ //否则退出
cout<<"no"<<endl;
exit(0);
}
}