Julia 中的合并排序实现

Mergesort implementation in Julia

我正在尝试在 Julia 中实现合并排序算法,但我似乎无法理解该算法所需的递归步骤。我的代码如下:

mₐ = [1, 10, 7, 4, 3, 6, 8, 2, 9]

b₁(t, z, half₁, half₂)= ((t<=length(half₁)) && (z<=length(half₂))) && (half₁[t]<half₂[z])
b₂(t, z, half₁, half₂)= ((z<=length(half₂)) && (t<=length(half₁)) ) && (half₁[t]>half₂[z])

function Merge(m₁, m₂)
    N = length(m₁) + length(m₂)
    B = zeros(N)

    i = 1
    j = 1

    for k in 1:N
        if b₁(i, j, m₁, m₂)
            B[k] =  m₁[i]
            i += 1 
        elseif b₂(i, j, m₁, m₂)
            B[k] =  m₂[j]
            j += 1
        elseif j >= length(m₂)
            B[k] =  m₁[i]
            i += 1 
        elseif i >= length(m₁)
            B[k] = m₂[j]
            j += 1
        end
    end

    return B
end


function MergeSort(M)
    if length(M) == 1
        return M
    elseif length(M) == 0
        return nothing
    end
    
    n = length(M)
    i₁ = n ÷ 2
    i₂ = n - i₁ 
    h₁ = M[1:i₁]
    h₂ = M[i₂:end]

    C = MergeSort(h₁)
    D = MergeSort(h₂)

    return Merge(C, D)
end

MergeSort(mₐ)

C变成单元素时总是会卡住,因为它returns它然后再拆分,唯一的解决办法是一旦它是单元素就让它循环。但是,这不是递归方法。

解决方案

采纳@Sundar R 的回答和建议。这是一个有效的实现

#implementation of MergeSort in julia

# merge function, it joins two ordered arrays and returning one single ordered array

function merge(m₁, m₂)
    N = length(m₁) + length(m₂)

    # create a zeros array of the same input type (int64)
    B = zeros(eltype(m₁), N)

    i = 1
    j = 1

    for k in 1:N
    
        if !checkbounds(Bool, m₁, i)
            B[k] = m₂[j]
            j += 1
        elseif !checkbounds(Bool, m₂, j)
            B[k] =  m₁[i]
            i += 1 
        elseif m₁[i]<m₂[j]
            B[k] =  m₁[i]
            i += 1 
        else
            B[k] =  m₂[j]
            j += 1
                
        end
    end

    return B
end

# merge mergesort, this function recursively orders m/2 sub array given an array M

function mergeSort(M)

    # base cases
    if length(M) == 1

        return M

    elseif length(M) == 0

        return nothing
        
    end

    # dividing array in two
    n = length(M)

    i₁ = n ÷ 2
        
    # be careful with the indexes, thank you @Sundar R
    i₂ = i₁ + 1 
    
    h₁ = M[1:i₁]

    h₂ = M[i₂:end]

    # recursively sorting the array 

    C = mergeSort(h₁)

    D = mergeSort(h₂)

    return merge(C, D)
    
end

#test the function

mₐ = [1, 10, 7, 4, 3, 6, 8, 2, 9]
b =  mergeSort(mₐ)

println(b)

问题在于用于拆分的索引,特别是 i₂n - i₁ 是数组后半部分的 元素数量 ,但不一定是后半部分开始的索引 - 因为您只需要 i₂ = i₁ + 1

使用i₂ = n - i₁,当n为2时,即当你将[1, 10]作为要排序的数组时,i₁ = n ÷ 2为1,i₂为(2 - 1) = 1 也。因此,不是将它拆分为 [1][10],而是最终将其“拆分”为 [1][1, 10],因此无限循环。

修复该问题后,由于一个小错误,Merge 中有一个 BoundsErrorelseif 条件应检查 >,而不是 >=(因为 Julia 使用基于 1 的索引,jj == length(m₂) 时仍然是一个有效索引)。

其他一些建议:

  1. zeros(N) returns 一个 Float64 数组,所以这里的结果总是一个 float 数组。我建议改为 zeros(eltype(m₁), N)
  2. 感觉 b₁b₂ 只会使代码复杂化并使其不那么清晰,我建议在那里使用一个简单的嵌套 if,一个外部的来检查索引- 查找 checkbounds,例如。 checkbounds(Bool, m₁, i) - 和一个内部的,看看哪个更大。
  3. Julia 惯例是对函数使用小写字母,因此 mergemergesort 而不是 MergeMergeSort

