算法:混合归并排序和插入排序执行时间

2024-02-01

美好的一天,SO社区,

我是一名计算机科学学生,目前正在进行合并排序和插入排序相结合的实验。据了解,对于一定的阈值S,InsertionSort将比MergeSort具有更快的执行时间。因此,通过合并两种排序算法,总运行时间将得到优化。

然而,在使用1000个样本量和不同大小的S进行多次实验后,实验结果并没有每次都给出明确的答案。这是获得的更好结果的图片(请注意,一半的时间结果并不那么明确):

现在,尝试相同的算法代码,样本大小为 3500:

最后,尝试使用相同的算法代码,样本大小为 500,000(请注意,y 轴以毫秒为单位:

尽管从逻辑上讲,当 S

目前,这些是我学到的时间复杂度:

归并排序:O(n log n)

插入排序:

  • 最佳情况:θ(n)
  • 最坏情况:θ(n^2)

最后,我在网上找到了一个资源:https://cs.stackexchange.com/questions/68179/combining-merge-sort-and-insertion-sort https://cs.stackexchange.com/questions/68179/combining-merge-sort-and-insertion-sort其中指出:

混合合并插入排序:

  • 最佳情况:θ(n + n log (n/x))
  • 最坏情况:θ(nx + n log (n/x))

我想问一下CS社区有没有结果显示明确的证据表明,低于某个阈值 S 时,混合归并排序算法比普通归并排序算法效果更好,如果是这样,为什么?

非常感谢社区,这可能是一个微不足道的问题,但它确实会澄清我目前关于时间复杂度等问题的许多问题:)

注意:我使用 Java 进行算法编码,运行时可能会受到 Java 在内存中存储数据的方式的影响。

Java代码:

 public static int mergeSort2(int n, int m, int s, int[] arr){
        int mid = (n+m)/2, right=0, left=0;
        if(m-n<=s)
            return insertSort(arr,n,m);
        else
        {
            right = mergeSort2(n, mid,s, arr);
            left = mergeSort2(mid+1,m,s, arr);
            return right+left+merge(n,m,s,arr);
        }      
    }

    public static int insertSort(int[] arr, int n, int m){
        int temp, comp=0;
        for(int i=n+1; i<= m; i++){
            for(int j=i; j>n; j--){ 
                comp++;
                comparison2++;
                if(arr[j]<arr[j-1]){
                    temp = arr[j];
                    arr[j] = arr[j-1];
                    arr[j-1] = temp;
                }
                else
                    break;
            }
        }
        return comp;
    }

    public static void shiftArr(int start, int m, int[] arr){
        for(int i=m; i>start; i--)
            arr[i] = arr[i-1];     
    }

public static int merge(int n, int m, int s, int[] arr){
        int comp=0;
        if(m-n<=s)
            return 0;
        int mid = (n+m)/2;
        int temp, i=n,  j=mid+1;
        while(i<=mid && j<=m)
        {
            comp++;
            comparison2++;


            if(arr[i] >= arr[j])
            {
                if(i==mid++&&j==m && (arr[i]==arr[j]))
                    break;
                temp = arr[j];
                shiftArr(i,j++,arr);
                arr[i] = temp;
                if(arr[i+1]==arr[i]){
                   i++;
                }
            }
            i++;


        }
        return comp;
    }

示例代码不是传统的合并排序。合并函数会移动数组,而不是合并原始数组和临时工作数组之间的运行并返回。

我测试了自上而下和自下而上的合并排序,两者都需要大约 42 毫秒 == 0.042 秒来对 500,000 个 32 位整数进行排序,而图中的明显结果则慢了 1000 倍,大约需要 42 秒,而不是 42 毫秒。我还测试了 10,000,000 个整数,排序需要 1 秒多一点的时间。

过去,我使用 C++ 将自下而上合并排序与混合自下而上合并/插入排序进行比较,对于 1600 万 (2^24 == 16,777,216) 32 位整数,使用 S 的混合排序大约快 8% == 16. S == 64 比 S == 16 稍慢。 Visual Studio std::stable_sort 是自下而上合并排序(临时数组是原始数组大小的 1/2)和插入排序的变体,并使用 S == 32。

对于小型数组,插入排序比合并排序更快,这是缓存局部性和使用插入排序对小型数组进行排序所需的更少指令的结合。对于伪随机数据和 S == 16 到 64,插入排序的速度大约是合并排序的两倍。

相对增益随着阵列尺寸的增加而减小。考虑到自底向上归并排序的影响,当S == 16时,只优化了4次归并排序。在我的测试用例中,有 2^24 == 16,777,216 个元素,即 4/24 = 1/6 ~= 16.7% 的传递次数,导致大约 8% 的改进(因此插入排序的速度大约是合并的两倍对这 4 个通道进行排序)。仅合并排序的总时间约为 1.52 秒,混合排序的总时间约为 1.40 秒,比仅需要 1.52 秒的过程增加了 0.12 秒。对于自上而下的合并排序,当 S == 16 时,将优化 4 个最深的递归级别。

更新 - 时间复杂度为 O(n log(n)) 的混合就地合并排序/插入排序的 Java 代码示例。 (注意 - 由于递归,辅助存储仍然在堆栈上消耗。)就地部分是在合并步骤期间通过交换合并到的区域中的数据与合并自的区域中的数据来完成的。这不是稳定的排序(由于合并步骤期间的交换,不保留相等元素的顺序)。对 500,000 个整数进行排序大约需要 1/8 秒,因此我将其增加到 1600 万 (2^24 == 16777216) 个整数,这需要 4 秒多一点的时间。不使用插入排序时,排序耗时约 4.524 秒,使用 S == 64 的插入排序时,排序耗时约 4.150 秒,增益约 8.8%。使用基本相同的 C 代码,改进较小:从 2.88 秒缩短到 2.75 秒,大约提高了 4.5%。

package msortih;
import java.util.Random;

public class msortih {

    static final int S = 64;    // use insertion sort if size <= S

    static void swap(int[] a, int i, int j) {
        int tmp = a[i]; a[i] = a[j]; a[j] = tmp;
    }

    // a[w:] = merged a[i:m]+a[j:n]
    // a[i:] = reordered a[w:]
    static void wmerge(int[] a, int i, int m, int j, int n, int w) {
        while (i < m && j < n)
            swap(a, w++, a[i] < a[j] ? i++ : j++);
        while (i < m)
            swap(a, w++, i++);
        while (j < n)
            swap(a, w++, j++);
    }  

    // a[w:]  = sorted    a[b:e]
    // a[b:e] = reordered a[w:]
    static void wsort(int[] a, int b, int e, int w) {
        int m;
        if (e - b > 1) {
            m = b + (e - b) / 2;
            imsort(a, b, m);
            imsort(a, m, e);
            wmerge(a, b, m, m, e, w);
        }
        else
            while (b < e)
                swap(a, b++, w++);
    }

    // inplace merge sort a[b:e]
    static void imsort(int[] a, int b, int e) {
        int m, n, w, x;
        int t;
        // if <= S elements, use insertion sort
        if (e - b <= S){
            for(n = b+1; n < e; n++){
               t = a[n];
               m = n-1;
                while(m >= b && a[m] > t){
                    a[m+1] = a[m];
                    m--;}
                a[m+1] = t;}
            return;
        }
        if (e - b > 1) {
            // split a[b:e]
            m = b + (e - b) / 2;
            w = b + e - m;
            // wsort -> a[w:e] = sorted    a[b:m]
            //          a[b:m] = reordered a[w:e]
            wsort(a, b, m, w);
            while (w - b > 2) {
                // split a[b:w], w = new mid point
                n = w;
                w = b + (n - b + 1) / 2;
                x = b + n - w;
                // wsort -> a[b:x] = sorted    a[w:n]
                //          a[w:n] = reordered a[b:x]
                wsort(a, w, n, b);
                // wmerge -> a[w:e] = merged    a[b:x]+a[n:e]
                //           a[b:x] = reordered a[w:n]
                wmerge(a, b, x, n, e, w);
            }
            // insert a[b:w] into a[b:e] using left shift
            for (n = w; n > b; --n) {
                t = a[n-1];
                for (m = n; m < e && a[m] < t; ++m)
                    a[m-1] = a[m];
                a[m-1] = t;
            }
        }
    }

