如何按字典顺序对数字进行排序?

时间:2009-05-19 13:59:10

标签: algorithm sorting big-o

以下是该方案。

我得到一个整数数组'A'。数组的大小不固定。我应该写的函数可以用一个只有几个整数的数组调用一次,而另一个时间,甚至可能包含数千个整数。此外,每个整数不必包含相同的位数。

我应该对数组中的数字进行“排序”,使得结果数组具有以字典方式排序的整数(即,它们基于它们的字符串表示进行排序。这里“123”是123的字符串表示) 。请注意,输出应仅包含整数,而不是其等效字符串。

例如:如果输入为:

[12 | 2434 | 23 | 1 | 654 | 222 | 56 | 100000]

然后输出应为:

[1 | 100000 | 12 | 222 | 23 | 2434 | 56 | 654]

我的初始方法:我将每个整数转换为字符串格式,然后在其右侧添加零,使所有整数包含相同的位数(这是涉及跟踪的混乱步骤等使得解决方案非常低效)然后进行基数排序。 最后,我删除了填充的零,将字符串转换回它们的整数并将它们放入生成的数组中。这是一个非常低效的解决方案。

我一直认为解决方案不需要填充等,并且有一个简单的解决方案,您只需要以某种方式处理数字(一些位处理?)以获得结果。

您能想到的空间最有效的解决方案是什么?时间方面?

如果您要提供代码,我更喜欢Java或伪代码。但如果这不适合你,任何这样的语言应该没问题。

14 个答案:

答案 0 :(得分:9)

可执行伪代码(又名Python):thenumbers.sort(key=str)。是的,我知道使用Python有点像作弊 - 它只是太强大强大;-)。但严重的是,这也意味着:如果你可以按字典顺序对字符串数组进行排序,就像Python本身可以排序的那样,那么只需从每个数字中取出“关键字符串”并对该辅助数组进行排序(然后可以通过以下方式重建所需的数字数组) str-> int转换,或通过间接等对索引进行排序等);这被称为DSU(装饰,排序,未装饰),它是Python排序实现的key=参数。

更详细(伪代码):

  1. 只要aux数组
  2. 分配一个char ** numbers数组
  3. for i从0到length of numbers-1aux[i]=stringify(numbers[i])
  4. 分配一个长度相同的int indices数组
  5. for i从0到length of numbers-1indices[i]=i
  6. 使用indices cmp(i,j)
  7. 排序strcmp(aux[i],aux[j])
  8. 分配一个长度相同的int results数组
  9. for i从0到length of numbers-1results[i]=numbers[indices[i]]
  10. memcpy results over numbers
  11. 免费提供aux[i]auxindicesresults

答案 1 :(得分:5)

由于您提到Java是有问题的实际语言:

您无需转换为字符串和从字符串转换。相反,定义自己的比较器并在排序中使用它。

具体做法是:

Comparator<Integer> lexCompare = new Comparator<Integer>(){
   int compareTo( Integer x, Integer y ) {
      return x.toString().compareTo( y.toString() );
   }
};

然后你可以像这样排序数组:

int[] array = /* whatever */;
Arrays.sort( array, lexCompare );

(注意:int / Integer不匹配会自动通过自动装箱工作)

答案 2 :(得分:3)

我只是把它们变成字符串,然后排序然后使用strcmp进行排序,它执行lex比较。

或者你可以编写一个“lexcmp”函数来比较使用%10和/ 10的两个数字,但这与调用atoi很多次基本相同,所以不是一个好主意。

答案 3 :(得分:3)

实际的排序可以通过您喜欢的任何算法完成。这个问题的关键是找到比较函数,根据这个方案,正确识别哪些数字应该“小于”其他数字:

bool isLessThan(int a, int b)
{
    string aString = ToString(a);
    string bString = ToString(b);

    int charCount = min(aString.length(), bString.length())
    for (charIndex = 0; charIndex < charCount; charIndex++)
    {
        if (aString[charIndex] < bString[charIndex]) { return TRUE; }
    }

    // if the numbers are of different lengths, but identical
    // for the common digits (e.g. 123 and 12345)
    // the shorter string is considered "less"
    return (aString.length() < bString.length());
}

