难理解且有意思的归并排序(go语言版本)

114 阅读3分钟

归并排序的核心原理是将一个数组拆成前后两个部分,然后对这前后两个部分分别进行排序,最后将这两个有序数组在合并成一个有序数组的过程。

假如我们有一个长度为1024的数组,我们可以将它分成前512跟后512部分,然后再讲这个512的数组分成前256跟后256部分,依次类推128,64,32,16,8,4,2,1,直到最后问题会转换成将两个长度为1的数组按照顺序排列起来,排完之后我们得到了两个长度为2的有序数组,再讲这两个长度为2的有序数组按照顺序排列起来得到一个长度为4的数组,然后依次是8,16,32,64,128,256,512,1024。

这个模式应该比较熟悉,跟二分查找比较像,所以时间复杂度也是log级别的,区别与二分查找,这里多了一个对两个有序数组排序的过程,所以时间复杂度会比二分查高一些,是nlogn。

归并排序的思想是一个典型的递归思想,大问题可以分解成小问题,小问题跟大问题的处理方法一样,并且分解存在终止条件(数组长度为1)

我们可以用go写出递归的方法。

func merge_sort_c(nums []int, p, r int) []int {
    //递归的终止条件,当数组只有一个元素的时候,直接返回该元素
    if p == r-1 {
        return []int{nums[p]}
    }
    //将大数组分解长两个数组
    q := (p + r) / 2
    //分别对左右两个数组进行处理
    merge_sort_c(nums, p, q)
    merge_sort_c(nums, q, r)
    //将左右两个数组合并成一个数组
    merge(nums[p:r], nums[p:q], nums[q:r])
    return nums
}

可以看到使用递归的框架写完代码后,整个归并排序的问题就转换成了一个对于两个有序数组组合成一个有序数组的问题了。

这里我们可以使用双指针,初始状态两个指针分别指向了两个数组的第一个元素,然后比较这两个元素,将较小的元素append到目标数组中,然后指针往前走一步。

直到两个指针中的任何一个走到了末尾,再讲另外一个数组的指针到末尾的元素append的目标数组中,这样就完成了两个有序数组的排序了。

用代码将上述过程实现出来。

func merge(nums []int, l []int, r []int) []int {
	llen := len(l)
	rlen := len(r)
	a, b := 0, 0
	sorted := make([]int, 0, llen+rlen)
	for {
        //指针走到末尾后将另一个数组的剩余元素append到目标数组中,break完成排序
		if a == llen {
			sorted = append(sorted, r[b:]...)
			break
		}
		if b == rlen {
			sorted = append(sorted, l[a:]...)
			break
		}
        //比较当前两个指针指向的元素,谁小将谁append到目标数组。
		if l[a] <= r[b] {
			sorted = append(sorted, l[a])
			a++
		} else {
			sorted = append(sorted, r[b])
			b++
		}
	}
	copy(nums, sorted)
	return sorted
}

最后可以在leecode的 这道题 去运行测试用例,验证其正确性。