开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
一、分而治之理解
在计算机种,成为分治法,字面解释为分而治之,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即为子问题的解的合并。
这个技巧被用在很多经典算法中,例如:排序算法(快速排序、归并排序),傅立叶变换
算法可以分为三个部分:
- 分解:将原问题分解为若干个规模较小,相对独立,与原问题形式相同的子问题。
- 解决:若子问题规模较小且易于解决时,则直接解。否则,用返回解决子问题的方式的递归算法。
- 合并:将各子问题的解合并为原问题的解。
二、经典算法讲解
通过上面的描述,仅仅是对分治法有了基本的认识,接下来我们对一些场景进行讲解,加深大家的印象
2.1 快速排序
看到快速排序,我们轻而易举想到这是数组十大排序算法之一,我们是否真正的去了解它呢,我们一起来看一下吧😊
快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
我们根据算法的三步骤来进行分析
- 分解:从数列中挑出一个元素,称为 “基准”(pivot),所有元素小于pivot放左边 ,大于pivot放右边
- 解决:方法1将数列分区,我们在区中分别递归执行分解操作
- 合并:将上述返回的结果合并即为原问题的解
步骤中说明了基准(pivot),要如何合理的选择基准值pivot,该值的取值对该算法有相当影响,若pivotkey取到了最大或最小,都会增加算法复杂度,影响性能
1.随机选取,在待排序列中随机选取,以降低取到最大或最小值的概率 2.三数取中,在待排序列的左端,中间,右端去三个值选取中位数,节省随机数产生的时间开销,以降低取到最大或最小值的概率 3. 九数取中,三次取样,每次取三个数,取它们的中位数,再取三个中位数的中位数
相关图例:
通过上面的讲解,我们可实现代码如下:
function quickSort(arr, left, right) {
let len = arr.length,
partitionIndex;
left = typeof left != 'number' ? 0 : left;
right = typeof right != 'number' ? len - 1 : right;
// 递归结束条件:left大于等于right的时候
if (left >= right) {
return;
}
// 得到基准元素的位置
partitionIndex = partition(arr, left, right);
quickSort(arr, left, partitionIndex-1); // 递归处理
quickSort(arr, partitionIndex+1, right);
return arr
}
// 解决部分:所有元素小于pivot放左边 ,大于pivot放右边
function partition(arr, left, right){
// 设定基准值(pivot)
let pivot = left,index = pivot + 1;
for (var i = index; i <= right; i++) {
if (arr[i] < arr[pivot]) {
swap(arr, i, index);
index++;
}
}
swap(arr, pivot, index - 1);
return index-1;
}
function swap(arr, i, j){
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
2.2 归并排序
首先,它是分治算法的典型应用。 作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
- 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
- 自下而上的迭代;
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。
我们根据算法的三步骤来进行分析(可能和大家理解的又误差,欢迎指正😀):
- 分解:把长度为n的数列分成两个长度为n/2的数列,在分解时注意区别的开闭,我们接下来采用左闭右开或者左闭右闭的写法。
- 解决:方法1 分区,一直到数组长度为1,则返回
- 合并:如何合并两个有序数组,我们单独使用一个辅助函数 mergeArray 来进行合并操作。合并操作分为以下几个步骤:
- 定义一个结果数组 res,定义两个待合并数组的下标 i 和 j;
- 比较当前项的大小,将更小项放入 res 中,对应下表加一;
- 某个数组遍历完毕后,将另一个的数组拼接到结果数组中;
相关图例:
相关代码:
function mergeSort(arr) {
let len = arr.length
if (len < 2) {
return arr
}
let middle = Math.floor(len / 2),
left = arr.slice(0, middle),
right = arr.slice(middle)
return merge(mergeSort(left),mergeSort(right))
}
function merge(left, right) {
// 将数组按照一定的顺序合并
var result = [];
while(left.length && right.length){
if(left[0] < right[0]){
result.push(left.shift())
}else{
result.push(right.shift())
}
}
while(left.length){
result.push(left.shift())
}
while(right.length){
result.push(right.shift())
}
return result
}