【问题标题】:Efficient way to count number of swaps to insertion sort an array of integers in increasing order计算交换次数以插入以递增顺序对整数数组进行排序的有效方法
【发布时间】:2012-02-18 22:50:32
【问题描述】:

给定一个长度为 n 的值数组,有没有办法计算插入排序将执行的交换次数,以便比 O(n2) 在时间上更好地对该数组进行排序?

例如:

arr[]={2 ,1, 3, 1, 2};  // Answer is 4.

算法:

for i <- 2 to N

    j <- i

 while j > 1 and a[j] < a[j - 1]

       swap a[j] and a[j - 1]  //I want to count this   swaps?

       j <- j - 1

【问题讨论】:

  • 编写自己的交换算法,并跟踪交换次数。
  • 为什么答案是 4?您可以使用 2 次交换对其进行排序(将 arr[0]arr[3] 交换,然后将 arr[2]arr[4] 交换)。
  • O(n^2) 是插入/选择排序的比较次数,而不是交换。
  • 您是要计算数学上最小的交换次数,还是某些特定算法完成的实际交换次数?
  • 但是插入排序不执行“交换”。

标签: c++ c arrays algorithm sorting


【解决方案1】:
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
int a[200001];
int te[200001];
unsigned long long merge(int arr[],int temp[],int left,int mid,int right)
{
    int i=left;
    int j=mid;
    int k=left;
    unsigned long long int icount=0;
    while((i<=mid-1) && (j<=right))
    {
        if(arr[i]<=arr[j])
        temp[k++]=arr[i++];
        else
        {
            temp[k++]=arr[j++];
            icount+=(mid-i);
        }
    }
    while(i<=mid-1)
    temp[k++]=arr[i++];
    while(j<=right)
    temp[k++]=arr[j++];
    for(int i=left;i<=right;i++)
    arr[i]=temp[i];
    return icount;
}
unsigned long long int mergesort(int arr[],int temp[],int left,int right)
{
    unsigned long long int i=0;
    if(right>left){
        int mid=(left+right)/2;
        i=mergesort(arr,temp,left,mid);
        i+=mergesort(arr,temp,mid+1,right);
        i+=merge(arr,temp,left,mid+1,right);
    }
    return i;
}
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=0;i<n;i++){
            scanf("%d",&a[i]);
        }
        printf("%llu\n",mergesort(a,te,0,n-1));
    }
    return 0;
}

