合并排序算法效率

时间:2017-02-16 05:04:43

标签: swift algorithm sorting mergesort

我目前正在参加在线算法课程,其中教师不提供代码来解决算法,而是粗略的伪代码。所以在上网之前,我决定自己去尝试一下。

在这种情况下,我们正在研究的算法是合并排序算法。在给出伪代码之后,我们还研究了针对数组中n个项目的运行时间算法。经过快速分析后,教师到达6nlog(base2)(n) + 6n作为算法的近似运行时间。

给出的伪代码仅用于算法的合并部分,并给出如下:

C = output [length = n]
A = 1st sorted array [n/2] 
B = 2nd sorted array [n/2] 
i = 1
j = 1
for k = 1 to n
    if A(i) < B(j)
        C(k) = A(i)
        i++
    else [B(j) < A(i)]
        C(k) = B(j) 
        j++
    end
end 

他基本上对上述情况进行了细分,4n+2 2为声明ij4为执行的操作数量 - forif,数组位置分配和迭代)。为了上课的缘故,他将这简化为6n 这对我来说都很有意义,我的问题来自我正在执行的实现以及它如何影响算法以及它可能添加的一些权衡/低效率。

下面是我在swift中使用游乐场的代码:

func mergeSort<T:Comparable>(_ array:[T]) -> [T] {
    guard array.count > 1 else { return array }

    let lowerHalfArray = array[0..<array.count / 2]
    let upperHalfArray = array[array.count / 2..<array.count]

    let lowerSortedArray = mergeSort(array: Array(lowerHalfArray))
    let upperSortedArray = mergeSort(array: Array(upperHalfArray))

    return merge(lhs:lowerSortedArray, rhs:upperSortedArray)
}

func merge<T:Comparable>(lhs:[T], rhs:[T]) -> [T] {
    guard lhs.count > 0 else { return rhs }
    guard rhs.count > 0 else { return lhs }

    var i = 0
    var j = 0

    var mergedArray = [T]()
    let loopCount = (lhs.count + rhs.count)
    for _ in 0..<loopCount {
        if j == rhs.count || (i < lhs.count && lhs[i] < rhs[j]) {
            mergedArray.append(lhs[i])
            i += 1
        } else {
            mergedArray.append(rhs[j])
            j += 1
        }
    }

    return mergedArray
}

let values = [5,4,8,7,6,3,1,2,9]
let sortedValues = mergeSort(values)

我对此的疑问如下:

  1. guard函数开头的merge<T:Comparable>语句实际上是否使效率更低?考虑到我们总是将数组减半,它唯一适用的时间是基本情况,当数组中有奇数项时。
    这对我来说似乎实际上会增加更多的处理并给出最小的回报,因为它发生的时间是我们将数组减半到一个没有项目的点。

  2. 关于合并中的if语句。由于它检查多个条件,这是否会影响我编写的算法的整体效率?如果是这样的话,对我的影响似乎是根据它何时会突破if陈述而变化(例如在第一个条件或第二个条件下)。
    在分析算法时,这是否被认为是重要的事情,如果是这样,当它从算法中突破时,您如何解释方差?

  3. 您可以向我提供有关我所写内容的任何其他分析/提示。非常感谢。

1 个答案:

答案 0 :(得分:3)

你很快就会了解Big-O和Big-Theta你不关心确切的运行时间(相信我很快就会说非常,比如在一两个讲座中) 。在此之前,您需要了解这一点:

  1. 是的,守卫需要一些时间,但每次迭代的时间都相同。因此,如果每次迭代在没有后卫的情况下花费X时间并且您执行n函数调用,那么总共花费X*n个时间。现在添加在每次通话中花费Y时间的guars。您现在总共需要(X+Y)*n个时间。这是一个常数因素,当n变得非常大时,与(X+Y)因子相比,n因子变得可以忽略不计。也就是说,如果您可以将函数X*n缩减为(X+Y)*(log n),那么添加Y工作量是值得的,因为您总共执行的迭代次数较少。

  2. 同样的理由适用于您的第二个问题。是的,检查&#34;如果X或Y&#34;花费更多时间而不是检查&#34;如果X&#34;但这是一个不变的因素。额外时间不会随n的大小而变化。

    在某些语言中,如果第一个条件失败,则只检查第二个条件。我们如何解释这一点?最简单的解决方案是认识到比较次数的上限为3,而迭代次数可能为数百万次n。但是3是一个常数,因此每次迭代最多可以增加一定量的工作量。你可以深入研究细节,并尝试推断出第一,第二和第三种情况的真实或错误的分布,但通常你并不想走这条路。假装你总是进行所有的比较。

  3. 所以是的,如果您执行与之前相同的迭代次数,添加防护可能对您的运行时有害。但有时在每次迭代中添加额外的工作可以减少所需的迭代次数。