分治法
- 分治法的设计思想:分治法顾名思义即分而治之,对于一个规模为n的问题,若该问题可以很容易的解决,如当n很小的情况下,那么就直接解决,否则,将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归的求解这些子问题,然后将这些子问题的解合并得到原问题的解。所以分治法和递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效的算法。
- 可以应用分治法来解决的问题应该具有的特征:
(1)该问题规模缩小到一定的程度就可以很容易的解决
(2)该问题可以分解为若干个规模较小的相似问题
(3)利用该问题分解出来的若干个子问题的解可以和并得到该问题的解
(4)该问题分解出来的各个子问题是相互独立的,即子问题之间不包含公共子问题
3. 分治法求解步骤
(1)分解原问题:将原问题分解成若干个问题规模更小且与原问题结构类似的子问题
(2)求解子问题:若这些子问题比较简单可以直接求解,那么就直接求解,否则递归的求解各个子问题
(3)合并子问题:合并各个子问题的解,得到原问题的解
二路归并排序
- 二路归并排序算法思想:先将待排序的序列a[n]={a1,a2,a3,a4,.....,an},看成n个独立的子序列,{a1},{a2},{a3},.....,{an}。那么显而易见这n个序列都是有序的,对于这n个有序序列进行第一次归并排序时,从左往右两两合并成一个更大的有序子序列,此时这些有序子序列中的元素个数为2,此时就将原序列分成了[n/2]或者([n/2]+1)个子序列,再继续将这些子序列从左往右两两合并成为一个更大的有序子序列,直至合并到只有一个子序列且该子序列中的元素个数为n为止,二路归并排序算法结束。
- 二路归并排序的分治策略
(1)分解:将原序列分解成为长度为length的若干个有序子序列
(2)求解子问题:将这些子序列从左至右两两合并形成一个更大的有序子序列。
(3)合并,该问题就是在原序列的基础上不断地拆分然后合并所以在求解过程中这一步操作已经完成了不需要特地再加合并这一项操作。
3. 算法求解思路
(1)设计一个函数Merge用于将相邻的两个有序的子序列合并成一个更大的有序子序列,由于用数组来存储,那么逻辑上相邻的两个子序列在物理存储位置上也是相邻的,那么我们需要一个下标low来表示第一个有序子序列的起始位置,一个下标mid表示第一个有序子序列的终点,由于两个子序列相邻那么另一个有序子序列的起始位置为mid+1,需要另一个下标high表示另一个相邻子序列的终止位置,还需要设置一个临时的数组temp用来暂时存放将这两个有序子序列合并且排序使其有序的结果,然后最后排序完成之后再将temp中的元素一一复制到原来的待排序序列的相应位置处即可,代码实现如下。
//该函数的作用是用于将两个相邻的有序序列合并
void Merge(int array[], int low, int mid, int high,int n)
{
int* temp = (int*)malloc(n * sizeof(int)); //用于临时存放归并好的两个有序序列
int i = low, j = mid + 1, k = 0;
while (i <= mid && j <= high)
{
if (array[i] < array[j])
{
temp[k] = array[i];
i++;
k++;
}
else
{
temp[k] = array[j];
j++;
k++;
}
}
if (i > mid)
{
for (; j <= high; j++)
{
temp[k] = array[j];
k++;
}
}
else if (j > high)
{
for (; i <= mid; i++)
{
temp[k] = array[i];
k++;
}
}
//将归并好的有序序列放在原序列的对应位置上
for (int i = low, j = 0; i <= high; i++, j++)
{
array[i] = temp[j];
}
free(temp);
}
(2)设计一个函数MergePass用于做一趟二路归并排序,一趟二路归并排序要做的内容就是从左往右将各个有序子序列进行两两合并,在合并的过程中要注意到若有序子序列的个数为奇数个的话,最后一个有序子序列不参与合并,若原待排序列的元素个数为n,由其划分而来的子序列的长度为length,那么划分的有序子序列的个数为n/length或者为n/length+1若为n/length + 1,那么最后一个有序子序列的长度不足length,所以要进行两两合并的次数为有序子序列个数除以2向下取整,算法实现如下:
//该算法用于解决一趟归并排序问题
void MergePass(int length, int n, int array[]) //length表示该躺归并排序中各个有序子序列的长度为一1,这里要考虑最后一个有序子序列长度不为length以及总的有序子序列的1个数是奇数个的情况
{
//该躺归并排序的有序子序列的个数为[n/length]
//考虑有序子序列为奇数的情况,当其为奇数时,最后一个有序子序列不参与归并,只需要归并前[n/length]-1个有序子序列即可,若其为偶数则需要归并所有的有序子序列
int all_subseq;
int low, mid, high;
if (n % length)
{
all_subseq = n / length + 1;
}
else
{
all_subseq = n / length;
}
for (int i = 0; i < all_subseq / 2; i++)
{
low = i * 2 * length;
mid = low + length - 1;
high = low + 2 * length - 1;
if (high >= n)
{
high = n - 1;
}
Merge(array, low, mid, high, n); //只要low的位置确定了那么mid和high的位置也就随之确定;
}//将相邻的有序子序列两两归并
}
(3)设计一个函数用于完成最终的二路归并排序,在此过程中需要用到前面定义的两个函数的功能来辅助实现,二路归并排序中第二次归并排序后得到的子序列的长度是第一次二路归并排序得到的子序列长度的两倍,最后一次二路归并排序是将两个有序子序列合成一个有序子序列,所以在设计过程中注意循环终止的条件,算法实现如下:
void MergeSort(int array[], int n)
{
int length;
for (length=1;length<n;length=2*length)
{
MergePass(length, n, array);
}
}
4. 程序源代码以及运行结果截图
(1)程序源代码
//用分治法求解二路归并排序
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
//该函数的作用是用于将两个相邻的有序序列合并
void Merge(int array[], int low, int mid, int high, int n);
//该算法用于解决一趟归并排序问题
void MergePass(int length, int n, int array[]); //length表示该躺归并排序中各个有序子序列的长度为一1,这里要考虑最后一个有序子序列长度不为length以及总的有序子序列的1个数是奇数个的情况
//该算法用于实现二路归并排序,需要用到前面定义的两个函数的功能
void MergeSort(int array[], int n);
int main()
{
int array[9] = { 11,7,6,5,4,9,12,3,2 };
printf("未排序前的序列:\n");
for (int i = 0; i < 9; i++)
{
printf("%d\t", array[i]);
}
printf("\n");
MergeSort(array, 9);
printf("按照升序排序后的序列:\n");
for (int i = 0; i < 9; i++)
{
printf("%d\t", array[i]);
}
return 0;
}
//该函数的作用是用于将两个相邻的有序序列合并
void Merge(int array[], int low, int mid, int high,int n)
{
int* temp = (int*)malloc(n * sizeof(int)); //用于临时存放归并好的两个有序序列
int i = low, j = mid + 1, k = 0;
while (i <= mid && j <= high)
{
if (array[i] < array[j])
{
temp[k] = array[i];
i++;
k++;
}
else
{
temp[k] = array[j];
j++;
k++;
}
}
if (i > mid)
{
for (; j <= high; j++)
{
temp[k] = array[j];
k++;
}
}
else if (j > high)
{
for (; i <= mid; i++)
{
temp[k] = array[i];
k++;
}
}
//将归并好的有序序列放在原序列的对应位置上
for (int i = low, j = 0; i <= high; i++, j++)
{
array[i] = temp[j];
}
free(temp);
}
//该算法用于解决一趟归并排序问题
void MergePass(int length, int n, int array[]) //length表示该躺归并排序中各个有序子序列的长度为一1,这里要考虑最后一个有序子序列长度不为length以及总的有序子序列的1个数是奇数个的情况
{
//该躺归并排序的有序子序列的个数为[n/length]
//考虑有序子序列为奇数的情况,当其为奇数时,最后一个有序子序列不参与归并,只需要归并前[n/length]-1个有序子序列即可,若其为偶数则需要归并所有的有序子序列
int all_subseq;
int low, mid, high;
if (n % length)
{
all_subseq = n / length + 1;
}
else
{
all_subseq = n / length;
}
for (int i = 0; i < all_subseq / 2; i++)
{
low = i * 2 * length;
mid = low + length - 1;
high = low + 2 * length - 1;
if (high >= n)
{
high = n - 1;
}
Merge(array, low, mid, high, n); //只要low的位置确定了那么mid和high的位置也就随之确定;
}//将相邻的有序子序列两两归并
}
//该算法用于实现二路归并排序,需要用到前面定义的两个函数的功能
void MergeSort(int array[], int n)
{
int length;
for (length=1;length<n;length=2*length)
{
MergePass(length, n, array);
}
}
(2)运行结果截图