归并排序算是一个比较简单的排序了,它没有涉及到一些复杂的数据结构。
什么是归并
归并就是把两个有序的数组合并成一个有序的新数组,并且这两个数组和生成的新数组必须同时为升序或降序
let arr1 = [1,3,6,8]
let arr2 = [3,5,10]
合并的规程:
- 两个数组的首位进行比较,小的哪一个就从对应的数组里shift(),然后把这个值push到新数组的末尾
- 重复上面的步骤,直到有一个数组为空了,那么就把另一个数组的剩余部分全部push到新数组里,结束。
代码实现:
function merge(arr1, arr2) {
let result = []
while (true) {
//检查两个数组那个空了
if (arr1.length == 0) {
result.push(...arr2)
break
}
if (arr2.length == 0) {
result.push(...arr1)
break
}
// 两个数组首位进行比较,小的被取出,并添加到新数组的末尾
if (arr1[0]<arr2[0]) {
result.push(arr1.shift())
}else{
result.push(arr2.shift())
}
}
return result
}
什么是归并排序
我们可以把一个数组里的每两个元素看作是两个数组,然后两两合并
let arr = [4,3,6,7,8,9,9,0]
let arr = [[4],[3],[6],[7],[8],[9],[9],[0]]
把arr数组两个两个传入merge函数 ,得到一个新数组
let newArr1 = [[3,4],[6,7],[8,9],[0,9]]
把newArr1数组两个两个传入merge函数 ,得到一个新数组
let newArr2 = [[3,4,6,7],[0,8,9,9]]
把newArr2数组两个两个传入merge函数 ,得到一个新数组
let newArr2 = [[0,3,4,6,7,8,9,9]]
然后取出newArr2[0]就是我们要的排序后的数组了。
来看看代码的实现:
// 从下向上归并,非递归实现
// 先实现一个合并两个有序数组的merge函数
function merge(arr1, arr2) {
let newArr = []
// 因为两个参数传进来必须是数组,但是第一轮合并传进来的肯定是一个number
//所以要用一个Array.of()来把一个number包装成[number]
if (!Array.isArray(arr1)) {
arr1 = Array.of(arr1)
}
if (!Array.isArray(arr2)) {
arr2 = Array.of(arr2)
}
while (true) {
if (arr1.length<=0) {
newArr.push(...arr2)
return newArr
}
if (arr2.length<=0) {
newArr.push(...arr1)
return newArr
}
let temp = arr1[0]<arr2[0] ? arr1.shift() : arr2.shift()
newArr.push(temp)
}
}
function mergeSort(arr) {
while (arr.length!=1) {
//取出前两个数组
let t1 = arr.shift()
let t2 = arr.shift()
//进行归并,并把生成的数组压入arr
arr.push(merge(t1,t2))
}
//因为我这种方法最后输出的是一个[[]]形式的长度为1的数组,所以可以进行打平数组
return arr.flat()
}
这里需要读者理解一下mergeSort函数里的arr.push(merge(t1,t2))
当然我这种写法借助了很多JS的特性,所以看上去会简单一点
到此我们就写完了归并排序了,这种写法是从下往上进行归并,也就是从一个个元素开始,慢慢合并到一个大数组。接下来说一下递归实现。
我们排序都是对一个无序数组进行排序,而归并是对两个有序数组的操作,归并有一定的排序的作用。
我们要创造归并的条件,归并要求两个数组,那我们就一开始把我们的数组分成两个两组传入merge()函数,归并要求两个数组有序,那我们就把这两个数组变成有序,怎么变呢?我们可以用归并排序让它有序,这里就不难看出这是一个递归的过程了。
先对上面的merge函数进行一下封装,套了个壳,主要是为了减少用户传递参数的数量,自动把arr这个数组变成两个数组,传到merge函数里,等待merge函数的返回值。
function merge(arr1, arr2) {
let result = []
while (true) {
if (arr1.length == 0) {
result.push(...arr2)
break
}
if (arr2.length == 0) {
result.push(...arr1)
break
}
if (arr1[0]<arr2[0]) {
result.push(arr1.shift())
}else{
result.push(arr2.shift())
}
}
return result
}
function mergeSort(arr) {
if (arr.length == 1) {
return arr
}
return merge(arr.slice(0,Math.floor(arr.length/2)),arr.slice(Math.floor(arr.length/2)))
}
那么要在哪里开始递归呢?可以看出前面的merge函数要依赖后面merge函数的结果,那么嵌套在merge函数里的merge函数要放在merge函数内部靠前面的位置
function merge(arr1, arr2) {
arr1 = merge(arr1.slice(0,Math.floor(arr1.length/2)),arr1.slice(Math.floor(arr1.length/2)))
arr2 = merge(arr2.slice(0,Math.floor(arr2.length/2)),arr2.slice(Math.floor(arr2.length/2)))
let result = []
while (true) {
if (arr1.length == 0) {
result.push(...arr2)
break
}
if (arr2.length == 0) {
result.push(...arr1)
break
}
if (arr1[0]<arr2[0]) {
result.push(arr1.shift())
}else{
result.push(arr2.shift())
}
}
return result
}
function mergeSort(arr) {
if (arr.length == 1) {
return arr
}
return merge(arr.slice(0,Math.floor(arr.length/2)),arr.slice(Math.floor(arr.length/2)))
}
但是是不是每次执行merge函数都要递归呢?什么时候不用递归,当传进去的两个参数(两个数组)的长度是1的时候,我们就没有必要对这种数组进行排序了,因为它本来就有序。
然后就有了最后的代码:
function merge(arr1, arr2) {
if (arr1.length != 1) {
arr1 = merge(arr1.slice(0,Math.floor(arr1.length/2)),arr1.slice(Math.floor(arr1.length/2)))
}
if (arr2.length != 1 ) {
arr2 = merge(arr2.slice(0,Math.floor(arr2.length/2)),arr2.slice(Math.floor(arr2.length/2)))
}
let result = []
while (true) {
if (arr1.length == 0) {
result.push(...arr2)
break
}
if (arr2.length == 0) {
result.push(...arr1)
break
}
if (arr1[0]<arr2[0]) {
result.push(arr1.shift())
}else{
result.push(arr2.shift())
}
}
return result
}
function mergeSort(arr) {
if (arr.length == 1) {
return arr
}
return merge(arr.slice(0,Math.floor(arr.length/2)),arr.slice(Math.floor(arr.length/2)))
}