您的位置:首页 > 其它

学习《算法导论》第二章 合并排序 总结

2015-08-29 21:59 302 查看

学习《算法导论》第二章 合并排序 总结

在上一节,学习了插入排序,插入排序使用的算法设计是增量方法. 而这一节我们学习另一种算法设计方法:分治法. 分治算法有一个优点:那就是很容易确定其运行时间.

分治法

分治策略:将原问题划分成n个规模较小而结构与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解.

分治模式一般有三个步骤:

分解:将原问题分解成一系列子问题

解决:递归地解决各子问题

合并:将子问题的结果合并成原问题的解

合并排序

合并排序直观地操作如下:

分解:将n个元素分成n/2个元素的子序列

解决:用合并排序法对两个子序列递归地排序

合并:合并两个已排序的子序列以得到排序结果

单个元素被视为是已排好序的,这样就能退出递归. 合并排序的关键是合并步骤、合并两个已排好序的子序列. 为此,我们引入一个辅助过程MERGE(A, p, q, r),其中A是一个数组,p、q和r是下标,假设子数组A[p..q]和A[q+1..r]都已排好序,并将它们合并成一个已排好序的数组A[p..r].下面先给出合并过程的伪代码:

MERGE(A,p, q, r)
1    n1 <--- q-p+1
2    n2 <--- r-p
3    create arrays L[1..n1+1] and R[1..n2+1]
4    for i <--- 1 to n1
5        do L[i] <--- A[p+i-1]
6    for j <--- 1 to n2
7        do R[j] <--- A[q+1]
8    L[n1+1] <--- ∞  // 哨兵值
9    R[n2+1] <--- ∞  // 哨兵值
10   i <--- 1
11   j <--- 1
12   for k <--- p to r
13       do if L[i] <= R[j]
14           then A[k] <--- L[i]
15                i <--- i+1
16           else A[k] <--- R[j]
17                j <--- j+1


由以上的伪代码可知:MERGE过程的时间代价为Θ(n). 此处n = r – q+1.

下面就可以讲合并排序了,MERGE过程是合并排序的一个子程序. 下面的MERGE-SORT(A, p,r)对子数组A[p..r]进行排序. 伪代码如下:

MERGE-SORT(A, p, r)
1    if p < r
2       then q <--- (p + r) / 2
3            MERGE-SORT(A, p, q)
4            MERGE-SORT(A, q + 1, r)
5            MERGE(A, p, q, r)


下图自底向上地说明了当n为2的幂时,整个过程的操作:



合并排序算法分析

为问题分析方便,这里假设n为2的幂次方. 当n > 1时,将运行时间作如下的分解:

分解:这一步就是计算出子数组的中间位置,常量时间,D(n) = Θ(1).

解决:递归地解两个规模为n/2的子问题,时间为2T(n/2).

合并:MERGE过程上面分析了,运行时间为C(n) = Θ(n).

所以,T(n) = 2T(n/2) +Θ(n). 那这个运行时间是多少呢?这是一个递归式,后面有专门章节介绍这个解法,可以用迭代法也可以用主定理得出:

T(n) = Θ(nlgn).

下面用另一种“递归树”来分析如何解递归式,假设n为2的幂次方.









如上a, b, c, d图,它被逐步地进行了扩展以形成递归树,在最后一图,它完全的扩展了的递归树有lgn+1层,而每一层的总代价为cn,因此总的运行时间代价为cnlgn+cn. 因此运行时间为Θ(nlgn).

合并排序的代码实现

// 合并子问题
void merge (int* num, int begin, int middle, int end)
{
int n1 = middle - begin + 1;
int n2 = end - middle;

int* left = (int*)malloc(n1 + 1);
int* right = (int*)malloc(n2 + 1);

int i = 0;
int j = 0;

// 将子问题1中的元素存入left数组
for (i = 0; i < n1; i++)
{
left[i] = num[begin + i];
}

// 将子问题2中的元素存入right数组
for (i = 0; i < n2; i++)
{
right[i] = num[middle + i + 1];
}

i = 0;
j = 0;
// 分别将哨兵存入left和right数组中,作为判断是否到达数组末尾
left[n1] = MAX_VALUE;
right[n2] = MAX_VALUE;

for (int k = begin; k <= end; k++)
{
if (left[i] < right[j])
{
num[k] = left[i];
i = i + 1;
}
if (left[i] >= right[j])
{
num[k] = right[j];
j = j + 1;
}
}

free(left);
free(right);
return;
}

// 合并排序
void mergesort (int* num, int start, int end)
{
int middle = 0;

if (start < end)
{
middle = (start + end) / 2;
mergesort (num, start, middle);  // 分解问题成子问题
mergesort (num, middle + 1, end); // 分解问题成子问题
merge (num, start, middle, end);  // 合并
}

return;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: