归并排序(递归版+非递归版)及其时间复杂度证明 Go实现

54 阅读1分钟

归并排序(递归版本)

首先是递归版本的归并排序,不做多的解释了,直接上代码。

// MergeSort [l, r)
func MergeSort(arr []int, l int, r int) {
	if l+1 >= r {
		return
	}
	mid := l + (r-l)>>1
	MergeSort(arr, l, mid)
	MergeSort(arr, mid, r)
	merge(arr, l, r)
}

// merge [l, r)
func merge(arr []int, l int, r int) {
	var help []int
	mid := l + (r-l)>>1
	i, j := l, mid
	for i < mid && j < r {
		if arr[i] < arr[j] {
			help = append(help, arr[i])
			i++
		} else {
			help = append(help, arr[j])
			j++
		}
	}

	for i < mid {
		help = append(help, arr[i])
		i++
	}
	for j < r {
		help = append(help, arr[j])
		j++
	}

	// copy help to arr
	copy(arr[l:r], help)
}

递归版本时间复杂度证明

T(n)=2T(n2)+nT(n) = 2*T(\frac{n}{2}) + n

两边同时除以 nn(这里的 nn 代表数据的样本总量)

T(n)n=T(n2)n2+1\frac{T(n)}{n} = \frac{T(\frac{n}{2})}{\frac{n}{2}} + 1

易得 T(n2)n2=T(n4)n4+1\frac{T(\frac{n}{2})}{\frac{n}{2}} = \frac{T(\frac{n}{4})}{\frac{n}{4}} + 1

......

最后可得 T(2)2=T(1)1+1\frac{T(2)}{2} = \frac{T(1)}{1} + 1

将上述式子累加,得到 T(n)n=T(1)1+O(log(n))\frac{T(n)}{n} = \frac{T(1)}{1} + O(log(n))

T(n)=nO(1)+nO(log(n))T(n) = n * O(1) + n * O(log(n))

T(n)=O(n)+O(nlog(n))T(n) = O(n) + O(nlog(n))

可得归并排序的时间复杂度为 O(nlog(n))O(nlog(n))

归并排序(非递归版本)

这里主要是利用步长 step 实现非递归。

对于非递归版本,我们假设对于如下这个数组,进行到步长为2的情况下,如图所示

IMG2.png

进行局部归并得到

未命名文件-导出.png

再移动指针进行下一次归并

未命名文件-导出 (1).png

接下来步长变为4,继续归并数组,然后步长变为 8, 16 ...,到这里是不是就明白了,跟归并排序的递归版本一模一样。

直到归并的步长能够做到一次归并就把 [0, step)[step, step + step) 排序完成(这里的 step + step 可能会大于数组的长度,所以我们需要稍作判断),我们就完成了整个数组的排序。

// MergeSortStep [l, r)
// Non-recursive method implementation
func MergeSortStep(arr []int, l int, r int) {
	step := 1
	for step < r-l {
		left, right := 0, step

		// merge [left, left+step) and [right, right+step)
		for left < r {
			var help []int
			i, j := left, right
			for i < left+step && j < right+step && j < r {
				if arr[i] < arr[j] {
					help = append(help, arr[i])
					i++
				} else {
					help = append(help, arr[j])
					j++
				}
			}
			for i < left+step && i < r {
				help = append(help, arr[i])
				i++
			}
			for j < right+step && j < r {
				help = append(help, arr[j])
				j++
			}

			if i == r {
				copy(arr[left:i], help)
			} else {
				copy(arr[left:j], help)
			}
			left, right = left+step*2, right+step*2
		}

		// Prevent the value from exceeding the maximum value of int
		if step > (r-l)>>1 {
			break
		}
		step <<= 1
	}
}

非递归版本时间复杂度证明

由于步长从1开始变化,每次乘以2,退出条件是 step < n ,所以我们很容易得到外层循环执行了 log(n)log(n) 次,内层循环每次都要完整的遍历一次数组,故总的时间复杂度为 O(nlog(n))O(nlog(n))