13

这是场景。

我得到一个整数数组'A'。数组的大小不是固定的。我应该编写的函数可能会用一个只有几个整数的数组调用一次,而另一次,它甚至可能包含数千个整数。此外,每个整数不需要包含相同数量的数字。

我应该对数组中的数字进行“排序”,以使生成的数组具有按字典顺序排列的整数(即,它们根据它们的字符串表示进行排序。这里的“123”是 123 的字符串表示)。请注意,输出应该只包含整数,而不是它们的字符串等价物。

例如:如果输入是:

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

那么输出应该是:

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

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

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

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

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

4

14 回答 14

9

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

更详细(伪代码):

  1. 分配一个 char** 的数组,aux只要numbers数组
  2. 对于 i 从 0 到length of numbers-1,aux[i]=stringify(numbers[i])
  3. indices分配一个相同长度的 int 数组
  4. 对于 i 从 0 到length of numbers-1,indices[i]=i
  5. 排序indices,使用 ascmp(i,j) strcmp(aux[i],aux[j])
  6. results分配一个相同长度的 int 数组
  7. 对于 i 从 0 到length of numbers-1,results[i]=numbers[indices[i]]
  8. memcpyresults结束numbers
  9. 释放每个aux[i], 还有aux, indices,results
于 2009-05-19T14:02:46.600 回答
6

由于您提到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不匹配通过自动装箱自动工作)

于 2009-05-19T14:25:55.193 回答
3

我只是将它们转换为字符串,然后使用 strcmp 进行排序,然后进行 lex 比较。

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

于 2009-05-19T14:08:46.960 回答
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());
}
于 2009-05-19T14:15:22.690 回答
2

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

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

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

于 2009-05-19T14:08:04.670 回答
2

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

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

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

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

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

于 2009-05-19T14:14:18.293 回答
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 位整数的版本。如果您不能立即看到模式,请考虑如何编写munge5、6 和 7 位整数的版本。

unmunge是 的倒数munge

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

于 2009-05-19T14:35:17.750 回答
1

如果您想尝试更好的预处理-排序-后处理,请注意 int 最多为 10 个十进制数字(暂时忽略有符号性)。

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

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,它可能会通过更少的内存总量来搅动,并且需要更少的收集。

于 2009-05-19T15:24:15.560 回答
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类似于ComparatorJason Cohen 的答案中所示。

“lar s log”部分显示lars了按 排序后的数组,说明了一种基于对数的方法,它给出与 do和其他基于字符串的方法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");
    }
}
于 2014-07-02T16:38:18.517 回答
1

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

于 2012-06-27T14:41:17.497 回答
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
}

如果它太慢(肯定比预处理慢),请在某处跟踪转换,以便比较函数不必继续执行它们。

于 2009-05-19T14:12:06.893 回答
0

可能的优化:而不是这个:

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

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

于 2009-05-19T14:19:10.107 回答
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 个元素所用的时间,但不包括将它们输出到控制台的时间。输出增加了大约一秒钟。

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

于 2009-05-19T14:20:02.960 回答
0

一种非常 hacky 的方法(使用 C)是:

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

在 Java 中(从这里):

long bits = Double.doubleToLongBits(5894.349580349);

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

所以你会在这里排序mantissa

于 2009-05-19T16:13:48.763 回答