答案 4 :(得分:2)

我的诱惑是说int到字符串的转换会发生在比较器代码而不是批量转换中。虽然从代码角度来看这可能更优雅,但我不得不说执行工作量会更大,因为每个数字可能会被多次比较。

我倾向于创建一个包含int和string表示的新数组(不确定是否需要为字符串比较填充字符串版本以生成您给出的顺序),对字符串进行排序然后将int值复制回原始数组。

我想不出一个聪明的数学方法来排序这个,就像你想要按字典顺序排序你自己的语句,所以你需要将数字转换为字符串才能做到这一点。

答案 5 :(得分:2)

你绝对不需要填充结果。它不会改变字典比较的顺序,它会更容易出错,而且只会浪费CPU周期。最“空间化”的有效方法是在比较数字时将数字转换为字符串。这样,您就不需要分配额外的数组,这些数字将在适当的位置进行比较。

只需根据需要将它们转换为字符串,即可快速获得相当好的实现。对数字进行字符串化并不是特别昂贵,因为您一次只处理两个字符串,所以它们很可能始终保留在CPU缓存中。因此,比较将比将整个数组转换为字符串的情况快得多,因为它们不需要从主存储器加载到缓存中。人们倾向于忘记CPU具有缓存,并且在较小的本地内存区域中执行大量工作的算法将从更快的缓存访问中受益。在某些体系结构中,缓存比内存快得多,您可以在从主内存加载数据时对数据执行数百次操作。因此,在比较函数中做更多工作实际上可能比预处理数组快得多。特别是如果你有一个大阵列。

尝试在比较器功能和基准测试中进行字符串序列化和比较。我认为这将是一个非常好的解决方案。示例java-ish伪代码:

public static int compare(Number numA, Number numB) {
    return numA.toString().compare(numB.toString());
}

我认为你能做的任何奇特的比较都必须大致相当于将数字转换成字符串所涉及的工作。所以你可能不会得到显着的好处。你不能只是直接进行位比较,这会给你一个不同于词典排序的顺序。无论如何,你需要能够找出数字的每个数字,所以最简单的方法就是让它们成为字符串。可能会有一些光滑的技巧,但是我能想到的每一条大道都是棘手的,容易出错的,而且工作量远远超过它的价值。

答案 6 :(得分:1)

伪代码:

sub sort_numbers_lexicographically (array) {
    for 0 <= i < array.length:
        array[i] = munge(array[i]);
    sort(array);  // using usual numeric comparisons
    for 0 <= i < array.length:
        array[i] = unmunge(array[i]);
}

那么,mungeunmunge是什么?

munge因整数大小而异。例如:

sub munge (4-bit-unsigned-integer n) {
    switch (n):
        case 0:  return 0
        case 1:  return 1
        case 2:  return 8
        case 3:  return 9
        case 4:  return 10
        case 5:  return 11
        case 6:  return 12
        case 7:  return 13
        case 8:  return 14
        case 9:  return 15
        case 10:  return 2
        case 11:  return 3
        case 12:  return 4
        case 13:  return 5
        case 14:  return 6
        case 15:  return 7
}

从根本上说,munge正在做的是说当列表排序时4位整数的顺序是什么。我相信你可以看到这里有一个模式---我不必使用开关---并且你可以写一个版本munge,可以很容易地处理32位整数。想想如果你不能立即看到模式,你将如何为5,6和7位整数编写munge版本。

unmungemunge的倒数。

因此,您可以避免将任何内容转换为字符串---您不需要任何额外的内存。

答案 7 :(得分:1)

如果你想尝试一个更好的预处理 - 排序 - 后处理,那么请注意一个int最多10个十进制数字(暂时忽略signed-ness)。

因此二进制编码的十进制数据适合64位。映射数字0-> 1,1-> 2等,并使用0作为NUL终止符(以确保“1”小于“10”)。将每个数字依次从最小的数字开始移动到长的顶部。对长整数进行排序,这将按照原始整数的字典顺序排列。然后通过一次一个地移回一个数字来回转出每个长的顶部:

