演算法基礎–貪心策略

kirito_song發表於2018-12-12
演算法基礎–貪心策略
本文主要作為自己的學習筆記,並不具備過多的指導意義。

概述

貪心演算法通常用來求解最優問題

  1. 由區域性最優解到整體最優解

    通過不斷對區域性最優進行操作,最終達到整體最優

  2. 無後效性

    後序操作,不會出現資料狀態的回滾

  3. 和DP(動態規劃)之間的聯絡

    很多貪心問題可以通過DP進行求解


最優裝載問題

  1. 給出N個物體,第一個物體重量為Mi
  2. 儘量選擇最多的物品,總重不超過C

先將物品按照質量排序,然後依次放入每個物品,直到總重量將超過C位置。

這裡依次將剩餘物品中質量最小的物品放入的過程,就是貪心的過程。


合併果子

一類總過程代價,取決於子過程代價的問題

  1. 有N堆果子,沒堆果子的數量為Ai,每次可以將兩堆果子合併,每次合併將消耗兩堆果子總數的體力。
  2. 求最小消耗的體力
  3. 1<
    N<
    10000

首先,如果我們什麼都不管直接兩兩合併:總計消耗48點體力

演算法基礎–貪心策略

然後,我們嘗試排序後兩兩合併:總計消耗44點體力

演算法基礎–貪心策略

最後,我們嘗試只將當前所有資料中最小的兩個進行合併:總計消耗38點體力

演算法基礎–貪心策略

解法

構建一個小根堆,每次從堆頂推出兩個元素合併。並且將合併都的元素追加進小根堆中即可。

具體證明的過程有一定難度,可以參考哈夫曼編碼證明的過程。

以上的操作過程,也就是貪心的過程。他只保證單次合併所消耗的體力最優,而不在意其他的資料該如何合併。

堆結構往往用來解決貪心的問題。因為貪心問題往往需要一個明確的指標,最大值或者最小值。


專案利潤

輸入:

cost[]:每個專案的花費

profits[]:每個專案的利潤(純利潤)

k:最多能做k個專案

w:表示初始資金

輸出:

最後可以獲得的最大錢數

說明:一次只能做一個專案,且做完一個之後馬上就能獲得收益,可以支援做下一個專案

  1. costprofits中的元素依次合併成一個新的節點node:
public class Node { 
public var c :Int //專案花費 public var p :Int //專案利潤 public init(cost:Int,profit:Int) {
self.c = cost self.p = profit
}
}複製程式碼
  1. 準備一個以專案花費構建的小根堆

將所有node依次放入

  1. 準備一個以專案利潤構建的大根堆

貪心過程:

  1. 從小根堆中依次彈出堆頂元素,直到node.c>
    w
    (專案所需資金大於當前資金)

    具體程式碼上,將小根堆陣列removeFirst,然後將arr[0]與arr[arr.count-1]位置交換。讓小根堆對arr[0]位置元素向下調整即可。

  2. 將小根堆中彈出的元素放入大根堆中(大根堆中即為當前可執行的專案)

    具體程式碼上,將元素追加進大根堆陣列末尾,並進行調整即可。

  3. 從大根堆中彈出堆頂元素,並將w += node.p(執行收益最大的專案,並且更新當前資金)

    具體程式碼上與第一步類似

該貪心過程總計執行k次,每一次執行都只需要關心小根堆中最小值,與大根堆中最大值即可。最後的w即為最大總資產。


會議安排

在優先的時間內安排數量最多的會議

做一張圖可以直觀表示過程:

我們將藍色表示為待安排紅色表示為已安排黑色表示為不可安排

我們可以嘗試幾種不同的貪心策略

  1. 每次選擇持續時間最短的安排
演算法基礎–貪心策略

顯然不可行

  1. 每次選擇開始時間最早的
演算法基礎–貪心策略

顯然也不可行

  1. 每次選擇開始時間最早的並且持續時間最短的來安排
演算法基礎–貪心策略
演算法基礎–貪心策略

由此可見該方案是可以行的

程式碼也很簡單,只需要關心當前有效資料內開始時間晚於當前會議結束時間結束時間最早的一個資料即可。

func bestArrange(programs:[Program]) ->
Int {
program.sort("end")//根據program.end進行排序 var res = 0 var current = 0 for p in programs {
if p.current >
current {
//開始時間晚於當前時間,否則作廢 res += 1 current = p.end //開會,當前時間變成會議結束時間
}
} return res
}複製程式碼

貪心策略的證明

貪心策略的數學證明通常很複雜,有能力可以去翻閱

這裡推薦一種很方便的方式,對數器。

通過小樣本大樣本量的測試,證明貪心策略的正確性。

以排序演算法的證明舉例

var checkOK = truefor i in 0..<
10000 {
var arr1 = generateRandomArray(size: 5, value: 20) //獲取一個長度為10,最大值為20的隨機數陣列 var arr2 = arr1 //陣列在swift裡屬於值型別,賦值動作會自動copy let originalArr = arr1 arr1.sort()//一定正確的演算法 radixSort(arr: &
arr2, maxDigit: 2) if arr1 != arr2 {
checkOK = false print(originalArr) print(arr2) break
}
}print(checkOK ? "比對成功":"比對失敗")複製程式碼

對於貪心問題,可能不一定存在一個一定正確的演算法。那麼我們完全可以不去比對結果是否一致,只要貪心策略的結果永遠優於預設順序得出的結果即可。

關於對數器的介紹可以參閱另一篇


參考資料

貪心演算法

貪心演算法3: 會議安排

左神牛課網演算法課

來源:https://juejin.im/post/5c0f7906f265da614f702790

相關文章