【讨论】:

    【解决方案2】:
    package insertoinSortAnalysis;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.util.Scanner;
    
    public class Solution {
    
        private int[] originalArray;
    
        public static void main(String[] args) {
    
            Scanner sc;
            try {
                sc = new Scanner(System.in);
    
                int TestCases = sc.nextInt();
    
                for (int i = 0; i < TestCases; i++) {
                    int sizeofarray = sc.nextInt();
    
                    Solution s = new Solution();
                    s.originalArray = new int[sizeofarray];
    
                    for (int j = 0; j < sizeofarray; j++)
                        s.originalArray[j] = sc.nextInt();
    
                    s.devide(s.originalArray, 0, sizeofarray - 1);
                    System.out.println(s.count);
                }
    
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
        public int[] devide(int[] originalArray, int low, int high) {
            if (low < high) {
                int mid = (low + high) / 2;
                int[] result1 = devide(originalArray, low, mid);
                int[] result2 = devide(originalArray, mid + 1, high);
    
                return merge(result1, result2);
            }
    
            int[] result = { originalArray[low] };
            return result;
        }
    
        private long count = 0;
    
        private int[] merge(int[] array1, int[] array2) {
    
            int lowIndex1 = 0;
            int lowIndex2 = 0;
            int highIndex1 = array1.length - 1;
            int highIndex2 = array2.length - 1;
            int result[] = new int[array1.length + array2.length];
            int i = 0;
    
            while (lowIndex2 <= highIndex2 && lowIndex1 <= highIndex1) {
                int element = array1[lowIndex1];
                while (lowIndex2 <= highIndex2 && element > array2[lowIndex2]) {
                    result[i++] = array2[lowIndex2++];
                    count += ((highIndex1 - lowIndex1) + 1);
                }
                result[i++] = element;
                lowIndex1++;
            }
    
            while (lowIndex2 <= highIndex2 && lowIndex1 > highIndex1) {
                result[i++] = array2[lowIndex2++];
            }
    
            while (lowIndex1 <= highIndex1 && lowIndex2 > highIndex2) {
                result[i++] = array1[lowIndex1++];
            }
    
            return result;
        }
    
    }
    

    【讨论】:

      【解决方案3】:

      插入排序中的每次交换都会移动两个相邻的元素 - 一个上一个,一个下一个 - 并通过这样做“纠正”一个交叉。所以:

      • 使用其初始数组索引 Xi 注释每个项目 X。

      • 使用稳定排序对项目进行排序(如果您将“初始位置”注释视为次要键,则可以使用快速排序)

      • 返回每个元素的带注释的初始位置与其最终位置之间的绝对差之和的一半(即,只需循环通过注释求和 abs(Xi - i))。

      就像大多数其他答案一样,这是 O(n) 空间和 O(n*log n) 时间。如果可以修改就地合并以计算交叉点,那就更好了。我不确定它是否可以。

      【讨论】:

      • 不正确。考虑 [3,2,1]。解决方案的输出 = 2,(1 位置的总变化 = 2,对于 3 = 2,对于 2 = 0,因此 2+2+0/2 = 2)实际输出 = 3。
      【解决方案4】:

      按照自然顺序排列连续元素所需的交换次数等于给定排列中的反转次数。

      所以这个问题的解决方案是在给定的数字数组中找到反转的数量。

      这可以使用归并排序在 O(n log n) 内解决。

      在合并步骤中,如果您从右侧数组中复制一个元素,则将一个全局计数器(用于计算反转)增加左侧数组中剩余的项目数。这样做是因为刚刚复制的右侧数组中的元素与左侧数组中存在的所有元素进行了反转。

      希望对你有帮助

      【讨论】:

        【解决方案5】:

        如果你想计算插入排序中需要交换的次数,那么你想找到以下数字:对于每个元素,数组中有多少先前的元素比它小?这些值的总和就是执行的交换总数。

        要查找数字,您可以使用顺序统计树,这是一种平衡的二叉搜索树,可以有效地告诉您树中有多少元素小于某个给定元素。具体来说,Orde 统计树支持 O(log n) 插入、删除、查找和计算树中有多少元素小于某个值。然后,您可以按如下方式计算将执行多少次交换:

        1. 初始化一个新的空订单统计树。
        2. 设置计数 = 0
        3. 对于每个数组元素,按顺序:
          1. 将元素添加到订单统计树。
          2. 添加以计算树中小于添加值的元素数。
        4. 返回计数,

        这会花费 O(log n) 时间进行 O(n) 次循环迭代,因此完成的总工作量为 O(n log n),这比蛮力方法要快。


        如果您想计算选择排序中的交换次数,那么您可以使用插入排序仅在第 k 次执行交换的事实,如果在处理列表的前 k-1 个元素后,该元素在位置 k 不是第 k 个最小的元素。如果你能有效地做到这一点,那么我们有以下算法的基本草图:

        1. 设置总计 = 0
        2. 对于 k = 1 到 n:
          1. 如果索引 k 处的元素不是第 k 个最大的元素:
            1. 将其与第 k 个最大的元素交换。
            2. 增量总计
        3. 返回总数

        那么我们如何有效地实现它呢?我们需要能够有效地检查给定索引处的元素是否是正确的元素,并且还需要有效地找到真正属于给定索引处的元素的位置。为此,首先创建一个平衡的二叉搜索树,将每个元素映射到其在原始数组中的位置。这需要时间 O(n log n)。现在您有了平衡树,我们可以通过为树中的每个元素分配该元素所属的排序序列中的位置来扩充结构。一种方法是使用顺序统计树,另一种方法是使用中序遍历遍历树,用位置注释树中的每个值。

        使用这种结构,我们可以在 O(log n) 时间内检查一个元素是否在正确的位置,方法是在树中向上查找元素(时间 O(log n)),然后查看位置它应该在的排序序列以及它当前所在的位置(请记住,我们在创建树时设置了它)。如果它与我们预期的位置不一致,那么它在错误的位置,否则它在正确的位置。此外,我们可以通过在树中查找这两个元素(总共 O(log n) 时间)然后在 O(1) 中交换它们的位置来有效地模拟两个元素的交换。

        因此,我们可以在 O(n log n) - O(n log n) 时间内实现上述算法来构建树,然后进行 n 次迭代 O(log n) 来确定是否交换。

        希望这会有所帮助!

        【讨论】:

        • 为什么你认为先前元素的数量比给定元素“小”?不应该是“更大”的元素数量吗?
        【解决方案6】:

        我不确定,但我怀疑找到最小数量是一个难题。除非有捷径,否则您只会搜索最佳排序网络,您应该能够使用您最喜欢的搜索引擎(或维基百科)找到好的资源。

        如果您只关心大 O 复杂度,答案是 O(n log n),如果您查看一些有效的就地排序算法的分析,您可能会得到更具体的界限(其中的一些实际常量)比如堆排序或平滑排序。

        【讨论】:

        • 哦,我误会了。我以为您想要对其进行排序所需的最佳交换次数。为什么不直接运行插入排序并计算它们呢?
        • 但是在这样做时,需要 O(n^2) 时间复杂度...我正在寻找更好的方法
        • 我认为 OP 想要计算交换次数,但要在 sub-O(n^2) 时间内执行计算。
        • 实际上,一般输入很难找到最佳的排序网络。我怀疑为某个输入找到它要容易得多。
        • 对于单个输入,一种方法是获取输入,按您喜欢的方式对其进行排序,将函数从输入写入排序输出作为排列,然后使用您喜欢的任何已知算法置换到转置的最佳因式分解。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-15
        • 2023-03-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多