查找未排序数组中的第k个最小元素

时间:2017-04-25 09:01:49

标签: java sorting partitioning pseudocode

我正在尝试实现以下伪代码。我只需要使用逻辑分区来执行此操作。

Procedure SELECT( k,S) 
{ if  |S| =1 then return the single element in S
   else  { choose an element a randomly from S;
          let S1,S2,and S3 be he sequences of elements in S   
          less than, equal to, and greater than m, respectively;
         if |S1| >=k then return SELECT(k,S1)
          else 
               if (|S1| + |S2| >=k then return m
               else  return SELECT(k-|S1|-|S2| , S3);
         }
}

到目前为止,这是我的尝试:

public static int select(int k, int[] s, int arrayLeft, int arrayRight) {
    if (s.length == 1) {
        return s[0];
    } else {
        Random rand = new Random();
        int right = rand.nextInt(arrayRight) + arrayLeft;
        int m = s[right];
        int pivot = partition(s, arrayLeft, right); // pivot = |s1|
        if (pivot >= k) {
            return select(k, s, arrayLeft, pivot - 1);
        } else {
            // Calculate |s2|
            int s2Length = 0;
            for (int i = pivot; s[i] == m; i++) {
                s2Length++;
            }
            if (pivot + s2Length >= k) {
                return m;
            } else {
                int s3Left = pivot + s2Length;
                return select(k - pivot - s2Length, s, s3Left + 1, s.length);
            }
        }
    }
}

// all elements smaller than m are to the left of it,
// all elements greater than m are to the right of it
private static int partition(int[] s, int left, int right) {
    int m = s[right];
    int i = left;
    for (int j = left; j <= right - 1; j++) {
        if (s[j] <= m) {
            swap(s, i, j);
            i++;
        }
    }
    swap(s, i, right);
    return i;
}

private static void swap(int[] s, int i, int j) {
    int temp = s[i];
    s[i] = s[j];
    s[j] = temp;
}

我的select方法没有返回实际的第k个最小元素。分区方法仅在小于m的元素上正常工作。在m右边的数组部分,有任何值的元素。我该如何解决?我在网上看到的所有解决方案都与我的方法相同。任何帮助表示赞赏!

1 个答案:

答案 0 :(得分:1)

我不确定您的代码应该如何工作的详细信息,但我认为我发现了一些可疑点。

首先,我认为您应该准确了解适用于您的方法以及它如何使用arrayLeftarrayRight。写一个Javadoc评论并说明这一点。这将使您和其他任何人更容易争论代码中的正确和错误。

这是错误的:

    if (s.length == 1) {

您正在通过所有递归调用传递相同的数组,因此如果它从一开始就没有长度1(一个简单的情况),它将永远不会有长度1.而是使用arrayLeft和{{ 1}}确定要考虑的元素数量。

这条线看起来不正确:

arrayRight

如果 int right = rand.nextInt(arrayRight) + arrayLeft; 为10且arrayLeft 12,则最多可以产生21.我在下一行中观察到arrayRight一次因为ArrayIndexOutOfBoundsException指向数组外部

此行中的注释不正确,可能会导致您对代码的错误参数:

right

int pivot = partition(s, arrayLeft, right); // pivot = |s1| 返回的pivot是重新排序后partition()的索引。我认为正确的陈述是m。请检查一下。

我进一步认为你不应该将pivot == arrayLeft + |s1|作为上述调用中的最后一个参数,而是right。此错误可能是您arrayRightpartition()右侧留下任何值的观察结果。

你也可能冒m的风险:

ArrayIndexOutOfBoundsException

您应该添加其他条件,例如 for (int i = pivot; s[i] == m; i++) { i <= arrayRight

最后,这在我看来是错误的:

i < s.length

我在想:

                return select(k - pivot - s2Length, s, s3Left + 1, s.length);

但请查看您自己的知识。我特别怀疑 return select(k - pivot - s2Length, s, s3Left, arrayRight);