草庐IT

java - 算法 : Hybrid MergeSort and InsertionSort Execution Time

coder 2024-03-13 原文

美好的一天 SO 社区,

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

但是,在多次运行实验后,使用1000的样本大小,不同大小的S,每次实验的结果都没有给出确定的答案。这是获得的更好结果的图片(请注意,有一半的时间结果不是确定的):

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

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

尽管从逻辑上讲,当 S<=10 时,hybrid="" mergesort="" 会更快,因为="" insertionsort="">

目前,这些是教给我的时间复杂度:

合并排序:O(n log n)

插入排序:

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

最后,我找到了在线资源:https://cs.stackexchange.com/questions/68179/combining-merge-sort-and-insertion-sort声明:

混合合并插入排序:

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

我想问一下 CS 社区中是否有结果显示明确的证据表明混合合并排序算法在低于特定阈值 S 时将比普通合并排序算法工作得更好,如果是这样,为什么?

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

注意:我使用 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 位整数进行排序,而图中的明显结果在大约 42 秒而不是 42 毫秒时慢了 1000 倍。我还测试了 10,000,000 个整数,排序需要 1 秒多一点。

过去,使用 C++,我将自底向上归并排序与混合自底向上归并/插入排序进行比较,对于 1600 万 (2^24 == 16,777,216) 个 32 位整数,混合排序大约为 8%使用 S == 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));
    }
}

关于java - 算法 : Hybrid MergeSort and InsertionSort Execution Time,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46496692/

有关java - 算法 : Hybrid MergeSort and InsertionSort Execution Time的更多相关文章

  1. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  2. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  3. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  4. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  5. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  6. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  7. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  8. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  9. java - 为什么 ruby​​ modulo 与 java/other lang 不同? - 2

    我基本上来自Java背景并且努力理解Ruby中的模运算。(5%3)(-5%3)(5%-3)(-5%-3)Java中的上述操作产生,2个-22个-2但在Ruby中,相同的表达式会产生21个-1-2.Ruby在逻辑上有多擅长这个?模块操作在Ruby中是如何实现的?如果将同一个操作定义为一个web服务,两个服务如何匹配逻辑。 最佳答案 在Java中,模运算的结果与被除数的符号相同。在Ruby中,它与除数的符号相同。remainder()在Ruby中与被除数的符号相同。您可能还想引用modulooperation.

  10. java - Ruby 相当于 Java 的 Collections.unmodifiableList 和 Collections.unmodifiableMap - 2

    Java的Collections.unmodifiableList和Collections.unmodifiableMap在Ruby标准API中是否有等价物? 最佳答案 使用freeze应用程序接口(interface):Preventsfurthermodificationstoobj.ARuntimeErrorwillberaisedifmodificationisattempted.Thereisnowaytounfreezeafrozenobject.SeealsoObject#frozen?.Thismethodretur

随机推荐