码迷,mamicode.com
首页 > 编程语言 > 详细

归并排序

时间:2018-11-23 11:31:36      阅读:216      评论:0      收藏:0      [点我收藏+]

标签:style   思想   time   rgb   str   padding   nts   计算机   mst   

概述

1945年,约翰·冯·诺依曼(John von Neumann)发明了归并排序,这是典型的分治算法的应用。在计算机科学中,归并排序是一种高效、通用、基于比较的排序算法。此外,归并排序还是稳定的,因为相同元素的相对次序在排序后不会发生变化。最开始,归并排序采用的是自顶向下的模式,后来,到了1948年,冯大神和赫尔曼·海因·戈德斯坦(Herman Heine Goldstine)两人共同撰写了一篇报告,描述了归并排序的另外一种实现方式,那就是自底向上。

接下来,本文将带你逐一讨论归并排序的这两种实现方式。

技术分享图片
John von Neumann.gif

技术分享图片
Herman Heine Goldstine.jpg

膜拜一下两位大佬(〃‘▽‘〃)。

归并排序的思想很简单:

  1. 将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。
  2. 将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。
技术分享图片
Merge_sort_animation2.gif

这里面就用到了非常重要的分治思想,把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,最后将子问题的解合并,就得到了原问题的解。

自顶向下

以数组A[8] = {6, 3, 2, 7, 1, 5, 4, 8}为例,归并排序自顶向下的排序过程如下图所示:

技术分享图片
归并排序自顶向下图示.png

在计算机程序中,这一过程可以用递归来实现。以下是C++代码实现:

 1/*自顶向下,递归
2  Array:待排序的数组首地址
3  low:待排序的范围的下界
4  high:待排序的范围的上界的后一个位置
5  比如你要对数组Array[0]~Array[5]进行排序,那么low=0,high=6*/

6void mergeSort_Recursive(int* Array, int low, int high) {
7    if (low + 1 < high) { //当子数组的长度大于1时,不断对数组进行分解
8        int mid = low + (high - low + 1) / 2//将数组分解成Array[low, mid)和A[mid, high),圆括号表示开区间,即数组中不包含此元素
9        mergeSort_Recursive(Array, low, mid);
10        mergeSort_Recursive(Array, mid, high);
11        merge(Array, low, mid, high); /*合并Array[low, mid)和A[mid, high),该函数在文末的完整代码中给出*/
12    }
13}

自底向上

自底向上的思想就是,进行分解,直接从线性表中的单个元素开始,进行两两合并,然后再以每两个元素为单位,进行两两合并……直到最后只剩下一个线性表。还是以刚才那个数组为例,自底向上的归并排序图示如下:

技术分享图片
归并排序自底向上.png

很明显,这种方式跳过了分解的步骤,操作步骤少了很多,而且在用代码实现时,采用的是迭代而不是递归的方式,空间复杂度也少了很多。

下面是C++代码实现:

 1/*自底向上,迭代
2  Array:待排序的数组首地址
3  low:待排序的范围的下界
4  high:待排序的范围的上界的后一个位置
5  比如你要对数组Array[0]~Array[5]进行排序,那么low=0,high=6*/

6void mergeSort(int* Array, int low, int high)
7
{
8    int step = 1;
9    while (step < high) {
10        for (int i = low; i < high; i += step << 1) {
11            int lo = i, hi = (i + (step << 1)) <= high ? (i + (step << 1)) : high; //定义二路归并的上界与下界 
12            int mid = i + step <= high ? (i + step) : high;
13            merge(Array, lo, mid, hi);
14        }
15
16        //将i和i+step这两个有序序列进行合并
17        //序列长度为step
18        //当i以后的长度小于或者等于step时,退出
19        step <<= 1;//在按某一步长归并序列之后,步长加倍
20    }
21}

综合评价

在最坏情况下,以上两种方式的时间复杂度均为技术分享图片。采用自顶向下的归并排序要用到递归,这种方法的好处就是代码容易理解,能比较直观地描述算法思想。但是,也正是由于递归,会使得在排序过程中占用大量计算机内部的栈空间,如果线性表长度过长,那么会可能会造成栈溢出,从而使程序崩溃,而自底向上的迭代就不会出现这种问题。所以,综合来看,在实际应用中,建议大家采用自底上的归并排序——也就是迭代。

递归对性能的消耗有时候甚至能达到指数级别,事实上,任何能用递归解决的问题,也都能用迭代解决。所以,不仅是归并排序,当你遇到其他问题时,如果能用迭代解决,那么不妨想想能不能用迭代来替换。

以下是完整代码:

 1#include <iostream>
2
3using namespace std;
4
5/*合并Array[low, mid)和Array[mid, high)
6  合并前应保证Array[low, mid)和Array[mid, high)中的元素都是有序的*/

7void merge(int* Array, int low, int mid, int high){
8    int* A = Array + low; //合并后的向量A[0, high - low) = _elem[low, high)
9    int lb = mid - low;
10    int* B = new int[lb]; //前子向量B[0, lb) = _elem[low, mid)
11    for (int i = 0; i < lb; B[i] = A[i++]); //复制前子向量B
12    int lc = high - mid;
13    int* C = Array + mid; //后子向量C[0, lc) = _elem[mid, high)
14
15    /*i, j, k分别指向A, B, C中的元素*/
16    for (int i = 0, j = 0, k = 0; (j < lb) || (k < lc);) { //B[j]和C[k]中小者转至A的末尾
17        if (j < lb && k < lc) //如果j和k都没有越界,那么就选择B[j]和C[k]中的较小者放入A[i]
18            A[i++] = B[j] < C[k] ? B[j++] : C[k++];
19        if (j < lb && lc <= k) //如果j没有越界而k越界了,那么就将B[j]放入A[i]
20            A[i++] = B[j++];
21        if (lb <= j && k < lc) //如果k没有越界而j越界了,那么就将C[k]放入A[i]
22            A[i++] = C[k++];
23    }
24
25    delete[] B; //释放临时空间B
26}
27
28/*自底向上,迭代
29  Array:待排序的数组首地址
30  low:待排序的范围的下界
31  high:待排序的范围的上界的后一个位置
32  比如你要对数组Array[0]~Array[5]进行排序,那么low=0,high=6*/

33void mergeSort(int* Array, int low, int high)
34
{
35    int step = 1;
36    while (step < high) {
37        for (int i = low; i < high; i += step << 1) {
38            int lo = i, hi = (i + (step << 1)) <= high ? (i + (step << 1)) : high; //定义二路归并的上界与下界 
39            int mid = i + step <= high ? (i + step) : high;
40            merge(Array, lo, mid, hi);
41        }
42
43        //将i和i+step这两个有序序列进行合并
44        //序列长度为step
45        //当i以后的长度小于或者等于step时,退出
46        step <<= 1;//在按某一步长归并序列之后,步长加倍
47    }
48}
49
50/*自顶向下,递归
51  Array:待排序的数组首地址
52  low:待排序的范围的下界
53  high:待排序的范围的上界的后一个位置
54  比如你要对数组Array[0]~Array[5]进行排序,那么low=0,high=6*/

55void mergeSort_Recursive(int* Array, int low, int high) {
56    if (low + 1 < high) { //当子数组的长度大于1时,不断对数组进行分解
57        int mid = low + (high - low + 1) / 2//将数组分解成Array[low, mid)和A[mid, high),圆括号表示开区间,即数组中不包含此元素
58        mergeSort_Recursive(Array, low, mid);
59        mergeSort_Recursive(Array, mid, high);
60        merge(Array, low, mid, high); //合并Array[low, mid)和A[mid, high)
61    }
62}
63
64int main() {
65    int A[8] = { 63271584 };
66
67    mergeSort(A, 08);
68    //mergeSort_Recursive(A, 0, 8);
69    for (int i = 0; i < 8; i++) {
70        cout << A[i] << " ";
71    }
72    system("pause");
73    return 0;
74}

最后,欢迎大家关注我的微信公众号:AProgrammer

技术分享图片
微信公众号二维码.jpg

注:文中部分图片来自网络

归并排序

标签:style   思想   time   rgb   str   padding   nts   计算机   mst   

原文地址:https://www.cnblogs.com/AProgrammer/p/10005876.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!