为什么快速排序代码会破坏稳定性?

时间:2017-01-19 00:47:03

标签: c algorithm sorting quicksort

以下是partition()使用的qSort()逻辑,

static void qSort(List *list, int low, int high, compareTo compare){

  if(high <= low){
    return; // no partition for sub array of size 1
  }
  int pivotIndex = partition(list, low, high, compare);
  qSort(list, low, pivotIndex-1, compare);
  qSort(list, pivotIndex+1, high, compare);
}

static int partition(List *list, int low, int high, compareTo compare){

  int pivot = low;
  int leftIndex = low + 1;
  int rightIndex = high;
  const void **array = list->array;

  while(true){

    while( leftIndex < high  && (compare(array[leftIndex], array[pivot]) < 0) ){
      leftIndex++;
    } 

    while( rightIndex > pivot && (compare(array[rightIndex], array[pivot])  >  0) ){
      rightIndex--;
    }

    if(leftIndex >= rightIndex){
      break;               // partition is done
    }
    if( compare(array[leftIndex], array[rightIndex]) == 0 ){
      leftIndex++; rightIndex--;
      continue;                   //Maintain stability
    }
    arraySwap(list, leftIndex, rightIndex);
  }
  if( compare(array[pivot], array[rightIndex]) != 0 ){
    arraySwap(list, pivot, rightIndex);           // Maintain stability
  }
  return rightIndex;
}

arraySwap()&amp;&amp; compare()定义为,

void arraySwap(List *list, int i, int j){

  const void **array = list->array;

  const void *tempPointer = array[i];
  array[i] = array[j];
  array[j] = tempPointer;
}

int compare(const void *key, const void *item){

  if( ((Person *)key)->age < ((Person *)item)->age  ){

    return -1;
  }else if( ((Person *)key)->age > ((Person *)item)->age  ){

    return 1;
  }else{

    return 0;
  }
}

partition()必须通过在每个arraySwap()之前进行额外检查来维持稳定性。

但是低于输出显示,稳定性得到部分维持(关键10与关键50不同是稳定的),

$ ./sort.exe
Before sorting
Age,LastName,FirstName
  50  B  A
  30  A  B
  20  X  D
  10  F  A
  50  A  B
  90  V  E
  60  N  M
  10  A  B
After sorting

Age,LastName,FirstName
  10  F  A
  10  A  B
  20  X  D
  30  A  B
  50  A  B
  50  B  A
  60  N  M
  90  V  E

partition()函数中, 下面的代码块只是为了保持稳定,

while(true){
  ....  
  if( compare(array[leftIndex], array[rightIndex]) == 0 ){
    leftIndex++; rightIndex--;
    continue;                        //Maintain stability
  }
  ....
} 
...      
if( compare(array[pivot], array[rightIndex]) != 0 ){ 
  ... 
}

问题:

为什么使用键50进行记录不稳定?

2 个答案:

答案 0 :(得分:8)

快速排序不稳定,因为分区步骤可能会交换相互比较相等的元素,因此它们的顺序与原始数组的顺序不同。

快速排序稳定需要一个比较函数,它总是会为不同的元素返回非零值。

答案 1 :(得分:4)

避免交换相等元素绝不足以实现稳定的快速排序。例如,考虑一下这个简单的案例:

key  value
2    A
3    B
3    C
1    D
1    E

以第一个元素为枢轴,第一个分区涉及三个交换:分区主要部分中的(1,4)和(2,3),然后(0,2)将枢轴放置到位。产量:

1   D
1   E
2   A
3   C
3   B

没有交换具有相同键的元素,但是具有键3的两个项的相对顺序被颠倒了。这种情况在分区的两端自然发生,因为数组的上半部分以相反的顺序遍历。

此外,当您将枢轴元素交换到位时,您有机会出现不稳定。因为它从数组的最左边开始,如果有任何其他元素具有相同的键,最终在左分区中,但是左分区的最右端的元素具有不同的键,则分区元素将相对于具有相同密钥的其他人移动。

为确保稳定排序,比较函数必须考虑原始元素顺序。这通常需要使用O(N)个额外元数据。将此解释为快速排序根本无法保持稳定是公平的,因为将原始元素顺序合并到比较函数中会有效地使所有元素不相等,因此稳定性问题没有实际意义。