归并排序
主要思想
参考该篇资料进行理解,通俗易懂。简而言之,归并排序的步骤就是先划分,再合并,所以只需要两个函数。此外,划分数组只做划分操作,合并数组的时候再进行排序。
实现代码
/**
* @param {number[]} nums
* @return {number[]}
*/
var sort= function(nums) {
//合并的部分就是将两个有序数组合并,跟力扣“合并两个有序链表”处理相似
//该函数是包括排序部分的
function heBing(front, end)
{
let temp = [];
while(front.length && end.length)
{
if(front[0] > end[0])
{
temp.push(end.shift());
}
else
{
temp.push(front.shift());
}
}
//数组不等长的时候,某个为空时,另一个的值还没全部压入
return [...temp, ...front, ...end];
}
//只做划分工作
function recur(nums)
{
if(nums.length < 2)
return nums;
//splice参数是整数,所以偶数奇数都可以这么写
let left = nums.splice(0, nums.length / 2);
return heBing(recur(left), recur(nums));
}
return recur(nums);
};
基本分析
空间复杂度:在合并函数中可以看到,因为需要辅助数组temp来存储子数组,所以空间复杂度为O(n)。个人理解这个n是原数组的n个数组元素,所以需要O(n)。
时间复杂度:O(nlogn)
快速排序
主要思想
每次取数组第一个元素作为参照值,先比较right所指元素与参照值的关系,注意判断left与right的大小关系,避免数组越界访问和不合理的排序;再比较left所指元素与参照值的关系,注意的点相同;最后进行分组即递归调用快排函数,注意输入的边界值。
实现代码
/**
* @param {number[]} nums
* @param {number} left
* @param {number} right
* @return {number[]}
*/
function quickSort(nums, left, right)
{
//终止条件
if(left >= right)
return;
let i = left, j = right;
let temp = nums[left]; //要有参照值
while(left < right) //i == j 时,不用再排序
{
//比较右边与参照值nums[j]关系
while(nums[right] > temp && left < right) //个人:left < right是避免数组访问越界
{
right--;
}
if(left >= right)
{
break;
}
//此时:nums[right] <= temp
nums[left] = nums[right];
nums[right] = temp;
//比较左边与参照值nums[j]关系
left++;
while(nums[left] <= temp && left < right)
{
left++;
}
if(left >= right)
{
break;
}
//此时:nums[left] > temp,保证“与参照值相等的元素排在其左边”
nums[right] = nums[left];
nums[left] = temp;
}
//当前情况是,left左边是比nums[left]小的,left右边是比nums[left]大的
//i j 分别是此轮排序最左和最右的边界
quickSort(nums, i, left - 1);
quickSort(nums, left + 1, j);
}
quickSort(nums, 0, nums.length - 1);
return nums;
基本分析
空间复杂度:虽然不需要额外数组空间存储排序元素,但采用递归就不可避免的消耗内存,一般情况是O(logn)。
时间复杂度:一般情况是O(nlogn),实际排序速度快于O(nlogn)。而最坏情况下与冒泡排序一样需要o(n^2)。