原地归并排序

2024-02-18 01:58
文章标签 归并 排序 原地

本文主要是介绍原地归并排序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转自:http://www.cppblog.com/converse/archive/2008/09/28/63008.html

原地归并算法

归并排序算法(mergesort)是将一个序列划分为同样大小的两个子序列,然后对两个子序列分别进行排序,最后进行合并操作,将两个子序列合成有序的序列.在合成的过程中,一般的实现都需要开辟一块与原序列大小相同的空间,以进行合并操作,归并排序算法的示例在 这里.

这里介绍一种不需要开辟新的空间就可以进行归并操作的算法.算法的核心部分是以下代码:
 1  /* *
 2  * 算法: 合并二已排序的连续序列
 3  * */
 4 template<typename T>
 5  void t_merge( T& v, size_t size, size_t pos )
 6 {
 7     size_t fir = 0, sec = pos;
 8      while ( fir < sec && sec < size )
 9     {
10          while ( fir < sec && v[fir] <= v[sec] ) fir++;
11         size_t maxMove = 0;
12          while ( sec < size && v[fir] > v[sec] ) maxMove++, sec++;
13         t_exchange( &v[fir], sec - fir, sec - fir - maxMove );
14         fir += maxMove;       
15     }
16 }

其中T是一个数组, size是数组尺寸, pos是归并划分的位置.也就是说[0,pos)和[pos, size)都分别是有序的.比如对序列1, 3, 5, 7, 2, 4, 6, 8进行归并操作, 此时size=8, pos = 4.

以<<算法导论>>中介绍的通过循环不变量的方法证明算法的正确性,在这个算法中, 循环不变量为[fir, sec)中的元素都是有序的:

1) 初始:此时fir = 0, sec = pos, 以前面介绍的函数参数的说明来看,满足循环不变量.

2) 迭代:来看看循环做了些什么操作.行10进行的操作为, 只要满足fir元素不大于sec元素, fir就一直递增;行12进行的操作是只要满足fir大于sec, sec就一直递增, 同时递增maxMove计数.因此, 进行完前面两个步骤之后, fir所指元素一定小于sec以及其后的所有元素.而位于sec之前的第二个子序列中的元素, 一定小于fir.因此, [sec-maxMove, sec)z中的元素小于所有[fir, sec - 1)的元素.通过调用t_exchange函数将位于[sec-maxMove, sec)中的元素"旋转"到fir之前.

也就是说, 这个过程在第二个已经排序好的子序列中寻找在它之内的小于目前第一个已经排序好的子序列的序列, 将它"旋转"到前面.

以序列 1, 3, 5, 7, 2, 4, 6, 8为例, 此时fir=1也就是指向3, sec=5也就是指向4, maxMove=1, 通过调用t_exchange函数之后将[sec-maxMove, sec)即[4,5)中的元素也就是2"旋转"到子序列3,5,7之前,于是该循环结束之后序列变为1,2,3,5,7,4,6,8, 此时fir=2, sec =5, 满足循环不变量.

3) 终止: 当循环终止时, fir或者sec之一超过了数组的尺寸, 显而易见, 此时序列变成了有序的.

完整的算法如下所示, 需要特别说明的是, 这段代码不是我想出来的, 原作者在 这里:
#include <stdio.h>
#include <iostream>

using  namespace std;

// int array[] = {1, 3, 5, 7, 2, 4, 6, 8};
int array[] = {3,5,7,8,1,2,4,6};
void display( int array[],  int n)
{
      for ( int i = 0; i < n; ++i)
     {
         printf("%d ", array[i]);
     }
     printf("\n");
}

/* *
* 算法: 交换二对象
*
*/
template<typename T>
void t_swap( T& v1, T& v2 )
{
    T t = v1; v1 = v2; v2 = t;
}

/* *
* 算法: 反转序列
*
*/
template<typename T>
void t_reverse( T* v, size_t size )
{
    size_t s = 0, e = size-1;
     while( s < e && s < size && e > 0 )
        t_swap( v[s++], v[e--] );
}

/* *
* 算法: 手摇算法,从指定位置旋转序列(见编程珠玑第二章)
*
*/
template<typename T>
void t_exchange( T* v, size_t size, size_t n )
{
    t_reverse( v, n );
    t_reverse( v + n, size - n );
    t_reverse( v, size );
}

