算法学习与代码实现3——合并排序
算法思路
合并排序使用的是分治法,分治法在《算法导论》上的介绍分为三个步骤:
- 分解(Divide):将原问题分解成一些列子问题;
- 解决(Conquer):递归地解各个子问题。若问题足够小,则直接求解。
- 合并(Combine):将子问题的结果合并成原问题的解。
同时,书上也介绍了合并排序(merge sort)相对应的三个步骤:
- 分解:将n个元素分成各含n/2个元素的子序列;
- 解决:用合并排序法对两个子序列递归地;
- 合并:合并两个已排序的子序列以得到排序结果。
其实可以说的通俗点,合并排序分为两个部分,核心部分就是合并,另一部分是合并排序。合并部分就是假设你手中有两摞已经排好序的扑克牌,牌面朝上,上面小下面大。然后每次比较两摞牌最上面的一张,取较小的一张面朝下放到新的一摞牌中。这样,在新的一摞牌中就实现了将两摞排好序的牌合成一摞。
另一部分是合并排序。作为递归的算法,合并排序必须对自己有信心——有信心能够完成排序,因为他要调用自己,对自己没有信心怎么调用自己呀。当然它的信心也是有依据的,它在内部实现中会将待排序的数分解成两份,对每份再次调用合并排序。当分解到足够小,也就是每份中只有一个数时,它自然就是排好序的了。这样,合并排序在内部就能够实现将待排序的数编程两份排好序的数,用刚才说到的合并处理一下,就编程一份排好序的数了。
算法性能
稳定性: 稳定排序
时间复杂度: O(nlogn)
空间复杂度: O(n)
伪代码
合并部分。MERGE过程的时间代价是O(n),在下面伪代码中,A是数组,p、q、r是下标,满足p≤q<r。该过程假设A[p..q]和A[q+1..r]都已排好序,并将它们合并成一个已排好序的子数组代替当前子数组A[p..r]。
MERGE(A, p, q, r)
n1 <- q - p + 1
n2 <- r - q
create arrays L[1..n1+1] and R[1..n2+1]
for i <- 1 to n1
do L[i] <- A[p+i-1]
for j <- 1 to n2
do R[j] <- A[q+j]
L[n1+1] <- ∞
R[n2+2] <- ∞
i <- 1
j <- 1
for k <- p to r
do if L[i] ≤ R[j]
then A[k] <- L[i]
i <- i + 1
else A[k] <- R[j]
then A[k] <- R[j]
j <- j + 1
下面是合并排序的伪代码,它对子数组A[p..r]进行排序
MERGE_SORT(A, p, r)
if p < r
then q <- (p+r)/2向下取整
MERGE-SORT(A, p, q) ▷ 相信自己
MERGE-SORT(A, q+1, r)
MERGE(A, p, q, r)
C语言实现
merge函数实现如下:
void merge(int * array, int head, int middle, int tail){
int n1 = middle - head + 1;
int n2 = tail - middle;
int * L = malloc(n1 * sizeof(int));
int * R = malloc(n2 * sizeof(int));
int i = 0, j = 0;
for ( i = 0; i < n1; ++i) {
L[i] = array[head + i];
}
for ( j = 0; j < n2; ++j) {
R[j] = array[middle + 1 + j];
}
i = 0;
j = 0;
for ( int k = head; k < tail + 1; k++) {
if ( i == n1 ) {
array[k] = R[j++];
}
else if ( j == n2 ) {
array[k] = L[i++];
}
else if ( L[i] <= R[j]) {
array[k] = L[i++];
}
else if ( L[i] > R[j] ) {
array[k] = R[j++];
}
}
free(L);
free(R);
}
有种说法叫过早优化是万恶之源,所以这里我没有对内存申请部分做优化,其实实现时可以做一点策略,当n1和n2小于一定值时使用固定分配内存,这样可以减少malloc过程的时间消耗。
merge_sort实现如下:
void merge_sort(int *array, int head, int tail){
if ( head < tail ) {
int middle = (head + tail) / 2;
merge_sort(array, head, middle);
merge_sort(array, middle + 1, tail);
merge(array, head, middle, tail);
}
}