前言
这次我们要学习的是一种基本的算法思想,分治。之前学习的二分查找其实是属于查找算法里的一类,但它也包含着分治思想。
一、主要思想
分治算法,根据字面意思解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。就像我们打牌前需要先洗牌,若牌的数目较多,一个人洗不过来,则会将牌进行分堆,单独洗一小堆牌是相对容易的,每一堆牌都洗完之后再放到一起,则完成洗牌过程。
二、使用场景
①该问题的规模只要小到一定程度就可以容易地解决。
②该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
③该问题分解出的子问题的解可以合并为总问题的解。
④该问题所分出的各个子问题之间相互独立,互不影响。
三、基本步骤
①分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题。
②求解:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题。
③合并:将各个子问题的解合并为原问题的解。

四、典型例题
1.归并排序
归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表。
图片来源于公众号:五分钟学算法
步骤
- 分解:将原问题(整个待排序列)分解为若干个规模较小,相互独立,与原问题形式相同的子问题(子序列)。本题我们采用折半的分解方法。
- 求解:若子问题规模较小而容易被解决则直接解(若子序列小到只有一个元素,就可以结束递归),否则递归地解各个子问题(否则分为左右子序列递归排序)。
- ③合并:将各个子问题的解(已经排好序的子序列)合并为原问题的解。在本题中使用Merge函数进行合并,也正是在Merge中才涉及到真正的比较大小从而进行排序。
代码
void Merge(int a[], int left, int mid, int right)
{//将两个排好序的子序列合并为一个,此过程也需要排序
int* temp = new int[right - left + 1]; //声明一个辅助数组并开辟空间
int pl = left; //一个用来遍历左侧子序列的指针
int pr = mid + 1; //一个用来遍历右侧子序列的指针
int pnow = 0; //一个存放指针
while (pl <= mid && pr <= right)
{//两个子序列都没有遍历完时,执行下面语句
if (a[pl] <= a[pr])
temp[pnow++] = a[pl++];
else
temp[pnow++] = a[pr++];
}
while (pl <= mid)
{//如果左边序列未检测完,直接将后面所有元素加到合并的序列中
temp[pnow++] = a[pl++];
}
while (pr <= right)
{//如果右边序列未检测完,直接将后面所有元素加到合并的序列中
temp[pnow++] = a[pr++];
}
for (int i = 0; i <= right - left; i++)
{//将排好序的数组返回给原数组
a[i+left] = temp[i];
}
}
void MergeSort(int a[], int start, int end)
{//传入一个待排数组,以及待排序片段的始末位置
if (start < end)
{//当子序列中只有一个元素时结束递归
int mid = (start + end) / 2; //从中间划分子序列
MergeSort(a, start, mid); //对左侧子序列进行递归排序
MergeSort(a, mid + 1, end); //对右侧子序列进行递归排序
Merge(a, start, mid, end); //合并排序好的左右子序列
}
}
分析
时间复杂度为O(nlogn)。
2.快速排序
步骤
- 在待排序列中任选一个数据作为基准(pivot),为这个基准在序列中找到一个位置,使得序列中左边元素都小于等于基准,右边元素都大于等于基准
- 完成第一步后,基准左右两边还是无序的,所以需要分别继续按照第一步的方法排序,以此类推
代码
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
void QuickSort(int arr[], int low, int high)
{
if (high <= low)
return; //传入数组只有一个元素时直接返回,也是递归的结束条件
int i = low; //指向序列头部
int j = high; //指向序列尾部
int key = arr[low]; //基准
while (i != j)
{//下列循环直到i = j时候结束
while (j > i && arr[j] >= key) //从右边开始遍历
j--; //只要j指向的元素大于key,就让j减一,指向下一个
swap(arr[i], arr[j]); //一旦j指向的元素不大于key,就让这个元素与key交换位置
while (j > i && arr[i] <= key) //从左边开始遍历
i++; //只要i指向的元素小于key,就让i加一,指向下一个
swap(arr[i], arr[j]); //一旦i指向的元素不小于key,就让这个元素与key交换位置
}
/*完成上述代码以后,key的左右两边都分别小于和大于key,接下来要继续如此排序左右两边的序列*/
/*完成上述代码后i=j,arr[i]=arr[j]=key*/
QuickSort(arr, low, i - 1); //对左边序列进行递归排序
QuickSort(arr, i + 1, high); //对右边序列进行递归排序
}
分析
时间复杂度为O(nlogn),最坏情况下为O(n^2)。
763





