簡體   English   中英

如何優化遞歸球拍函數的運行時間以確定列表中元素的最大值?

[英]How to optimize runtime on recursive Racket function to determine maximum of element in list?

這是我出色且有效的 LISP 球拍“以 lambda 為中介”風格的遞歸函數,用於確定列表中具有最高符號值的符號。

(define maximum
  (lambda [x]
    (cond
      [(empty? x) 0]
      [(cons? x)
           (cond
             [(>= (first x) (maximum (rest x))) (first x)]
             [else (maximum (rest x))]
           )
      ]
    )
  )
)

(check-expect (maximum '(1 2 3)) 3)
(check-expect (maximum '(1)) 1)
(check-expect (maximum '(0)) 0)

如何檢查和優化運行時?

運行時遞歸與迭代有什么不同嗎?

謝謝您的回答!

親切的問候,

有一件主要的事情可以極大地提高性能,將其從指數時間變為線性時間。

不要重新計算遞歸,將其保存為中間結果。

在內部cond表達式中, (maximum (rest x))計算兩次。 一次是第一個分支的問題,一次是第二個分支的答案。

(cond
  [(>= (first x) (maximum (rest x))) (first x)]
  [else (maximum (rest x))])

在第一個問題為假的常見情況下, (maximum (rest x))將被重新計算,使其必須做的工作加倍。 更糟糕的是,在最壞的情況下,當最大值結束時,這種加倍可能會發生在每個遞歸級別。 這就是使它呈指數增長的原因。

要解決此問題,您可以使用local來定義和命名中間結果。

(local [(define maxrst (maximum (rest x)))]
  (cond
    [(>= (first x) maxrst) (first x)]
    [else maxrst]))

這將輸入長度的 big-O 復雜度從指數變為線性。

還有其他潛在的優化,例如利用尾調用,但這些不如保存中間結果以避免重新計算遞歸那么重要。

如何設計程序 2e 圖 100:使用本地定義提高性能中也描述了這種使用local定義提高性能的方法

您可以使用time-apply來測量運行時間。 這是一個過程,它將調用具有大列表的給定函數並返回time-apply所做的結果:

(define (time-on-list f size #:initial-element (initial-element 0)
                      #:trials (trials 10)
                      #:verbose (verbose #f)
                      #:gc-times (gc-times '()))
  (define pre-gc (if (memv 'pre gc-times) #t #f))
  (define post-gc (if (memv 'post gc-times) #t #f))
  (when verbose
    (printf "trials  ~A
pre-gc  ~A (not counted in runtime)
post-gc ~A (counted-in-runtime)~%"
                        trials
                        pre-gc
                        post-gc))
  ;; Intentionally construct a nasty list
  (define ll (list (for/list ([i (in-range size)]) i)))
  (define start (current-milliseconds))
  (when (and post-gc (not pre-gc))
    (collect-garbage 'major))
  (let loop ([trial 0] [cpu 0] [real 0] [gc 0])
    (if (= trial trials)
        (values (/ cpu trials 1.0) (/ real trials 1.0) (/ gc trials 1.0))
        (begin
          (when pre-gc
            (collect-garbage 'major))
          (when verbose
            (printf "  trial ~A at ~Ams~%" (+ trial 1) (- (current-milliseconds)
                                                        start)))
          (let-values ([(result c r g)
                        (time-apply (if post-gc
                                        (λ (l)
                                          (begin0
                                            (f l)
                                            (collect-garbage 'major)))
                                        f)
                                    ll)])
            (loop (+ trial 1) (+ cpu c) (+ real r) (+ gc g)))))))

您可以將其與不同的size值一起使用,以了解性能。 默認情況下,它平均超過 10 次試驗,但可以調整。 您還可以在過程中的各個點要求 GC,但您可能不應該這樣做。 這是基於我用來測試事物性能的過程:它不是特別完成的代碼。

您幾乎肯定不想在函數的大大小值上運行它:請參閱其他答案。 特別是,以下是您的函數長度最長為 25 的列表的時間:

(0 0 0 0 0 0 0 0 0 0.1 0.1 0.2 0.4 0.9 1.9 3.5 
 6.7 13.6 29.7 54.3 109.8 219.7 436.6 958.1 2101.4)

這應該讓你相信有些事情是非常錯誤的!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM