找分钟。序列的“加入”操作

时间:2010-12-20 00:50:13

标签: algorithm sorting sequence

假设我们有一个列表/正整数数组x1,x2,...,xn。 我们可以对这个序列进行 join 操作,这意味着我们可以用一个元素替换两个彼此相邻的元素,这是这些元素的总和。例如:

- > array / list:[1; 2; 3; 4; 5; 6]

  • 我们可以加入 2和3,并用5替换它们;
  • 我们可以加入 5和6,并用11;
  • 替换它们
  • 我们不能 加入 2和4;
  • 我们不能 加入 1和3等。

主要问题是找到给定序列的最小连接操作,之后该序列将按递增顺序排序。

注意:空元素和单元素序列 按递增顺序排序。

基本示例:

  • for [4; 6; 5; 3; 9]解决方案是1(我们加入 5和3)

  • 表示[1; 3; 6; 5]解决方案也是1(我们加入 6和5)

我正在寻找的是一种解决此问题的算法。它可以是伪代码,C,C ++,PHP,OCaml或类似代码(我的意思是:如果您使用其中一种语言编写解决方案,我会理解解决方案)。

6 个答案:

答案 0 :(得分:7)

这是使用动态规划解决的理想问题,@ lijie所描述的重现是正确的方法,只需稍作调整即可确保考虑所有可能性。有两个关键观察结果:(a)任何连接操作序列都会导致原始向量的一组非重叠求和子序列,以及(b)对于最佳连接序列,如果我们看在任何求和子序列(m ... n)的右边,该部分是问题的最佳解决方案:“找到子矢量(n + 1)... N的最佳连接序列,以便得到对最终序列进行排序,并且所有元素都是> = sum(m ... n)。

直接实现递归当然会导致指数时间算法,但使用动态编程的简单调整使其成为O(N ^ 2),因为基本上所有(m,n)对都被认为是一次。使用动态编程实现递归的一种简单方法是使(m,n)索引的数据结构在计算后存储f(m,n)的结果,以便下次调用f(m)时,n),我们可以查找以前保存的结果。以下代码使用R编程语言执行此操作。我正在使用我们想要找到最小连接数的公式来获得 非递减序列。 对于那些刚接触R的人来测试这段代码,只需从任何镜像下载R(Google“R Project”),将其激活,并将两个函数定义(f和solve)粘贴到控制台中,然后使用“solve(c(...))”解决任何向量在下面的例子中。

f <- function(m,n) {
  name <- paste(m,n)
  nCalls <<- nCalls + 1 
  # use <<- for global assignment
  if( !is.null( Saved[[ name ]] ) ) {
    # the solution for (m,n) has been cached, look it up
    nCached <<- nCached + 1
    return( Saved[[ name ]] )
  }
  N <- length(vec) # vec is global to this function
  sum.mn <- -Inf 
  if(m >= 1)
    sum.mn <- sum( vec[m:n] )
  if(n == N) { # boundary case: the (m,n) range includes the last number
    result <- list( num = 0, joins = list(), seq = c())
  } else
  {
    bestNum <- Inf
    bestJoins <- list()
    bestSeq <- c()
    for( k in (n+1):N ) {
      sum.nk <- sum( vec[ (n+1):k ] )
      if( sum.nk < sum.mn ) next
      joinRest <- f( n+1, k )
      numJoins <- joinRest$num + k-n-1
      if( numJoins < bestNum ) {
        bestNum <- numJoins
        if( k == n+1 )
          bestJoins <- joinRest$joins else
        bestJoins <- c( list(c(n+1,k)), joinRest$joins )
        bestSeq <- c( sum.nk, joinRest$seq)
      }
    }  
    result <- list( num = bestNum, joins = bestJoins, seq = bestSeq )
  }
  Saved[[ name ]] <<- result
  result
}

solve <- function(input) {
  vec <<- input
  nCalls <<- 0
  nCached <<- 0
  Saved <<- c()
  result <- f(0,0)
  cat( 'Num calls to f = ', nCalls, ', Cached = ', nCached, '\n')
  cat( 'Min joins = ', result$num, '\n')
  cat( 'Opt summed subsequences: ')
  cat( do.call( paste, 
                lapply(result$joins, 
                       function(pair) paste(pair[1], pair[2], sep=':' ))),
       '\n')
  cat( 'Final Sequence: ', result$seq, '\n' )
}

以下是一些示例运行:

> solve(c(2,8,2,2,9,12))
Num calls to f =  22 , Cached =  4 
Min joins =  2 
Opt summed subsequences: 2:3 4:5 
Final Sequence:  2 10 11 12 

> solve(c(1,1,1,1,1))
Num calls to f =  19 , Cached =  3 
Min joins =  0 
Opt summed subsequences:  
Final Sequence:  1 1 1 1 1 

> solve(c(4,3,10,11))
Num calls to f =  10 , Cached =  0 
Min joins =  1 
Opt summed subsequences: 1:2 
Final Sequence:  7 10 11 

> solve(c (2, 8, 2, 2, 8, 3, 8, 9, 9, 2, 9, 8, 8, 7, 4, 2, 7, 5, 9, 4, 6, 7, 4, 7, 3, 4, 7, 9, 1, 2, 5, 1, 8, 7, 3, 3, 6, 3, 8, 5, 6, 5))
Num calls to f =  3982 , Cached =  3225 
Min joins =  30 
Opt summed subsequences: 2:3 4:5 6:7 8:9 10:12 13:16 17:19 20:23 24:27 28:33 34:42 
Final Sequence:  2 10 10 11 18 19 21 21 21 21 26 46 

请注意,@ kotlinski考虑的序列的最小连接数为 30,而不是32或33。

答案 1 :(得分:3)

Greedy algorithm

import Data.List (inits)

joinSequence :: (Num a, Ord a) => [a] -> Int
joinSequence (x:xs) = joinWithMin 0 x xs
  where joinWithMin k _ [] = k
        joinWithMin k x xs =
          case dropWhile ((< x) . snd) $ zip [0..] $ scanl1 (+) xs
            of (l, y):_ -> joinWithMin (k + l) y $ drop (l+1) xs
               _ -> k + length xs
joinSequence _ = 0

在每一步中,抓住更多元素,直到它们的总和不小于最后一个。如果元素用完,只需将剩下的所有元素加入到前一组中。


那是错的。

Combinatorial explosion

joinSequence :: (Num a, Ord a) => [a] -> Int
joinSequence = joinWithMin 0 0
  where joinWithMin k _ [] = k
        joinWithMin k m xs =
            case dropWhile ((< m) . snd) $ zip [0..] $ scanl1 (+) xs
              of [] -> k + length xs
                 ys -> minimum [ joinWithMin (k+l) y $ drop (l+1) xs 
                               | (l, y) <- ys ]

尝试尽可能加入并尽量减少。我想不出一个聪明的启发式来限制回溯,但这应该是O(n²)dynamic programming和O(2 n )写的。

答案 2 :(得分:1)

动态编程方法:

让原始数组为a[i], 0 <= i < N

f(m, n)定义为使a[n..N-1]排序所需的最小连接数,以便排序子列表中的所有元素都为>(或>=,如果是另一个期望变量)a[m..n-1]的总和(让空列表的总和为-inf)。

基本案例是f(m, N) = 0(子列表为空)。

