繁体   English   中英

不允许元素计数的排序算法

[英]Sorting algorithm which does not allow counting of elements

我在公司面试中看到过交叉测试这个问题,但我首先不清楚这个问题。 你们能澄清我的疑问吗?

问题:编写程序对仅包含 0、1 和 2 的 integer 数组进行排序。 不允许计算元素,您应该以 O(n) 时间复杂度进行计算。

例如数组:{2, 0, 1, 2, 1, 2, 1, 0, 2, 0}

Output 到一个链表。

  • 记住列表的开头。
  • 记住 position 从 1 开始。
  • 记住列表的结尾。

遍历整个数组。

  • 如果遇到0,将其添加到链表的第一个position。
  • 如果遇到1,在1的position后面加上。
  • 如果遇到 2,请将其添加到列表末尾。

高温高压

与其用另一个难以理解的伪代码爆破你,我会给你问题的名称:这个问题被称为荷兰国旗问题(由 Edsgar Dijkstra 首次提出),可以通过三路合并来解决(请参阅解决此问题的第一个答案中的 PHP 代码,尽管效率很低)。

Bentley 和 McIlroy 的开创性论文Engineering a Sort Function中描述了一种更有效的三路合并解决方案。 它使用四个索引来划分中间数组的范围,中间数组的值是未排序的,两边是 1,中间是 0 和 2:

三路合并

在建立了这个不变量之后, =部分(即 1)被交换回中间。

这取决于您所说的“不允许计数”是什么意思。

一种简单的方法是创建一个新的空数组,然后查找 0,将它们附加到新数组中。 重复 1 和 2,它在 O(n) 时间内排序。

但这或多或少是一种基数排序。 就像我们在数 0 和 1 和 2 一样,所以我不确定这是否符合您的标准。


编辑:我们可以只用 O(1) 额外的 memory 来做到这一点,方法是为我们的插入点保留一个指针(从数组的开头开始),并在数组中扫描 0,将每个 0 与指针所在的元素交换,并增加指针。 然后重复1,2,它仍然是O(n)。

Java实现:

import java.util.Arrays;

public class Sort 
{
    public static void main(String[] args)
    {
        int[] array = {2, 0, 1, 2, 1, 2, 1, 0, 2, 0};
        sort(array);
        System.out.println(Arrays.toString(array));
    }

    public static void sort(int[] array)
    {
        int pointer = 0;
        for(int i = 0; i < 3; i++)
        {
            for(int j = 0; j < array.length; j++)
            {
                if(array[j] == i)
                {
                    int temp = array[pointer];
                    array[pointer] = array[j];
                    array[j] = temp;
                    pointer++;
                }
            }
        }
    }
}

给出 output:

[0, 0, 0, 1, 1, 1, 2, 2, 2, 2]

抱歉,它是 php,但它似乎 O(n) 并且可以很容易地用 java 编写:)

$arr = array(2, 0, 1, 2, 1, 2, 1, 0, 2, 0);

$tmp = array(array(),array(),array());
foreach($arr as $i){
    $tmp[$i][] = $i;
}
print_r(array_merge($tmp[0],$tmp[1],$tmp[2]));

在 O(n) 中,伪代码:

def sort (src):
    # Create an empty array, and set pointer to its start.

    def dest as array[sizeof src]
    pto = 0

    # For every possible value.

    for val in 0, 1, 2:
        # Check every position in the source.

        for pfrom ranges from 0 to sizeof(src):
            # And transfer if matching (includes update of dest pointer).
            if src[pfrom] is val:
                dest[pto] = val
                pto = pto + 1

    # Return the new array (or transfer it back to the source if desired).

    return dest

这基本上是在源列表上迭代 3 次,如果元素与此遍所需的值匹配,则添加元素。 但它仍然是 O(n)。

等效的 Java 代码将是:

class Test {
    public static int [] mySort (int [] src) {
        int [] dest = new int[src.length];
        int pto = 0;

        for (int val = 0; val < 3; val++)
            for (int pfrom = 0; pfrom < src.length; pfrom++)
                if (src[pfrom] == val)
                    dest[pto++] = val;
        return dest;
    }

    public static void main(String args[]) {
        int [] arr1 = {2, 0, 1, 2, 1, 2, 1, 0, 2, 0};
        int [] arr2 = mySort (arr1);
        for (int i = 0; i < arr2.length; i++)
            System.out.println ("Array[" + i + "] = " + arr2[i]);
    }
}

输出:

Array[0] = 0
Array[1] = 0
Array[2] = 0
Array[3] = 1
Array[4] = 1
Array[5] = 1
Array[6] = 2
Array[7] = 2
Array[8] = 2
Array[9] = 2

但说真的,如果潜在雇主给了我这个问题,我会直接提出 state 如果他们愿意,我可以回答这个问题,但正确的答案是只使用Array.sort 然后,当且当该方法和特定数据集存在性能问题时,您可以调查更快的方法。

尽管要求是什么,但这种更快的方法几乎肯定会涉及计数。 您不会因任意限制而束缚您的开发人员。 需求应该指定需要什么,而不是如何。

如果你这样回答我这个问题,我会当场录用你。

这个答案计算元素。

因为数组中的值很少,只需计算每种类型的数量,然后使用它来重新填充数组。 我们还利用值从 0 开始连续的事实 - 使其匹配典型的 java int 循环。

public static void main(String[] args) throws Exception
{
    Integer[] array = { 2, 0, 1, 2, 1, 2, 1, 0, 2, 0 };
    List<Integer>[] elements = new ArrayList[3]; // To store the different element types

    // Initialize the array with new lists
    for (int i = 0; i < elements.length; i++) elements[i] = new ArrayList<Integer>();

    // Populate the lists
    for (int i : array) elements[i].add(i);

    for (int i = 0, start = 0; i < elements.length; start += elements[i++].size())
        System.arraycopy(elements[i].toArray(), 0, array, start, elements[i].size());

    System.out.println(Arrays.toString(array));
}

Output:

[0, 0, 0, 1, 1, 1, 2, 2, 2, 2]

Push 和 Pull 具有恒定的复杂性!

  1. 将每个元素推入优先级队列

  2. 将每个元素拉到索引 0...n

(:

您可以一次性完成,将遇到的每个元素放入最终的 position:

void sort012(int* array, int len) {
    int* p0 = array;
    int* p2 = array + len;
    for (int* p = array; p <= p2; ) {
        if (*p == 0) {
            std::swap(*p, *p0);
            p0++;
            p++;
        } else if (*p == 2) {
            std::swap(*p, *p2);
            p2--;
        } else {
            p++;
        }
    }
}

因为数组中的值很少,只需计算每种类型的数量,然后使用它来重新填充数组。 我们还利用值从 0 开始连续的事实 - 使其匹配典型的 java int 循环。

整个排序算法只需要三行代码:

public static void main(String[] args)
{
    int[] array = { 2, 0, 1, 2, 1, 2, 1, 0, 2, 0 };

    // Line 1: Define some space to hold the totals
    int[] counts = new int[3]; // To store the (3) different totals

    // Line 2: Get the total of each type
    for (int i : array) counts[i]++;

    // Line 3: Write the appropriate number of each type consecutively back into the array:
    for (int i = 0, start = 0; i < counts.length; start += counts[i++]) Arrays.fill(array, start, start + counts[i], i);

    System.out.println(Arrays.toString(array));
}

Output:

[0, 0, 0, 1, 1, 1, 2, 2, 2, 2]

我们从来没有提到array.length ,不在乎数组有多长。 它遍历数组只接触每个元素一次,使这个算法 O(n) 符合要求。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM