zoukankan      html  css  js  c++  java
  • 【啊哈!算法】之五、归并排序

    归并排序是利用"归并"技术来进行排序。归并是指将若干个已排序的子文件合并成一个有序的文件。

    这是采用分治算法的一个典型的应用!

    这里要讲两种:两路归并排序,归并排序~!

    归并排序是一种稳定的排序算法;

    用顺序存储结构。也易于在链表上实现。


    算法复杂度:

         比较操作的次数介于(n log n)/2n log n - n + 1。 赋值操作的次数是(2nlogn)。最优时间复杂度O(n),最差时间复杂度O(nlogn),平均时间复杂度O(nlogn)。 归并算法的空间复杂度为:Θ (n)


    归并操作的过程如下:

    1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
    2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
    3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
    4. 重复步骤3直到某一指针达到序列尾
    5. 将另一序列剩下的所有元素直接复制到合并序列尾
    (来自维基百科)


    一、两路归并排序

    我们有两个有序(升序)序列存储在同一数组中相邻的位置上,不妨设为A[l..m],A[m+1..h],将它们归并为一个有序数列,并存储在A[l..h]。

    为了减少数据移动次数,不妨采用一个临时工作数组TEMP,将中间排序结果暂时保存在数组中,等归并结束后,再将TEMP数组值复制给A。

    现在我们来看一下过程:

    归并过程中,设置p1,p2和p3三个指针,其初值分别指向三个有序区的起始位置。归并时依次比较A[p1]和A[p2]的关键字,取关键字较小的记录复制到TEMP[p3]中,然后将被复制记录的指针p1或p2加1,以及指向复制位置的指针p3加1。

    重复这一过程直至有一个已复制完毕,此时将另一序列中剩余数据依次复制到TEMP中即可。


    OK,下面给出测试代码:

    #include<iostream>
    using namespace std;
    
    void marge2(int *a, int low, int mi, int N)
    {
    	int i = low, m = mi + 1;
    	int p = 0;
    	int *TEMP = new int((N - low + 1)*sizeof(int));
    	if(!TEMP)
    		return ;
    	//三个指针,a中的比较大小放到temp中
    	for(;i <= mi && m <= N;)
    		TEMP[p++] = (a[i] <= a[m])?a[i++]:a[m++];
    	//前面的剩下的元素放入TEMP中
    	for(;i <= mi;)
    		TEMP[p++] = a[i++];
    	//后面的剩下的元素放入TEMP中
    	while(m <= N)
    		TEMP[p++] = a[m++];
    	//将所有元素放入A中
    	for(p = 0, i = low; i <= N; p++, i++)
    		a[i] = TEMP[p];
    
    }
    
    void puta(int *a, int N)
    {
    	for(int i = 0; i < N; i++)
    		cout << a[i] << endl;
    }
    
    int main(void)
    {
    	int a[] = {12, 15, 16, 17, 3, 4, 14, 36};
    	marge2(a, 0, 3, 8);
    	
    	puta(a, 8);
    	return 0;
    }


    二、归并排序

    自顶向下和自底向上

    1、自顶向下

     他采用分治算法

    设有数组A[low...high]

    步骤:

    1、分解: 和二分一样,取数组的中间点mid=(low+high)/2

    2、求解: 分别对数组A[low..mid]和A[mid+1...high]进行归并排序

    3、组合:将已排序的A[low..mid]和A[mid+1...high]组合成最终的有序组。


    下面看一下图解:


    ok,根据图示应该很清楚了,下面给出代码:

    void mergesort(int *a, int low, int high)
    {
    	int mid;
    	if(low < high)
    	{
    		mid = (low + high) / 2;
    		mergesort(a, low, mid);
    		mergesort(a, mid + 1, high);
    		merge2(a, low, mid, high);
    	}
    }
    
    int main(void)
    {
    	int a[] = {12, 15, 16, 17, 3, 4, 14, 36};
    	//merge2(a, 0, 3, 8);
    	mergesort(a, 0, 7);
    	puta(a, 8);
    	return 0;
    }

    此段代码接上文代码,因为有调用函数!



    2、自底向上

    思想: (我们还是设数组A[low...high])

    第1趟归并排序时,将数列A[1..n]看作是n个长度为1的有序序列,将这些序列两两归并,若n为偶数,则得到[n/2]个长度为2的有序序列;若n为奇数,则最后一个子序列不参与归并。第2趟归并则是将第1趟归并所得到的有序序列两两归并。如此反复,直到最后得到一个长度为n的有序文件为止。


    要注意的是: 调用归并操作将相邻的一对子文件进行归并时,必须对子文件的个数可能是奇数、以及最后一个子文件的长度小于length这两种特殊情况进行特殊处理:

    1、若子文件个数为奇数,则最后一个子文件无须和其它子文件归并(即本趟轮空);

    2、若子文件个数为偶数,则要注意最后一对子文件中后一子文件的区间上界是n。


    下面面来看一下动画演示:归并排序自底向上


    ok下面给出代码:

    void mergepass(int *a, int n, int length)
    {
    	int i;
    	for(i = 0; i + 2*length - 1 <= n; i = i + 2*length)
    		merge2(a, i, i + length - 1, i + 2*length - 1);
    	 if(i + length - 1 < n)
    		 merge2(a, i, i + length - 1, n);
    }
    
    void mergesort2(int *a, int n)
    {
    	for(int i = 1; i < n; i *= 2)
    		mergepass(a, n, i);
    }
    
    int main(void)
    {
    	int a[] = {12, 15, 16, 17, 3, 4, 14, 36};
    	//merge2(a, 0, 3, 8);
    	mergesort2(a, 7);
    	puta(a, 8);
    	return 0;
    }


    2012/8/14

    jofranks 于南昌

  • 相关阅读:
    SGU 176.Flow construction (有上下界的最大流)
    POJ 2391.Ombrophobic Bovines (最大流)
    poj 1087.A Plug for UNIX (最大流)
    poj 1273.PIG (最大流)
    POJ 2112.Optimal Milking (最大流)
    SGU 196.Matrix Multiplication
    SGU 195. New Year Bonus Grant
    关于multicycle path
    ppt做gif动图
    codeforces 598A Tricky Sum
  • 原文地址:https://www.cnblogs.com/java20130723/p/3211416.html
Copyright © 2011-2022 走看看