uint64_t munge(uint32_t i) {
    uint64_t acc = 0;
    while (i > 0) {
        acc = acc >> 4;
        uint64_t digit = (i % 10) + 1;
        acc += (digit << 60);
        i /= 10;
    }
    return acc;
}

uint32_t demunge(uint64_t l) {
    uint32_t acc = 0;
    while (l > 0) {
        acc *= 10;
        uint32_t digit = (l >> 60) - 1;
        acc += digit;
        l << 4;
    }
}

或类似的东西。由于Java没有无符号整数,因此您必须稍微修改它。它使用了大量的工作内存(输入大小的两倍),但仍然比初始方法要少。它可能比在比较器中即时转换为字符串更快,但它使用更多的峰值内存。但是,根据GC的不同,它可能会减少内存总量,并且需要较少的收集。

答案 8 :(得分:1)

如果所有数字都小于1E + 18,您可以将每个数字转换为UINT64,乘以10并加1,然后乘以10,直到它们至少为1E + 19。然后排序那些。要取回原始数字,请将每个数字除以10,直到最后一位数字为非零(应为1),然后再将其除以10。

答案 9 :(得分:1)

该问题并未说明如何在词典整理顺序中处理负整数。前面介绍的基于字符串的方法通常会将负值排序到前面;例如,{-123,-345,0,234,78}将按此顺序保留。但如果忽略减号,则输出顺序应为{0,-123,234,-345,78}。人们可以通过稍微麻烦的额外测试来调整基于字符串的方法来产生该顺序。

在理论和代码中,使用比较器比较两个整数的常用对数的小数部分可能更简单。也就是说,它将比较两个数字的基数10对数的尾数。基于对数的比较器将比基于字符串的比较器运行得更快或更慢,具体取决于CPU的浮点性能规格和实现质量。

本答案末尾显示的java代码包括两个基于对数的比较器:alogCompareslogCompare。前者忽略了符号,因此会从{-123,-345,0,234,78}产生{0,-123,234,-345,78}。

接下来显示的数字组是java程序产生的输出。

“dar rand”部分显示生成的随机数据数组dar。它读取然后向下,每行5个元素。请注意,数组sarlaralars最初是dar的未排序副本。

通过dar排序后,“dar排序”部分为Arrays.sort(dar);

“sar lex”部分在使用sar排序后显示数组Arrays.sort(sar,lexCompare);,其中lexCompare类似于Jason Cohen的回答中显示的Comparator

“lar s log”部分按lars排序后显示数组Arrays.sort(lars,slogCompare);,说明了一个基于对数的方法,该方法提供与lexCompare和其他基于字符串的方法相同的顺序

“lar a log”部分按lara排序后显示数组Arrays.sort(lara,alogCompare);,说明忽略减号的基于对数的方法。

dar rand    -335768    115776     -9576    185484     81528
dar rand      79300         0      3128      4095    -69377
dar rand     -67584      9900    -50568   -162792     70992

dar sort    -335768   -162792    -69377    -67584    -50568
dar sort      -9576         0      3128      4095      9900
dar sort      70992     79300     81528    115776    185484

 sar lex    -162792   -335768    -50568    -67584    -69377
 sar lex      -9576         0    115776    185484      3128
 sar lex       4095     70992     79300     81528      9900

lar s log    -162792   -335768    -50568    -67584    -69377
lar s log      -9576         0    115776    185484      3128
lar s log       4095     70992     79300     81528      9900

lar a log          0    115776   -162792    185484      3128
lar a log    -335768      4095    -50568    -67584    -69377
lar a log      70992     79300     81528     -9576      9900

Java代码如下所示。

