计算插入次数的有效方法是按递增顺序排序整数数组

时间:2012-01-25 18:22:24

标签: c++ c arrays algorithm sorting

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

例如:

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

6 个答案:

答案 0 :(得分:22)

如果要计算插入排序所需的交换次数,那么您希望找到以下数字:对于每个元素,数组中的先前元素数量是多少?然后,这些值的总和就是执行的交换总数。

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

  1. 初始化一个新的空订单统计树。
  2. 设置计数= 0
  3. 对于每个数组元素,按顺序:
    1. 将元素添加到订单统计树。
    2. 添加以计算树中少于添加值的元素数。
  4. 返回计数,
  5. 这会进行O(n)次迭代,占用O(log n)时间,因此完成的总工作量为O(n log n),这比蛮力方法快。


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

    1. 设置总数= 0
    2. 对于k = 1到n:
      1. 如果索引k处的元素不是第k个最大元素:
        1. 用第k个最大元素交换它。
        2. 增加总数
    3. 返回总数
    4. 那么我们如何有效地实现这一目标呢?我们需要有效地检查给定索引处的元素是否是正确的元素,并且还需要有效地找到真正属于给定索引的元素的位置。要做到这一点,首先要创建一个平衡的二进制搜索树,将每个元素映射到原始数组中的位置。这需要时间O(n log n)。现在您已经拥有了平衡树,我们可以通过为树中的每个元素分配此元素所属的排序序列中的位置来扩充结构。一种方法是使用订单统计树,另一种方法是使用inorder遍历迭代树,用树的位置注释树中的每个值。

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

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

      希望这有帮助!

答案 1 :(得分:8)

以自然顺序排列它们所需的连续元素的互换次数等于给定排列中的反转次数。

因此,解决此问题的方法是找到给定数组中的反转次数。

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

在合并步骤中,如果从右侧数组复制元素,则按左侧数组中剩余的项目数递增全局计数器(计数反转)。这样做是因为刚刚复制的右侧数组中的元素涉及反转,左侧数组中存在所有元素。

希望这有帮助

答案 2 :(得分:1)

我不确定,但我怀疑找到最小数字是一个难题。除非有快捷方式,否则您只需搜索最佳的排序网络,您应该能够在自己喜欢的搜索引擎(或维基百科)上找到合适的资源。

如果你只关心大O的复杂性,答案是O(n log n),如果你看一下有效的就地分析,你可能会得到更具体的界限(那里有一些实际常数)排序算法,如heapsort或smoothsort。

答案 3 :(得分:1)

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;
    }

}

答案 4 :(得分:0)

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

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

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

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

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

答案 5 :(得分:0)

#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;
}