递归为f(m, n) = min_{n < k <= N s.t. sum(a[n..k-1]) > sum(a[m..n-1])} f(n, k) + k-n-1。如果没有适合的k值,那么让f(m, n) = inf(任何>= N也可以使用,因为最多只有N-1个连接。

f(m,n)m的降序计算n

然后,所需答案为f(0,0)

修改

哎呀这基本上是第二个答案,我相信,虽然我对Haskell不太熟悉,不知道它到底在做什么。

答案 3 :(得分:0)

一些Haskell代码:

sortJoin (a:b:c:xs)
    | a <= b    = a : sortJoin (b:c:xs)  
    | a+b <= c  = a+b : sortJoin (c:xs)  
    | otherwise = sortJoin (a:b+c:xs)    
sortJoin (a:b:[]) = if a <= b then [a,b] else [a+b]
sortJoin a@_ = a

edits xs = length xs - length (sortJoin xs)

更新:使用test = [2,8,2,2,8,3,8,9,9,2,9,8,8,7,4,2,7,5,9, 4,6,7,4,7,3,4,7,9,1,2,5,1,8,7,3,3,6,3,8,5,6,5]

...现在我们得到:

> sortJoin test
[2,8,12,20,20,23,27,28,31,55]
> edits test
32

答案 4 :(得分:0)

希望保持简单。这是一些指数时间的伪代码。

Function "join" (list, max-join-count, join-count) ->
    Fail if join-count is greater than max-join-count.
    If the list looks sorted return join-count.
    For Each number In List
        Recur (list with current and next number joined, max-join-count, join-count + 1)

Function "best-join" (list) ->
    max-join-count = 0
    while not join (list, max-join-count++)

这是Clojure的一个实现:

(defn join-ahead [f i v]
  (concat (take i v)
          [(f (nth v i) (nth v (inc i)))]
          (drop (+ 2 i) v)))

(defn sort-by-joining
  "Sort a list by joining neighboring elements with `+'"
  ([v max-join-count join-count]
     (if (or (nil? max-join-count)
             (<= join-count max-join-count))
       (if (or (empty? v)
               (= v (sort v)))
         {:vector v :join-count join-count}
         (loop [i 0]
           (when (< (inc i) (count v))
             (let [r (sort-by-joining (join-ahead + i v)
                                      max-join-count
                                      (inc join-count))]
               (or r (recur (inc i)))))))))
  ([v max-join-count]
     (sort-by-joining v max-join-count 0))
  ([v]
     (sort-by-joining v nil 0)))

(defn fewest-joins [v]
  (loop [i 0]
    (if (sort-by-joining v i)
      i
      (recur (inc i)))))

(deftest test-fewest-joins
  (is (= 0 (fewest-joins nil)))
  (is (= 1 (fewest-joins [4 6 5 3 9])))
  (is (= 6 (fewest-joins [1 9 22 90 1 1 1 32 78 13 1]))))

答案 5 :(得分:0)

这是F#中的pchalasani代码,有一些修改。 memoization类似,我在O(1)时间内为sums添加了sumRange函数生成器,并将起始位置移动到f 1 0以跳过在minJoins中检查n = 0。

let minJoins (input: int array) =
    let length = input.Length
    let sum = sumRange input

    let rec f = memoize2 (fun m n ->
        if n = length then
            0
        else
            let sum_mn = sum m n 

            {n + 1 .. length}
            |> Seq.filter (fun k -> sum (n + 1) k >= sum_mn)
            |> Seq.map (fun k -> f (n + 1) k + k-n-1)
            |> Seq.append {length .. length}
            |> Seq.min
        )
    f 1 0

完整代码。

open System.Collections.Generic

// standard memoization
let memoize2 f = 
    let cache = new Dictionary<_, _>()
    (fun x1 x2 -> 
        match cache.TryGetValue((x1, x2)) with
        | true, y -> y
        | _ -> 
            let v = f x1 x2
            cache.Add((x1, x2), v)
            v)

// returns a function that takes two integers n,m and returns sum(array[n:m])
let sumRange (array : int array) =
    let forward = Array.create (array.Length + 1) 0

    let mutable total = 0
    for i in 0 .. array.Length - 1 do
        total <- total + array.[i]
        forward.[i + 1] <- total

    (fun i j -> forward.[j] - forward.[i - 1])

// min joins to sort an array ascending
let minJoins (input: int array) =
    let length = input.Length
    let sum = sumRange input

    let rec f = memoize2 (fun m n ->
        if n = length then
            0
        else
            let sum_mn = sum m n 

            {n + 1 .. length}
            |> Seq.filter (fun k -> sum (n + 1) k >= sum_mn)
            |> Seq.map (fun k -> f (n + 1) k + k-n-1)
            |> Seq.append {length .. length} // if nothing passed the filter return length as the min
            |> Seq.min
        )
    f 1 0

let input = [|2;8;2;2;8;3;8;9;9;2;9;8;8;7;4;2;7;5;9;4;6;7;4;7;3;4;7;9;1;2;5;1;8;7;3;3;6;3;8;5;6;5|]
let output = minJoins input
printfn "%A" output
// outputs 30