// Code for "How can I sort numbers lexicographically?" - jw - 2 Jul 2014
import java.util.Random;
import java.util.Comparator;
import java.lang.Math;
import java.util.Arrays;
public class lex882954 {
// Comparator from Jason Cohen's answer
    public static Comparator<Integer> lexCompare = new Comparator<Integer>(){
        public int compare( Integer x, Integer y ) {
            return x.toString().compareTo( y.toString() );
        }
    };
// Comparator that uses "abs." logarithms of numbers instead of strings
    public static Comparator<Integer> alogCompare = new Comparator<Integer>(){
        public int compare( Integer x, Integer y ) {
            Double xl = (x==0)? 0 : Math.log10(Math.abs(x));
            Double yl = (y==0)? 0 : Math.log10(Math.abs(y));
            Double xf=xl-xl.intValue();
            return xf.compareTo(yl-yl.intValue());
        }
    };
// Comparator that uses "signed" logarithms of numbers instead of strings
    public static Comparator<Integer> slogCompare = new Comparator<Integer>(){
        public int compare( Integer x, Integer y ) {
            Double xl = (x==0)? 0 : Math.log10(Math.abs(x));
            Double yl = (y==0)? 0 : Math.log10(Math.abs(y));
            Double xf=xl-xl.intValue()+Integer.signum(x);
            return xf.compareTo(yl-yl.intValue()+Integer.signum(y));
        }
    };
// Print array before or after sorting
    public static void printArr(Integer[] ar, int asize, String aname) {
        int j;
        for(j=0; j < asize; ++j) {
            if (j%5==0)
                System.out.printf("%n%8s ", aname);
            System.out.printf(" %9d", ar[j]);
        }
        System.out.println();
    }
// Main Program -- to test comparators
    public static void main(String[] args) {
        int j, dasize=15, hir=99;
        Random rnd = new Random(12345);
        Integer[] dar = new Integer[dasize];
        Integer[] sar = new Integer[dasize];
        Integer[] lara = new Integer[dasize];
        Integer[] lars = new Integer[dasize];

        for(j=0; j < dasize; ++j) {
            lara[j] = lars[j] = sar[j] = dar[j] = rnd.nextInt(hir) * 
                rnd.nextInt(hir) * (rnd.nextInt(hir)-44);
        }
        printArr(dar, dasize, "dar rand");
        Arrays.sort(dar);
        printArr(dar, dasize, "dar sort");
        Arrays.sort(sar, lexCompare);
        printArr(sar, dasize, "sar lex");
        Arrays.sort(lars, slogCompare);
        printArr(lars, dasize, "lar s log");
        Arrays.sort(lara, alogCompare);
        printArr(lara, dasize, "lar a log");
    }
}

答案 10 :(得分:0)

如果你想提高空间效率,我会尝试在排序的比较功能中完成工作

int compare(int a, int b) {
   // convert a to string
   // convert b to string
   // return -1 if a < b, 0 if they are equal, 1 if a > b
}

如果它太慢(它确实比预处理慢),请跟踪某处的转换,以便比较功能不必继续这样做。

答案 11 :(得分:0)

可能的优化:而不是:

  

我将每个整数转换为字符串格式,然后在其右侧添加零,以使所有整数包含相同的位数

你可以将每个数字乘以(10 ^ N - log10(数字)),N是一个大于任何数字log10的数字。

答案 12 :(得分:0)

#!/usr/bin/perl

use strict;
use warnings;

my @x = ( 12, 2434, 23, 1, 654, 222, 56, 100000 );

print $_, "\n" for sort @x;

__END__

一些时间......首先,空的@x:

C:\Temp> timethis s-empty
TimeThis :  Elapsed Time :  00:00:00.188

现在,有10,000个随机生成的元素:

TimeThis :  Elapsed Time :  00:00:00.219

这包括生成10,000个元素所花费的时间,但不包括将它们输出到控制台的时间。输出增加了大约一秒钟。

所以,节省一些程序员的时间; - )

答案 13 :(得分:0)

一个真正的hacky方法(使用C)将是:

  • 生成转换为浮动的所有值的新数组
  • 使用尾数(有效位)位进行排序以进行比较

在Java中(来自here):

long bits = Double.doubleToLongBits(5894.349580349);

boolean negative = (bits & 0x8000000000000000L) != 0; 
long exponent = bits & 0x7ff0000000000000L >> 52;
long mantissa = bits & 0x000fffffffffffffL;

所以你要在这里排长mantissa