为了补充前面的答案,这些答案处理了您现有代码中的一些问题,这里提供了一个相对高效和直接的 Julia 实现以供参考 mergesort:

# Top-level function will allocate temporary arrays for convenience
function mergesort(A)
    S = similar(A)
    return mergesort!(copy(A), S)
end

# Efficient in-place version
# S is a temporary working (scratch) array
function mergesort!(A, S, n=length(A))
    width = 1
    swapcount = 0
    while width < n
        # A is currently full of sorted runs of length `width` (starting with width=1)
        for i = 1:2*width:n
            # Merge two sorted lists, left and right:
            # left = A[i:i+width-1], right = A[i+width:i+2*width-1]
            merge!(A, i, min(i+width, n+1), min(i+2*width, n+1), S)
        end
        # Swap the pointers of `A` and `S` such that `A` now contains merged
        # runs of length 2*width.
        S,A = A,S
        swapcount += 1

        # Double the width and continue
        width *= 2
    end
    # Optional, if it is important that `A` be sorted in-place:
    if isodd(swapcount)
        # If we've swapped A and S an odd number of times, copy `A` back to `S`
        # since `S` will by now refer to the memory initially provided as input
        # array `A`, which the user will expect to have been sorted in-place
        copyto!(S,A)
    end
    return A
end

# Merge two sorted subarrays, left and right:
# left = A[iₗ:iᵣ-1], right = A[iᵣ:iₑ-1]
@inline function merge!(A, iₗ, iᵣ, iₑ, S)
    left, right = iₗ, iᵣ
    @inbounds for n = iₗ:(iₑ-1)
        if (left < iᵣ) && (right >= iₑ || A[left] <= A[right])
            S[n] = A[left]
            left += 1
        else
            S[n] = A[right]
            right += 1
        end
    end
end

这足以使我们与 Base 实现相同算法

处于同一水平
julia> using BenchmarkTools

julia> @benchmark mergesort!(A,B) setup = (A = rand(50); B = similar(A))
BenchmarkTools.Trial: 10000 samples with 194 evaluations.
 Range (min … max):  497.062 ns …  1.294 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     501.438 ns              ┊ GC (median):    0.00%
 Time  (mean ± σ):   526.171 ns ± 49.011 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █▅ ▁     ▁ ▃▇▄                        ▁                      ▂
  █████▇▇▆▇█▇████▇▅▆▅▅▅▆█▆██▄▅▅▄▆██▆▆▄▄▆██▅▃▄██▄▅▅▃▃▃▃▄▅▁▄▄▃▁█ █
  497 ns        Histogram: log(frequency) by time       718 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> issorted(mergesort(rand(50)))
true

julia> issorted(mergesort(rand(10_000)))
true

julia> @benchmark Base.sort!(A, alg=MergeSort) setup=(A = rand(50))
BenchmarkTools.Trial: 10000 samples with 216 evaluations.
 Range (min … max):  344.690 ns …  11.294 μs  ┊ GC (min … max): 0.00% … 95.73%
 Time  (median):     352.917 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   401.700 ns ± 378.399 ns  ┊ GC (mean ± σ):  3.57% ±  3.76%

  █▇▄▄▄▂▁▂▁▂▃▁▁    ▃▂  ▁                            ▁▁          ▁
  ████████████████▇██████▆▆▆▅▆▆▆▆▅▃▅▅▄▅▃▅▅▄▆▅▄▅▄▅▃▄▄██▇▅▆▆▇▆▄▅▅ █
  345 ns        Histogram: log(frequency) by time        741 ns <

 Memory estimate: 336 bytes, allocs estimate: 3.

尽管在大多数数字情况下,两者在时间和内存(后者是由于需要工作数组)方面都比 similarly efficient pure-Julia implementation of quicksort!:

花费更多
julia> @benchmark VectorizedStatistics.quicksort!(A) setup = (A = rand(50))
BenchmarkTools.Trial: 10000 samples with 993 evaluations.
 Range (min … max):  28.854 ns … 175.821 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     35.268 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   38.703 ns ±   7.478 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▂       ▃█▁ ▃▃   ▃▆▂  ▂   ▃  ▂        ▁                    ▂ ▂
  █▆▃▅▁▁▄▅███▆███▆▆███▁▇█▇▅▇█▆▇█▁▆▅▃▅▄▄██▅▆▅▇▅▄▃▁▄▃▁▄▁▃▃▃▁▄▄▇█ █
  28.9 ns       Histogram: log(frequency) by time      68.7 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.