    public static void main(String[] args) {
        int[] a = new int[16*1024*1024];
        Random r = new Random(0);
        for(int i = 0; i < a.length; i++)
            a[i] = r.nextInt();
        long bgn, end;
        bgn = System.currentTimeMillis();
        imsort(a, 0, a.length);
        end = System.currentTimeMillis();
        for(int i = 1; i < a.length; i++){
            if(a[i-1] > a[i]){
                System.out.println("failed");
                break;
            }
        }
        System.out.println("milliseconds " + (end-bgn));
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

算法:混合归并排序和插入排序执行时间 的相关文章

随机推荐

  • 使用lambda表达式连接pyqt中的槽

    我正在尝试将插槽与 lambda 函数连接起来 但它没有按我预期的方式工作 在下面的代码中 我成功正确连接了前两个按钮 对于我循环连接的后两个 这是错误的 之前有人问过同样的问题 Qt 使用 lambda 将槽与参数连接 https sta
  • OrderedDict 不按顺序排列

    这个循环的想法是迭代列表 如果对象的某个属性不是 OrderedDict 的键 则会添加它 它是对象列表的字典 for object in someList if object DATE not in myOrderedDict myOrd
  • 使 Omni 能够在 Windows 上的 vim 7.2 上运行

    我正在尝试将 Omni Complete 功能与 gVim 7 2 一起使用 但在 Windows 上我不断收到一条错误消息 错误 需要使用 python 编译 vimE117 未知功能 pythoncomplete complete 看起
  • 如何确定变量的内存占用(大小)?

    PHP 或 PHP 扩展 中是否有函数可以找出给定变量使用了多少内存 sizeof只是告诉我元素 属性的数量 memory get usage有帮助的是它给了我所使用的内存大小whole脚本 有没有办法对单个变量执行此操作 请注意 这是在开
  • django 排除的性能问题

    我有一个 Django 1 8 应用程序 并且使用 MsSQL 数据库 以 pyodbc 作为数据库后端 使用 django pyodbc azure 模块 我有以下型号 class Branch models Model name mod
  • BroadcastReceiver如何启动新意图

    我实现了一个广播接收器 以便在互联网连接丢失时 阻止 我的应用程序 我所说的阻止是指应用程序必须打开 无互联网连接 活动 这是我的接收器代码 public class ConnectivityReceiver extends Broadca
  • Gradle:将所有测试依赖项复制到 zip 文件

    我对 gradle 很陌生 所以也许我问的问题很简单 我正在寻找一种解决方案 将 testCompile 范围内的所有依赖项放入 zip 文件中 我检查了http forums gradle org gradle topics how do
  • 如何在 JavaScript 中访问当前范围之外的变量?

    我正在用 javascript 编写一个应用程序 但无法弄清楚如何访问此 jquery 解析中函数中声明的变量 在内部我可以访问全局变量 但我真的不想为这些值创建全局变量 基本上我想从 xml 文档中提取文件名simulationFiles
  • chrome.identity.getAuthToken 不起作用

    我正在使用 Chrome Identity API 在我的 Chrome 扩展程序上为用户提供 Google 身份验证 我参考了Google官方教程 链接 Chrome 身份 API https developer chrome com a
  • BIRT 的 HTMLRenderReport 向嵌入图像添加类似“file://”的 url(而不是将它们嵌入到 HTML 中)

    我有一个 BIRT 报告 母版页中有一个图像 我的 BIRT 设计文件 我在报告中嵌入了一个 png 它在执行后生成了以下 XMLbody tag
  • JavaScript 中字符串的子类化

    我有一个字符串方法String prototype splitName 将作者姓名 字符串 拆分为名字和姓氏 该声明var name authorName splitname 返回一个对象字面量name with name first an
  • 回调 after_destroy 未通过 ActiveAdmin 触发

    我使用 ActiveAdmin 作为我的应用程序的后台 我有以下三个模型 class Organization has many organization collection relations has many collections
  • JTextField:文本太长时如何在 JTextField 左侧设置文本

    我有一个很长的String我想显示在JTextField 如果String太长了 它显示的是右侧部分String 而不是左边部分 即使我使用setHorizontalAlignment JTextField LEFT 例如 如果我的Stri
  • 发送后10秒使用JDA删除消息

    我正在制作一个不和谐的机器人 它发送一个嵌入来显示用户库存 我正在制作一个游戏机器人 为了避免混乱 我想在 10 20 秒后删除该消息 任何人都知道我该怎么做 如果你完全理解这些问题 那么请不要说 哦 你需要遵循等等格式 我正在使用 Jav
  • ISO-8859-1(正确/错误)

    这是真话还是假话 Unicode 是 ISO 8859 1 的超集 因此前 256 个 Unicode 字符对应于 ISO 8859 1 规范的编码 ISO 8859 1 仅包含 256 编码 也就是说 没有什么比 256 个代码更多的了
  • 如何从同一个类中的另一个运算符重载成员函数调用运算符重载成员函数(或使用运算符)?

    我正在用 C 编写一段代码来处理复数 我也在练习运算符重载 所以我超载了 乘法运算符 现在我想在我的重载中使用重载运算符 除法运算符 但是当我使用时 它的显示错误 这是代码 include
  • 为什么在初始化结构时出现段错误?

    找了一圈一小时 我想我最好在这里发布问题 我简化了代码 段错误在函数中initMyStruct include stdlib h typedef struct int arr1 int arr2 myStruct void allocMyS
  • 设置为[UIImage new]后恢复navigationBar背景图片

    我需要一个完全透明的地图视图导航栏 所以我这样做了 self navigationController navigationBar setBackgroundImage UIImage new forBarMetrics UIBarMetr
  • 连接到 AND UA651BLE 时出现状态为 133 的 Type_Gatt_Error

    我正在尝试连接到 AnD UA 651BLE 血压监测仪并获取 Android 应用程序中的值 该应用程序能够找到设备 但我在 onConnectionStateChange 中收到 Type Gatt Error 这对于某些设备 如三星
  • 算法:混合归并排序和插入排序执行时间

    美好的一天 SO社区 我是一名计算机科学学生 目前正在进行合并排序和插入排序相结合的实验 据了解 对于一定的阈值S InsertionSort将比MergeSort具有更快的执行时间 因此 通过合并两种排序算法 总运行时间将得到优化 然而