快速排序是一种常用的排序算法,快速排序也使用了分而治之(divide /dɪˈvaɪd/ and conquer /ˈkɒŋkər/ 简称D&C)。分而治之是一种思路,它是一种递归思想,每个递归函数都有两部分:
- 基线条件(base case)
- 递归条件(recursive case) /rɪˈkɜːsɪv/
递归条件指的是函数调用自己,而基线条件则指的是函数不再调用自己,从而避免形成无限循环。
function fibonacci(n) {
if (n <=2) { // n <=2 基线条件
return 1
};
return fibonacci(n - 2) + fibonacci(n - 1); // n>2 为递归条件
};
fibonacci(10) //55
有了递归的基础接下来我们再来看下快速排序的写法!
1.快速排序易懂版
有下图这么一个数组[4, 5, 1, 6, 9, 22, 6]
我们先确定基准点,那么我们取中间值为基准点!
然后以基准点为参照值对数组进行排序,比6小的放左边,比6大的放右边!
第一轮排序完成了,我们开始对左右两边再进行排序,先看左边的!
和上面一样,先取中间值为基准点!
然后以基准点为参照值对数组进行排序,比5小的放左边,比5大的放右边,这里4,1都比5小,所以都在左边,右边没有值!
接下来对1,4这两个元素进行排序,先取基准点!
以基准点为参照值对数组进行排序,比4小的放左边,比4大的放右边,右边也没有值!
再看右边的数组!
先取中间值为基准点!
然后以基准点为参照值对数组进行排序,比22小的放左边,比22大的放右边,这里9,6都比22小,所以都在左边,右边也没有值!
接下来对6,9这两个元素进行排序,先取基准点!
以基准点为参照值对数组进行排序,比9小的放左边,比9大的放右边,右边也没有值!
最终合并数组完成整个排序(因为数字比较少,我把所有的步骤都列出来了)!
上面是快速排序的一个完整的步骤!那么我们代码中怎么实现!
- 明确基线条件和递归条件(当数组的元素为空或者只包含一个元素,在这种情况下,只需原样返回数组,根本就不用排序,那么当数组的元素大于1时就是递归条件)。
- 选择好基准点为parseInt(arr.length / 2)。
- 创建左边一个数组left,右边一个数组为right,当比基准点大时放进right里面,比基准点小时放进left里面。
- 然后对left,right 不断的重复2,3步骤。
- 合并数组。
function quickSort(arr) {
let temp = arr.slice(0), //复制一个数组
left = [],
right = [],
middle = temp[parseInt(temp.length / 2)]; //取基准点
if (temp.length < 2) {//递归的基线条件
return temp;
};
temp.splice(parseInt(temp.length / 2), 1); //数组去掉基准点这个元素
for (var i = 0; i < temp.length; i++) {
if (temp[i] < middle) {
left.push(temp[i]) //比基准点小时放进left里面
} else {
right.push(temp[i])//比基准点大时放进right里面
};
};
return quickSort(left).concat([middle]).concat(quickSort(right));//递归并合并数组
};
console.log(quickSort([4, 5, 1, 6, 9, 22, 6])); //[1, 2, 5, 6, 6, 9, 22]
上面这种快速排序比较易懂,但是每次都要创建left,right数组,这样就增加了空间复杂度!效率就降低了!
2.快速排序易变种版
我们还是要先找基准点,那么这里的基准点就选第一个元素,上面我们是创建left,right数组来区分,这里可以用两个变量i和j,分别指向序列最左边和最右边,你可以理解i是小于基准点的值,j是大于基准点的值,还是用上面的数组[4, 5, 1, 6, 9, 22, 6]
这里基准点是4,首先,变量j向左移动,当小于基准点4时停下脚步!,然后变量i向右移动,当大于基准点4时候停下脚步!
这里变量i的值是5,变量j的值是1,交换I,j的值!
然后j再向左移动找比基准点4小的值,找到了1停下脚步,接着i向右移动找比基准点4大的值,当碰到了j停下脚步!
最后交换基准点和i或者j的值(它们相等)
第一轮排序就完成了,4左边是比4小的,4右边是比4大的,左边右边进行重复上面的操作!
那么我们代码中怎么实现!
- 明确基线条件和递归条件(如果数组只有一个元素,根本就不用排序,那么当数组的元素大于1时就是递归条件)。
- 选择好基准点为arr[0]。
- 创建两个变量i和j,分别指向序列最左边和最右边。
- 不断的重复2,3步骤。
let arr = [4, 5, 1, 6, 9, 22, 6];
function quickSort(arr, left, right) {
let i = left,
j = right,
middle = arr[left];//取基准点
if (left >= right) {//如果数组为空或者只有一个元素,跳出递归
return;
};
while (i < j) {//当i=j跳出循环
while (arr[j] > middle && i < j) {//从右向左找第一个比基准点小的值
j--;
};
while (arr[i] <= middle && i < j) {//从左向右找第一个大于等于基准点的值,注意这里可以相等,是为了保证基准点的位置不变
i++;
};
if (i < j) {//交换值
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
};
当i和j相等,再和基准值交换值
arr[left] = arr[i];
arr[j] = middle;
quickSort(arr, left, i - 1);//递归基准点左边的数组,并且当left>=i-1跳出递归
quickSort(arr, i + 1, right);//递归基准点右边的数组,并且当i+1>=right跳出递归
};
quickSort(arr, 0, arr.length - 1);//[1, 4, 5, 6, 6, 9, 22]
以上两种方法都可以实现快速排序,只不过是实现的方式不一样而已,快速排序的平均运行时间为O(n log n),最糟情运行时间为 O(n2),不懂的话可以点击算法的时间复杂度(大O表示法)