复习分治法,借着这个机会将用到分治法的合并排序和快速排序算法好好梳理一下并作出总结。
分治法求解问题的三要素是分解、求解、合并。分解是指将一个难以直接求解的复杂问题按照某种方式分解成若干个规模较小、相互独立且和原问题类型相同的子问题,求解是指子问题分解至可直接求解,合并是指将子问题的解以某种方式合并成原问题的解。
分治法求解排序问题的思想很简单,只需以某种方式将序列分成两个或多个子序列,分别进行排序,再将已排序的子序列合并成一个有序序列即可。合并排序和快速排序是两种典型的运用了分治策略的排序算法。
首先介绍最基本的合并排序算法——两路合并排序。
两路合并排序的基本运算是将两个有序序列合并成一个有序序列。以数列一(5,25,55)和数列二(10,20,30)合并成有序序列(5,10,20,25,30,55)。实现这种合并的算法:比较两个数列的最小值,输出其中的较小者,重复此过程直至一数列为空,依次输出另一非空数列的剩余元素即可。下面演示此过程:
数列一:5,25,55 数列二:10,20,30 生成数列:
5<10 输出5 数列一:25,55 数列二:10,20,30 生成数列:5
10<25 输出10 数列一:25,55 数列二:20,30 生成数列:5,10
…… 数列一:55 数列二: 生成数列:5,10,20,25,30
输出55 数列一:55 数列二: 生成数列:5,10,20,25,30,55
算法执行完成。
笔者使用的是陈慧南版算法设计与分析。其中算法主要以类的形式实现,不过在理解好算法之后相信是很容易将之独立抽象成函数。
下面贴出以上合并函数的代码:
template
void SortableList::Merge(int left, int mid, int right)
{ //a数组为待排序的数组
T* temp =new T[right - left + 1]; //temp数组用来暂时保存排序好的数组
int i = left, j = mid + 1, k = 0; //left 至mid为左半数组 ,mid+1至right为右半数组
while((i <= mid)&&(j <= right)) //对应第一步 当左半数组和右半数组都不为空时
if(a[i] <= a[j]) //输出两个数组中的较小者
temp[k ++] = a[i ++];
else
temp[k ++] = a[j ++];
while(i <= mid) //当一数组为空时,输出另一数组的剩余元素
temp[k ++] = a[i ++];
while(j <= right) //当一数组为空时,输出另一数组的剩余元素
temp[k ++] = a[j ++];
for(i = 0, k = left; k <= right;) //将temp数组赋值给原数组
a[k ++] = temp[i ++];
}
合并排序的分解很简单,主要难点在于合并(merge)函数而快速排序难点在于分解,这个稍后再说。下面使用分治法求解,分治法两路合并算法可描述为三步:第一步,将待排序的元素序列一分为二,得到两个长度基本相同的子序列;第二步,对两个子序列分别排序,若子序列较长则继续细分重复以上一分为二的步骤,直至子序列的长度不大于1为止,此为关键步骤,需要将数列二分到每个子数列的长度不超过一为止,因为只有不大于一的数列可直接求解,这一点在之后过程会演示;第三步将排好序的子序列合并成一个有序数列,实现函数即为以上的merge函数。
以数组5,2,4,7,1,3,2,6为例,先将整个数组分解至每个子序列长度不超过1,即分解至以下图片的最底一行,然后调用merge函数向上层逐层合并递归成最终排序好的数列。
以下是mergesort函数代码:template
void SortableList::MergeSort(int left, int right)
{
if(left < right) //控制 当调用至序列只剩一个元素结束函数向上层递归执行
{
int mid = (left + right) / 2;// 一分为二
MergeSort(left, mid); //二分左边序列
MergeSort(mid + 1, right);//二分右边序列
Merge(left, mid, right); //排序
}
}
最后贴上完整代码:
#include
#include
#include
using namespace std;
void Swap(int &a, int &b)
{
int t = a;
a = b;
b = t;
}
template
class SortableList
{
public:
SortableList(int m)
{
n = m;
}
void MergeSort();
void Merge(int left, int mid, int right);
void QuickSort();
void Input();
void Init();
void Output();
private:
int RPartition(int left, int right);
int Partition(int left, int right);
void MergeSort(int left, int right);
void QuickSort(int left, int right);
T l[1000];//输入的数组值
T a[1000];//实际排序对象
int n;
};
template
void SortableList::Input()
{
for(int i = 0; i < n; i++)
cin >> l[i];
}
//Init()函数的作用是在两路合并排序结束后将序列恢复到初始序列
//再进行快速排序
template
void SortableList::Init()
{
for(int i =0; i < n; i++)
a[i] = l[i];
}
template
void SortableList::Output()
{
for(int i = 0; i < n; i++)
cout << a[i] << " ";
cout << endl << endl;
}
//两路合并排序
template
void SortableList::MergeSort()
{
MergeSort(0, n - 1);
}
template
void SortableList::MergeSort(int left, int right)
{
if(left < right) //控制 当调用至序列只剩一个元素结束函数向上层递归执行
{
int mid = (left + right) / 2;// 一分为二
MergeSort(left, mid); //二分左边序列
MergeSort(mid + 1, right);//二分右边序列
Merge(left, mid, right); //排序
}
}
template
void SortableList::Merge(int left, int mid, int right)
{ //a数组为待排序的数组
T* temp =new T[right - left + 1]; //temp数组用来暂时保存排序好的数组
int i = left, j = mid + 1, k = 0; //left 至mid为左半数组 ,mid+1至right为右半数组
while((i <= mid)&&(j <= right)) //对应第一步 当左半数组和右半数组都不为空时
if(a[i] <= a[j]) //输出两个数组中的较小者
temp[k ++] = a[i ++];
else
temp[k ++] = a[j ++];
while(i <= mid) //当一数组为空时,输出另一数组的剩余元素
temp[k ++] = a[i ++];
while(j <= right) //当一数组为空时,输出另一数组的剩余元素
temp[k ++] = a[j ++];
for(i = 0, k = left; k <= right;) //将temp数组赋值给原数组
a[k ++] = temp[i ++];
}
int main()
{
int m;
cout << "数组长度n: ";
cin >> m;
SortableList List(m);
cout << "输入" << m << "个数字:" << endl;
List.Input();
List.Init();//得到初始状态
List.MergeSort();
cout << "两路合并排序后:" << endl;
List.Output();
return 0;
}
特记下,以备后日回顾。