分治策略在我们的排序算法中的快速排序、归并以及二分查找中经常用到,本文就来探讨一下分治策略。当然,与分治策略密不可分的还有我们经常用到的递归。
目录
一、递归基础
二、分治策略基础
三、来看几个递归分治的例子
1、快速排序
2、求第K大(小)/最小(大)的K个数
3、最接近点对问题
一个直接或间接的调用自身的算法称为递归算法。递归的使用会让我们对于问题的理解更加的清楚。但是并非所有的程序都适合用递归去进行解决。
分治策略的设计思想:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。递归的解决这些子问题,然后将这些子问题的解合并得到原问题的解。
它缩小的是问题的规模,反复使用分治策略,可以使子问题与原问题类型一致而规模却不断缩小。在分治策略中它的主要思想就是划分,尽可能将问题的规模划分最小。
快速排序是我们八大排序中的一种,它的主要思想就是基于划分的,通过划分,将问题的规模缩小,对子问题进行解决最后合并得到原问题的解。快排的最主要的一点就是做好划分。因此在实现快排的时候,划分部分我们有三种方法。
(1)划分部分
第一是按照通常的做法从第一个开始选取记录,进行划分;
第二是三位取中法,即就是找到原序列中的左中右三个位置的值,找到第二大的数值作为标记;
第三是随机化法,随机选取一个位置的数值,把最左边的值与其进行交换。
(2)排序部分
第一种是按照递归的做法实现排序;
第二种是非递归实现排序,在非递归实现排序的时候可以利用栈、队列、pair等容器作为支持;
具体实现代码:
//划分部分:第一个数值作为记录进行交换
template
int Partition(Type *arr,int left,int right)
{
int i = left;
int j = right;
Type temp = arr[i];
while(i < j)
{
while(i temp) --j;
if(i < j) arr[i] = arr[j];
while(i
int MidPartition(Type *arr,int left,int right)
{
int mid = (right - left + 1)/2+left;
//arr[left],arr[mid],arr[right]
if(arr[mid] > arr[left] && arr[mid] < arr[right])
{
swap(arr[left],arr[mid]);
}else if(arr[right] > arr[m] && arr[right] < arr[left])
{
swap(arr[left],arr[right]);
}
return Partition(arr,left,right);
}
//划分部分:随机化法
template
int RandPartition(Type *arr,int left,int right)
{
srand(time());
int rpos = rand() % (right - left + 1)+left;
swap(arr[left],arr[rpos]);
return Partition(arr,left,right);
}
//递归实现
template
void PassQuick(Type *arr,int left ,int right)
{
if(left < right)
{
int mid = Partition(arr,left,right);
PassQuick(arr,left,mid-1);
PassQuick(arr,mid+1,right);
}
}
//非递归实现,利用栈
template
void SNicePassQuick(Type *arr,int left ,int right)
{
stack st;
if(left >= right) return;
st.push(left);
st.push(right);
while(!st.empty())
{
right = st.top();st.pop();
left = st.top();st.pop();
int mid = Partition(arr,left,right);
if(left < mid -1)
{
st.push(left);
st.push(mid-1);
}
if(mid+1 < right)
{
st.push(mid+1);
st.push(left);
}
}
}
//非递归实现,利用队列
template
void QNicePassQuick(Type *arr,int left ,int right)
{
queue qu;
if(left >= right) return;
qu.push(left);
qu.push(right);
while(!qu.empty())
{
left = qu.front();qu.pop();
right = qu.front();qu.pop();
int mid = Partition(arr,left,right);
if(left < mid -1)
{
qu.push(left);
qu.push(mid-1);
}
if(mid+1 < right)
{
qu.push(mid+1);
qu.push(left);
}
}
}
//非递归实现,利用队列中的pair
template
void PQNicePassQuick(Type *arr,int left ,int right)
{
queue> qu;
if(left >= right) return;
pairLR(left,right);
qu.push(LR);
while(!qu.empty())
{
LR = qu.front();qu.pop();
int mid = Partition(arr,LR.first,LR.second);
if(LR.first < mid -1)
{
pairS1(left,mid-1);
qu.push(S1);
}
if(mid+1 < LR.second)
{
pairS2(mid+1,right);
qu.push(S2);
}
}
}
template
void QuickSort(Type *arr,int n)
{
//PassQuick(arr,0,n-1);
//SNicePassQuick(arr,0,n-1);
//QNicePassQuick(arr,0,n-1);
PQNicePassQuick(arr,0,n-1);
}
这类题目的思想,当然也是我们的分治+递归。
例如,求第K大,对原序列进行划分,第一划分后得到以下下标index,然后利用K和index进行比较,如果K 找最小的K个数,同样也是进行划分,直到在第K个位置前的数都小于第k个位置的数,这样我们就找到了最小的K个数。 具体的实现如下: 问题的提法:给定水平面上的n个点,找其中一对点,使得在n个点所组成的所有点对中,该点对间的距离最小。 这里我们可以将它映射为一维数组上相差最小的两个值。这看起来很简单将其排序然后一次线性扫描就能找到。 二维的情况下,假设用x轴上某个点m将S划分为两个集合S1和S2,最接近点对有可能在s1中,也有可能在s2中,还有可能在s1和s2之间,对于第三种情况找到s1中最小的,找到s2中最大的,三种情况进行比较,找到最接近的。 具体的实现如下: 当然除了这些还有像求阶乘、斐波那契数列、全排列、归并排序以及二分查找等,这些都可以利用这样的思想进行实现。/*查找第k小的数:将区域划分到最小,也就是当区域中只有一个数时,返回的就是该数
主要思想是划分,分治策略
*/
template
3、最接近点对问题
template