归并排序(JS/JavaScript实现)
算法原理
时间复杂度与空间复杂度
一趟归并需要将SR[0]-SR[length-1]中相邻两个h个位置的数进行两两归并,并将结果放到TR中,这需要将排序序列中的所有记录扫描一遍,因此耗费O(n)时间,而由完全二叉树的深度可知,整个归并排序需要进行[log2n]次,因此,总的时间复杂度为O(nlongn),而且这是归并排序算法中最好、最坏、平均的时间性能。
由于归并排序再归并过程中需要与原始记录同样数量的存储空间存放归并结果以及递归时深度为log2n的栈空间,因此空间复杂度为O(n+longn)
代码逻辑
了解了原理之后,要懂得怎么用代码去实现。首先我这里用到了三个数组,一个数组是传入的原始数组,用SR表示。其次是TR2,TR2是Msort里申请的一个临时数组,会在当数组长度为1的时候被依次赋值。在Msort里,TR1更像是一个形参,用来区分TR2。在Merge里,交换位置的时候被当成一个新数组来使用,最后TR1是归并后的有序数组。
代码1
var sortArray = function(nums) {
Msort(nums, nums, 0, nums.length-1)
return nums
}
var Msort = function(sr, tr1, s, t){
let m
let tr2 = []
if(s === t){
tr1[s] = sr[s]
}
else{
/*
将SR[s..t]平分为SR[s..m]和SR[m+1..t]
*/
m = Math.floor((s + t) / 2)
/*
递归将SR[s..m]归并为有序的TR2[s..m]
*/
Msort(sr, tr2, s, m)
/*
递归将SR[m+1..t]归并为有序的TR2[m+1..t]
*/
Msort(sr, tr2, m+1, t)
/*
将TR2[s..m]和TR2[m+1..t],
归并到TR1[s..t]
*/
Merge(tr2, tr1, s, m, t)
}
}
var Merge = function(sr, tr, i, m, n){
let j, k, l
/*
找到两个数组的最小值,当有一个数组循环完毕则跳出循环,
k为新数组的索引值
*/
for(j = m + 1, k = i; i <= m && j <= n; k++){
/*
比较哪个数组的值更小,然后放进新的数组里
*/
if(sr[i] < sr[j]){
tr[k] = sr[i++]
}else{
tr[k] = sr[j++]
}
}
/*
通过if循环比较出哪个数组的值没有通过以上循环放进新数组
*/
if(i <= m){
/* 将没放进新数组的值放进新数组 */
for(l = 0; l <= m-i; l++){
tr[k+l] = sr[i+l]
}
}
if(j <= n){
/* 将没放进新数组的值放进新数组 */
for(l = 0; l <= n-j; l++){
tr[k+l] = sr[j+l]
}
}
}
写法2
以上那种写法是我通过阅读大话数据结构这本书写出来的,然后我又通过别人的博客了解到了接下来的这种写法。在数组的利用上,只用到了两个数组,因为这种写法直接将原数组按索引分为多个数组,没有将值有重新赋予拎一个数组。其次,在分好组后的排序中,通过一个while循环和一个if循环就完成了对两个数组的比较与赋值给新数组,使得代码得到简化,在LeetCode运行时间也减少一倍。大家可以学习一下
动图展示
代码2
var sortArray = function(nums) {
/*
申请一个临时数组,用来存放排序过后的新数组
*/
let temp = []
Merge(nums, temp, 0, nums.length)
return nums
};
var Merge = function(nums, temp, left, right){
/*
当左索引等于右索引,则说明当前数组已经分的只剩一个元素,结束递归
*/
if(left == right) return
/*
取得中间的坐标
*/
let m = Math.floor((left + right) / 2)
/*
对中间分开的两个数组分别进行再次分组
*/
Merge(nums, temp, left, m)
Merge(nums, temp, m+1, right)
/*
每次分完组后,进行排序,取得两个数组的第一个索引值
*/
let i = left, j = m+1, k = 0
/*
两个数组都遍历完才跳出循环
*/
while(i <= m || j <= right){
/*
首先看后半部分,i没有遍历完并且I索引的值小于j索引的值则将i赋值给k然后两者+1
如果i索引的值大于j索引的值,或者i已经遍历完了,则将j赋值给k然后两者+1
再看前面部分,i遍历完了就会遍历j,则j遍历完了也要遍历i,所以j>right时就代表
j遍历完了,直接对i进行遍历
*/
if( j > right || (i <= m && nums[i] < nums[j])){
temp[k++] = nums[i++]
}else{
temp[k++] = nums[j++]
}
}
/*
最后将排好序的临时数组的值对应赋值到原数组
*/
for(let l = 0; l < k; l++){
nums[left + l] = temp[l]
}
}