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

本文详细阐述了分治策略在合并排序算法中的应用,包括分解、解决和合并三个关键步骤。通过分解数组为两半并递归排序,最终使用辅助过程MERGE进行有序合并,展示了算法的时间复杂度为O(nlogn)。本文还提供了合并排序的代码实现,从伪代码到实际C语言代码,全面解析了算法的每一个细节。

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

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

分治法

分治策略:将原问题划分成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的幂时,整个过程的操作:

图1 算法的处理过程

合并排序算法分析

为问题分析方便,这里假设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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值