/* *
* 算法: 合并二已排序的连续序列
*
*/
template<typename T>
void t_merge( T& v, size_t size, size_t pos )
{
    size_t fir = 0, sec = pos;
     while ( fir < sec && sec < size )
    {
         while ( fir < sec && v[fir] <= v[sec] ) fir++;
        size_t maxMove = 0;
         while ( sec < size && v[fir] > v[sec] ) maxMove++, sec++;
        t_exchange( &v[fir], sec - fir, sec - fir - maxMove );
        fir += maxMove;

        display(array,  sizeof(array)/ sizeof( int));
    }
}

/* *
* 算法: 归并排序
*
*/
template<typename T>
void t_merge_sort( T* v, size_t size )
{
     if ( size <= 1 )  return;
    t_merge_sort( v, size/2 );
    t_merge_sort( v + size/2, size - size/2 );
    t_merge( v, size, size/2 );
}

int main()
{
     display(array,  sizeof(array)/ sizeof( int));

     t_merge(array,  sizeof(array) /  sizeof( int), ( sizeof(array)/ sizeof( int))/2);
      // t_merge_sort(array, sizeof(array) / sizeof(int));

     display(array,  sizeof(array)/ sizeof( int));
      return 0;
}


补充说明:
其实前面采用"旋转"算法将元素前移不是必须的, 可以将所要移动的元素之前的部分后移, 再将元素插入到合适的位置.比如前面说的对于序列1, 3, 5, 7, 2, 4, 6, 8, 第一步要将元素2前移至3之前, 可以将3,5,7后移, 然后将2插入到合适的位置.
但是这样有一个问题, 如果要移动的元素多了, 那么就需要更多的临时空间保存要前移的元素, 这样对空间就不是O(1)的了.而旋转算法可以做到O(1)的空间达到要求.

这篇关于原地归并排序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/719683

相关文章

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

hdu 1285(拓扑排序)

题意: 给各个队间的胜负关系,让排名次,名词相同按从小到大排。 解析: 拓扑排序是应用于有向无回路图(Direct Acyclic Graph,简称DAG)上的一种排序方式,对一个有向无回路图进行拓扑排序后,所有的顶点形成一个序列,对所有边(u,v),满足u 在v 的前面。该序列说明了顶点表示的事件或状态发生的整体顺序。比较经典的是在工程活动上,某些工程完成后,另一些工程才能继续,此时

《数据结构(C语言版)第二版》第八章-排序(8.3-交换排序、8.4-选择排序)

8.3 交换排序 8.3.1 冒泡排序 【算法特点】 (1) 稳定排序。 (2) 可用于链式存储结构。 (3) 移动记录次数较多,算法平均时间性能比直接插入排序差。当初始记录无序,n较大时, 此算法不宜采用。 #include <stdio.h>#include <stdlib.h>#define MAXSIZE 26typedef int KeyType;typedef char In

【软考】希尔排序算法分析

目录 1. c代码2. 运行截图3. 运行解析 1. c代码 #include <stdio.h>#include <stdlib.h> void shellSort(int data[], int n){// 划分的数组,例如8个数则为[4, 2, 1]int *delta;int k;// i控制delta的轮次int i;// 临时变量,换值int temp;in

学习记录:js算法(二十八):删除排序链表中的重复元素、删除排序链表中的重复元素II

文章目录 删除排序链表中的重复元素我的思路解法一:循环解法二:递归 网上思路 删除排序链表中的重复元素 II我的思路网上思路 总结 删除排序链表中的重复元素 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 图一 图二 示例 1:(图一)输入:head = [1,1,2]输出:[1,2]示例 2:(图

鸡尾酒排序算法

目录 引言 一、概念 二、算法思想 三、图例解释 1.采用冒泡排序:   2.采用鸡尾酒排序:  3.对比总结 四、算法实现  1.代码实现  2.运行结果 3.代码解释   五、总结 引言 鸡尾酒排序(Cocktail Sort),也被称为双向冒泡排序,是一种改进的冒泡排序算法。它在冒泡排序的基础上进行了优化,通过双向遍历来减少排序时间。今天我们将学习如何在C

快速排序(java代码实现)

简介: 1.采用“分治”的思想,对于一组数据,选择一个基准元素,这里选择中间元素mid 2.通过第一轮扫描,比mid小的元素都在mid左边,比mid大的元素都在mid右边 3.然后使用递归排序这两部分,直到序列中所有数据均有序为止。 public class csdnTest {public static void main(String[] args){int[] arr = {3,

O(n)时间内对[0..n^-1]之间的n个数排序

题目 如何在O(n)时间内,对0到n^2-1之间的n个整数进行排序 思路 把整数转换为n进制再排序,每个数有两位,每位的取值范围是[0..n-1],再进行基数排序 代码 #include <iostream>#include <cmath>using namespace std;int n, radix, length_A, digit = 2;void Print(int *A,