qsort是否要求进行一致的比较,还是可以将其用于改组?

时间:2009-04-26 01:32:53

标签: c sorting random

更新:请在糟糕的想法下提交。生活中没有任何免费的东西,这肯定是证据。一个简单的想法变坏了。但这绝对是值得学习的东西。

懒惰的编程挑战。如果我传递一个50-50为qsort的比较函数返回true或false的函数,我认为我可以有效地取消编写3行代码的结构数组。

int main ( int argc, char **argv)
{
    srand( time(NULL) );  /* 1 */
    ...
    /* qsort(....) */     /* 2 */
}

...

int comp_nums(const int *num1, const int *num2)
{
    float frand = 
          (float) (rand()) / ((float) (RAND_MAX+1.0));  /* 3 */

    if (frand >= 0.5f)
         return GREATER_THAN;
    return LESS_THAN;
}

我需要寻找的任何陷阱?是否可以通过交换更少的线路,或者这对于3条非平凡的线路来说是最干净的?

6 个答案:

答案 0 :(得分:13)

糟糕的主意。我的意思是非常糟糕。

您的解决方案会产生不可预测的结果,而不是随机结果,并且存在差异。你不知道随机比较的qsort会做什么,以及所有组合是否同样可能。这是洗牌的最重要标准:所有组合必须具有相同的可能性。有偏见的结果等同于大麻烦。在你的例子中没有办法证明这一点。

你应该实现Fisher-Yates shuffle(也称为Knuth shuffle)。

答案 1 :(得分:6)

除了其他答案之外,这比简单的Fisher-Yates shuffle更糟糕,因为它太慢了。 qsort算法是O(n * log(n)),Fisher-Yates是O(n)。

维基百科提供了更多细节,说明为什么这种“洗牌”通常不如the Fisher-Yates method那样有效:

  

与其他改组比较   算法

     

Fisher-Yates洗牌是相当的   高效;的确,它的渐近时间   和空间复杂性是最佳的。   结合高品质无偏见   随机数源,它也是   保证生产无偏见   结果。相比其他一些   解决方案,它也有优势   如果只是结果的一部分   需要排列,它可以   中途停止,甚至停止   停止并重复重启,   产生排列   根据需要逐步增加。在高层   快速编程语言   内置排序算法,   替代方法,每个元素   分配要被洗牌的集合   然后是一个随机数   根据这些数字排序,可以   在实践中更快[引用   虽然情况更糟,但需要]   渐近时间复杂度(O(n log n)   与O(n))。像费雪耶茨一样   洗牌,这种方法也会产生   如果正确的话,无偏见的结果   实施,可能更宽容   随机的某些偏差   数字。但是,必须小心   确保分配随机   数字永远不会重复,因为   排序算法一般不会   在a的情况下随机排序元素   领带。上述方法的变体   已经在语言中使用了一些   支持排序   用户指定的比较函数是   通过使用a对其进行排序来对列表进行洗牌   返回的比较函数   随机值。但是,这不是   总是工作:有一些常见的   使用排序算法,结果   最终由于内部偏见   排序中的不对称   实现。[7]

此链接指向here

  写这篇文章时,还有一件事   文章我尝试了各种各样的   方法的版本和发现   原版中还有一个缺陷   (由我重命名为shuffle_sort)。我曾是   当我说“它返回一个很好的时候错了   每次都是洗牌阵列   称为“。

     

结果并没有很好地改变   所有。他们有偏见。厉害。那   意味着一些排列(即   元素的排序更有可能   相对于其它的。这是另一个片段   代码来证明它,再次借用   新闻组讨论:

N = 100000
A = %w(a b c)
Score = Hash.new { |h, k| h[k] = 0 }
N.times do
  sorted = A.shuffle  
  Score[sorted.join("")] += 1
end

Score.keys.sort.each do |key|
  puts "#{key}: #{Score[key]}"
end
  

此代码   洗牌10万次,三次   元素:a,b,c和记录多少   每次可能的结果都是   实现。在这种情况下,只有   六种可能的排序,我们应该   得到每个约16666.66次。如果   我们尝试了一个无偏见的shuffle版本   (shuffleshuffle_sort_by),.   结果如预期:

 
 abc: 16517
 acb: 16893
 bac: 16584
 bca: 16568
 cab: 16476
 cba: 16962
  

当然,   有一些偏差,但他们   不应超过百分之几   期望值,他们应该   每次运行此代码时都不同。   我们可以说分布是   甚至。

     

好的,如果我们使用,会发生什么   shuffle_sort方法?

 abc: 44278 
 acb: 7462
 bac: 7538
 bca: 3710
 cab: 3698
 cba: 33314
  

事实并非如此   一个均匀的分布。再次?

它显示了排序方法的偏差,并详细说明了为什么会这样。最后,他链接到Coding Horror

  

让我们来看看正确的   Knuth-Fisher-Yates洗牌算法。

for (int i = cards.Length - 1; i > 0; i--)
{
  int n = rand.Next(i + 1);
  Swap(ref cards[i], ref cards[n]);
}
  

你看到了区别吗?我错过了   这是第一次。比较互换   对于3张卡片组:

 
Naïve shuffle   Knuth-Fisher-Yates shuffle
rand.Next(3);    rand.Next(3);
rand.Next(3);    rand.Next(2);
rand.Next(3); 
  

天真的洗牌   结果在3 ^ 3(27)可能的牌组   组合。这很奇怪,因为   数学告诉我们有   真的只有3个!或者6种可能   3张牌组合。在里面   KFY shuffle,我们从一个初始开始   订单,从第三个位置交换   与三张牌中的任何一张,然后交换   再次从第二个位置开始   剩下的两张牌。

答案 2 :(得分:2)

不,这不会正确地改组数组,它​​几乎不会在原始位置周围移动元素,而是以指数分布。

答案 3 :(得分:1)

比较函数不应该返回一个布尔类型,它应该返回一个负数,一个正数或零,qsort()用它来确定哪个参数大于另一个。

答案 4 :(得分:1)

The Old New Thing接受了这个

我认为在下行过程中递归地随机分区集合并在上行路径上连接结果的基本思路是可行的(它将平均O(n * log n)二进制决策并且接近log2(事实上) (n))但q-sort不一定会使用随机谓词。

BTW我认为对于任何O(n * log n)排序策略都可以说同样的论点和问题。

答案 5 :(得分:0)

兰德并不是最随机的东西......如果你想洗牌或其他东西,这不是最好的。另外一个Knuth shuffle会更快,但如果它不能永远循环你的解决方案是好的