为什么我的插入排序比mergesort更快?

时间:2013-03-08 00:55:41

标签: ruby algorithm sorting

# sort.rb
class Array
  def insertion
    (1..self.count).each do |i|
      (i..0).each do |j|
        first = j - 1
        second = j
        if self[second] > self[first]
          swap(second, first)
        end
      end
    end
    self
  end

  def mergesort
    return self if self.size <= 1
    mid = self.size / 2
    left = self[0, mid]
    right = self[mid, self.size-mid]
    merge_array(left.mergesort, right.mergesort)
  end

  # helpers

  def merge_array(left, right)
    sorted = []
    until left.empty? or right.empty?
      if left.first <= right.first
        sorted << left.shift
      else
        sorted << right.shift
      end
    end
    sorted.concat(left).concat(right)
  end

  def swap(previous, current)
    copy = self[previous]
    self[previous] = self[current]
    self[current] = copy
  end
end

我的rspec文件:

require './sort'

unsorted = 5000.downto(1).to_a.shuffle

describe Array, "#insertion" do
  it "sorts using insertion sort" do
    time = Time.now
    unsorted.insertion.should eq(unsorted.sort)
    puts "insertion"
    puts Time.now - time
  end
end

describe Array, "#merge" do
  it "sorts using merge sort" do
    time = Time.now
    unsorted.mergesort.should eq(unsorted.sort)
    puts "merge"
    puts Time.now - time
  end
end

我们知道插入排序应该比合并排序慢,因为插入排序的运行时平均为O(n ^ 2),而合并排序为O(n * log(n))。但是,当我运行上面的测试代码时,merge比插入慢10倍。

insertion
0.001294 seconds

.merge
0.017322 seconds

我的猜测是我使用了一些计算成本较高的方法,例如shiftconcat,但相差10倍太过分了。

如何改进合并排序?

2 个答案:

答案 0 :(得分:8)

这里有很多东西:

  1. 这不是插入排序,而是冒泡排序。
  2. (i..0).each什么都不做,因为范围不能以相反的顺序排列(你的规范没有通过)。请改用downto
  3. 逻辑本身是错误的,如果你的最后一个索引位于字符串的开头,那么你想在第二个元素小于第一个元素时进行交换。
  4. 您的规范使用相同的数组,但插入方法会改变数组,因此当它到达合并排序时,它已经被排序了。
  5. 没有充分的理由把它们放在Array上(超出它的新颖性),一般来说,猴子补丁是一种不好的做法(我会解释原因,但它超出了这个响应的范围)
  6. 可以使用多个赋值self[a], self[b] = self[b], self[a]简化交换方法。
  7. 名称firstsecondpreviousnext都令人困惑。他们的名字暗示他们是元素,但实际上他们是索引,我会重命名为index1(可能是first_index,但这可能会变得冗长。)
  8. 为什么要在countsize之间切换?这令人困惑,让它看起来像你复制并粘贴了其他人的代码(事实上,其中一个函数改变了数组而另一个函数没有,一般来说,合并排序看起来像是由知道的人编写的他们正在做什么,“插入”排序没有。)
  9. 合并排序可能更慢,因为它创建了大量不需要的数组(它没有副作用,但从性能的角度来看,最好只是dup数组然后根据开始和结束索引对其进行排序。
  10. 测试不是很有用,因为它们只对一个数组进行排序。假设数组已经大部分已经排序,那么冒泡排序必须执行很少的交换,所以它只是在数组上迭代了很多次就完成了。
  11. 这些调用之间可能存在环境差异(优化,垃圾回收状态),最好使用Benchmark库。它有bmbm试图最小化这些差异。
  12. 因为你正在计时器unsorted.insertion.should eq(unsorted.sort)内运行测试,所以你不仅要计算你的排序,你还要计算Ruby的unsorted.sort以及RSpec断言。最好将排序包装在计时代码中,然后输出结果。

答案 1 :(得分:4)

思路:

  • 尝试将您的测试大小提升到成千上万的数量,并在几个不同的数组上进行平均,因为与合并排序相比,插入排序在最佳情况下会非常快
  • 在合并中预先分配数组,而不是动态构建数组,因为您应该知道大小
  • 从匹配时间
  • 中取出正确性(......应该)