【问题标题】:Algorithm Issue -- Determine if array has already been partitioned (i.e. one step of quicksort)算法问题——确定数组是否已经被分区(即快速排序的一步)
【发布时间】:2013-08-08 14:58:49
【问题描述】:

在过去的一个月里,我的算法决赛的最后一个问题一直让我发疯。问题来了:

你有一个数组A[0...n],写一个算法(在“适当的”伪代码中)在 O(n) 中运行,它可以确定这个数组是否已经相对于某个索引 k 进行了分区,如果是,找到k;如果不是则返回-1;

澄清一下,Partition

对于A[0...n] 中的每个元素e,如果e < A[k]e 放在A[k] 的“左侧”,否则将e 放在A[k] 的“右侧”。

一个分区数组的例子(w.r.t. k = 11):

A = [4 2 5 3 7 4 2 6 8 4 11010 10 20 11 15 13 28 99 11]

然后

myAlgo(A) -> (11)

A = [10, 20, 30, 40, 11,100, 150, 101, 125]

然后

myAlgo(A) -> (5)

但不是:

A = [10, 20, 30, 40, 5]

myAlgo(A) -> (-1)

我的第一个想法(非常天真)太糟糕了,我简直无法用语言表达。基本上,它无意中检查了数组是否已排序并从中间拉出一个相当随机的值。

我的下一个想法是扫描列表并首先检查以找到我在达到递减数字之前达到的最高数字并将所有这些数字排除在外......基本上保持一个最大值和一个最小值,如果事情落在外面然后将我可能的分区索引转移到我的子集的末尾。

这是我尝试(非常非常糟糕地)实现它的地方(使用测试用例):

int myAlgo(const int* A, int n);

int main() {

    const int A[] = {10, 20, 30, 40, 11, 100, 150, 101, 125};

    int index;
    if((index = myAlgo(A, 9)) != -1) {
        printf("A[%d] = %d", index, A[index]);
    }
    else {
        printf("Not Partitioned >:/");
    }

    return 0;
}

int myAlgo(const int* A, int n) {
    // the index of the smallest possible number in the remainder of the list
    int minIdx = 0;

    // the index of the largest number we've encountered
    int maxIdx = 0;

    // index of possible partition "center"
    int kIdx = 0;

    bool isPart = false;

    for(int i=0; i < n; ++i) {
        if( A[maxIdx] <= A[i] )  {
            maxIdx = i;
            if(isPart == false)  { kIdx = i; minIdx = i;} // if we flipped then this is a good time to grab a partitioner index
            isPart = true;
        }
        else { isPart = false; minIdx = i; }
        printf("A[%d] = %d <==> A[%d]: %d : %c\n", maxIdx, A[maxIdx], i, A[i], (isPart?'T':'F'));
        if( A[minIdx] > A[i] ) { isPart = false; }
        printf("A[%d] = %d <==> A[%d]: %d : %c\n", minIdx, A[minIdx], i, A[i], (isPart?'T':'F'));
    }

    printf("A[%d] = %d : %c\n\n", kIdx, A[kIdx], (isPart?'T':'F'));

    // We gotta check this to make sure it is a valid list...
    if(isPart) return kIdx;
    else return -1;
}

但是,毫不奇怪,我的输出是这样的:


A[0] = 10 <==> A[0]: 10 : T
A[0] = 10 <==> A[0]: 10 : T
A[1] = 20 <==> A[1]: 20 : T
A[0] = 10 <==> A[1]: 20 : T
A[2] = 30 <==> A[2]: 30 : T
A[0] = 10 <==> A[2]: 30 : T
A[3] = 40 <==> A[3]: 40 : T
A[0] = 10 <==> A[3]: 40 : T
A[3] = 40 <==> A[4]: 11 : F
A[4] = 11 <==> A[4]: 11 : F
A[5] = 100 <==> A[5]: 100 : T
A[5] = 100 <==> A[5]: 100 : T
A[6] = 150 <==> A[6]: 150 : T
A[5] = 100 <==> A[6]: 150 : T
A[6] = 150 <==> A[7]: 101 : F
A[7] = 101 <==> A[7]: 101 : F
A[6] = 150 <==> A[8]: 125 : F
A[8] = 125 <==> A[8]: 125 : F
A[5] = 100 : F                 <-- The index is right... but isPart is wrong

Not Partitioned >:/

真的希望今晚能够入睡,因此非常非常感谢任何提示/提示/想法/等。


哇! @Amit 帮我解决了我的问题,这是我的更新功能:

int partIdx2(const int* A, int n) {

    int* max = malloc(n * sizeof(int));
    int* min = malloc(n * sizeof(int));

    for(int i=0; i < n; i++)
    {
        if(i==0) {
            max[i] = A[i];
            min[n - 1] = A[n-1];
        }
        else {
            max[i] = MAX(max[i-1], A[i]);
            min[n - 1 - i] = MIN(min[n - 1 - i + 1], A[n - 1 - i]);
        }
    }

    for(int i=1; i < n-1; i++) {
        if(A[i] >= max[i-1] && A[i] <= min[i+1]) { 
            free(max);
            free(min);
            return i; 
        }
    }

    free(max);
    free(min);

    return -1;
}

【问题讨论】:

  • 我真的很喜欢这个解决方案。除非需要,否则我不喜欢分配空间,而且我对这个问题的尝试也没有 (see it live with your problem sets)。不知道我是否错过了什么,但这真是令人头疼。
  • @WhozCraig:我一开始就试图这样做,但我一直在试图找到我的支点可能达到的绝对最小值时遇到了困难……使用 Amit 解决方案中的一些见解,我会可能让我的第一个想法再试一次。
  • @dalle,如果枢轴是已知的,这将很有用,但要使用is_partitioned(...) 解决这个问题(请注意,我们是 A. 检查一般分区性和 B. 返回枢轴索引)我们必须为列表中的每个元素调用它(我们的谓词是x &lt; A[pivot])......这将导致 O(n^2) 复杂度。

标签: c algorithm


【解决方案1】:

O(n) 时间 + 空间解决方案是有两个数组,maxmin

max[i] = max{arr[0],arr[1],...,arr[i]}
min[i] = min{arr[i],arr[i+1],...,arr[n-1]}

请注意,您可以使用线性时间创建两个数组。

拥有这些数组后,需要查找是否存在索引k,这样:

arr[k] >= max[k-1] && arr[k] <= min[k+1]

这也可以在线性时间内完成

这是可行的,因为如果上述条件成立,那么k 之后的每个元素都保证高于或等于arr[k],并且它之前的每个元素都低于或等于arr[k],这几乎是定义分区。

【讨论】:

  • 好吧,现在我很惊讶!这就是我的问题所在,我过早地取了最小值,我必须在列表中向后(找到最小值)和向前(找到最大值)工作,以使我的实现正常工作。谢谢!
【解决方案2】:

有趣的问题

在我看来,必须有可能解决这个问题,而不必求助于额外的缓冲区空间。

我们知道如果有一个枢轴元素,那么

  • (未知)枢轴位置左侧的所有元素都小于或等于枢轴元素
  • (未知)枢轴位置右侧的所有元素都大于或等于枢轴元素

由此我们知道

  • 枢轴左侧的所有元素都小于或等于枢轴右侧的任何元素,并且
  • 枢轴右侧的所有元素都大于或等于枢轴左侧的任何元素

这种情况的一个特殊情况是

  • 枢轴左侧的所有元素都小于或等于最右侧的元素
  • 枢轴右侧的所有元素都大于或等于最左侧的元素

使用这些基本原理,我们应该能够递归地“回到”枢轴位置(如果有的话)。

伪代码:

Set highest value found on low side to value of first element
Set lowest value found on high side to value of last element
Set low index to first element
Set high index to last element
repeat
  increment low index
  if low index >= array length -> fail
  if value at new low index > highest so far on the low side
    set new highest-on-low-side value
      if new value greater than lowest value so far on right side,
        set low index back to what it was and mark it as stuck
        set highest-on-low-side value back to what it was
  decrement high index
  if high index < 0 -> fail
  if value at new high index < lowest so far on the high side
    set new lowest-on-high-side value
      if new value less than the highest value so far on the left side,
        set high index back to what it was and mark it as stuck
        set lowest-on-high-side value back to what it was
until both low and high index is stuck or until low index >= high index
if low index = high index
  pivot position = low index
else
  failure

这是一个实际的 Pascal 实现,我用少量测试输入简要验证了这个想法,但目前我没有时间进行全面的验证。

function PivotIndex(a: array of integer): Integer;
var
  HighestValueOnLeftSide: Integer;
  LowestValueOnRightSide: Integer;
  LowIndex: Integer;
  HighIndex: Integer;
  LowStuck, HighStuck: Boolean;
begin
  HighestValueOnLeftSide := -1;
  LowestValueOnRightSide := MaxInt;
  LowIndex := -1;
  HighIndex := length(a);
  LowStuck := False;
  HighStuck := False;
  repeat
    if not LowStuck then begin
      inc(LowIndex);
      if LowIndex >= length(A) then begin
        Result := -1;
        exit;
      end;
      if A[LowIndex] > HighestValueOnLeftSide then
        if A[LowIndex] > LowestValueOnRightSide then begin
          LowStuck := True;
          dec(LowIndex);
        end else
          HighestValueOnLeftSide := A[LowIndex];
    end;
    if not HighStuck then begin
      dec(HighIndex);
      if HighIndex < 0 then begin
        Result := -1;
        exit;
      end;
      if A[HighIndex] < LowestValueOnRightSide then
        if A[HighIndex] < HighestValueOnLeftSide then begin
          HighStuck := True;
          inc(HighIndex);
        end else
          LowestValueOnRightSide := A[HighIndex];
    end;
  until LowStuck and HighStuck or (LowIndex >= HighIndex);
  if LowIndex = HighIndex then
    Result := LowIndex
  else
    Result := -1;
end;

我确信这可以变得更加优雅和高效,但是如果您发现任何直接的问题,请告诉我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-05
    • 2017-01-14
    • 2016-06-23
    • 1970-01-01
    • 1970-01-01
    • 2018-04-10
    • 2018-05-27
    • 2023-01-31
    相关资源
    最近更新 更多