『壹』 [演算法] 貪心演算法證明思路
動態規劃和貪心演算法都需要問題具有最優子結構,但不同的是貪心 自頂向下 ,先做選擇再求解一個結果子問題,而動態規劃自底向上求解子問題,需要先求出子問題的最優解再做選擇。這是因為,動態規劃最優解有兩個子問題時,求解子問題 時有j-i-1種選擇,但貪心選擇特徵能夠使 其中一個子問題必定為空 ,這種子問題和選擇數目的減少使得子問題能夠自頂向下被解決。
a) 定義子問題空間,做出一個選擇從而產生一個或多個子問題。子問題空間的定義結合需要求解的目標和選擇後子問題的描述刻畫來考慮。
b) 利用「剪切-粘貼」證明作為最優解的組成部分的每個子問題的解也是它本身的最優解。如果子問題的解不是最優解,將其替換為對應的最優解從而一定能得到原問題一個更優的解,這與最初的解是原問題的最優解的前提假設矛盾,因此最優子結構得證。
貪心的本質是局部最優解能產生全局最優解,即產生兩個子問題 和 時,可以直接解決子問題 (在子問題 中,使用貪心策略選擇a作為局部最優解)然後對子問題 進行分解,最終可以合並為全局最優解。
因此,要證明貪心選擇性質只需要證明 存在一個最優解是通過當前貪心選擇策略得到的 ,反過來,即證明**通過非貪心策略得到的原問題的最優解中也一定包含局部最優解a。
定義通過非貪心策略的選擇可以得到的一個最優解A,將最優解中的元素和當前貪心策略會選擇的元素逐個交換後得到的解A'並不比
A差(假設貪心策略會選擇的元素在當前最優解中未被選擇,通過「剪切-粘貼」證明得到的仍是最優解),可以證明存在原問題的最優解可以通過貪心選擇得到。
『貳』 貪心思想
在學習數據結構的時候,我們已經見過了貪心思想在Prim和Kruskal中的完美應用,貪心思想因為其的簡潔在演算法中經常會被用到,有的時候在生活中,我們也會無意中使用到l貪心演算法。比如在去shopping時,經常需要進行找零錢的過程,我們總是不自覺的先把大的找出來。
那麼什麼是貪心思想?
貪心演算法總是作出在當前看來最好的選擇,也就是說貪心演算法並不從整體最優考慮,它所作出的選擇只是在某種意義上的局部最優選擇。
只有在滿足最優子結構的情況下貪心演算法得到的結果才是最優結果。
比如找錢的問題,我要給你一百,那麼我盡可能每一次給你最多的。
或者比如磁碟的最優存儲問題,所謂貪心選擇性質是指所求問題的整體最優解可以通過一系列局部最優的選擇,即貪心選擇來達到。
Prim和kruskal演算法都是每次選擇最小的邊納入生成樹。
所謂貪心選擇性質是指所求問題的整體最優解可以通過一系列局部最優的選擇,即貪心選擇來達到。這也是貪心問題和動態規劃問題的主要區別。
在n行m列的正整數矩陣中,要求從每一行中選一個數,使得選出的n個數的和最大。
可運用貪心策略,選n次,每一次選相應行中的最大值即可。
但是,在一個n*m的方格陣中,每一格子賦予一個數,規定每次移動時只能向上或向右,現試找出一條路徑,使其從左下角至右上角所經過的權值之和最大。
同樣考慮貪心策略,從左下角向右上角移動,每次移動選擇權值較大的一個方向。
以2*3矩陣為例,採用貪心的策略得到的是1,3,4,6和為14但是實際的最優結果為1,2,100,6和為109.
所以說貪心演算法並不是總是可行,證明當前問題存在貪心選擇性質(全局最優解可以通過局部最優貪心選擇達到)和最優子結構性質(問題的最優解包含了其子問題的最優解)。所以貪心問題如果當前的選擇不會干擾之後的選擇,則不會出現問題。
其他的情況就需要進行證明,證明的最好辦法就是將最小子問題進行一步步的合並,直到最後還原為最後的原問題,若所得到的解是總體最優的則可以使用貪心思想,否則不可以。
比如上面的問題,我們的走一步的最優解為1,3,然後我們判斷一次走兩步的最優解是否任然為1,3這個路徑,答案顯然不是,變為 1,2,100這個路徑,所以顯然不能使用貪心思想。
假設1元、2元、5元、10元、20元、50元、100元的紙幣分別有c0, c1, c2, c3, c4, c5, c6張。現在要用這些錢來支付K元,至少要用多少張紙幣?用貪心演算法的思想,很顯然,每一步盡可能用面值大的紙幣即可。在日常生活中我們自然而然也是這么做的。
有n個需要在同一天使用同一個教室的活動a1,a2,…,an,教室同一時刻只能由一個活動使用。每個活動ai都有一個開始時間si和結束時間fi 。一旦被選擇後,活動ai就占據半開時間區間[si,fi)。如果[si,fi]和[sj,fj]互不重疊,ai和aj兩個活動就可以被安排在這一天。該問題就是要安排這些活動使得盡量多的活動能不沖突的舉行。
部分背包問題, 有n個物體,第i個物體重量為wi,價值為vi,在總重量不超過C的情況下,讓總價值盡可能的高。每個物體都可以只取一部分。
我們可以考慮重量和價值的比值作為單價。
『叄』 什麼是貪婪演算法
是貪心演算法吧……
就是每次都取最優值。。。比如合並果子:
有n堆果子,每個果子都有一個重量,每次可以任意選擇2堆果子將其合並成一堆,花費是這兩堆果子的重量值之和,求最終合並成一堆的最小(最大)花費。
演算法就是,每次取重量最小(最大)的兩堆果子合並,直到還剩一堆。
『肆』 哈夫曼編碼(貪心演算法)
參考: 哈夫曼編碼
哈夫曼編碼是一種十分有效的編碼方法,廣泛應用於 數據壓縮 中
通過採用 不等長 的編碼方式,根據 字元頻率的不同 ,選擇 不同長度的編碼 ,對頻率 越高 的字元採用 越短 的編碼實現數據的高度壓縮。
這種對頻率越高的字元採用越短的編碼來編碼的方式應用的就是貪心演算法的思想。
下面看一個例子:
假如我們有一個包含1000個字元的文件,每個字元佔1個byte(1byte=8bits),則存儲這100個字元一共需要8000bits。這還是有一些大的
那我們統計一下這1000個字元中總共有多少種字元,原來需要8bit來表示一個字元,如果使用更少的位數來表示這些字元,則可以減少存儲空間。
假設這1000個字元中總共有a、b、c、d、e、f共6種字元,使用使用3個二進制位來表示的話,存儲這1000個字元就只需要3000bits,比原來更節省存儲空間。
或許還可以再壓縮一下:
根據字元出現的 頻率 給與字元 不等長 的編碼,頻率越高的字元編碼越短,頻率越低的字元編碼越長。
它不能像等長編碼一樣直接按固定長度去讀取二進制位,翻譯成字元,為了能夠准確讀取翻譯字元,它要求一個字元的編碼不能是另外一個字元的前綴。
假設a、b、c、d、e、f這6個字元出現的頻率依次降低,則我們可以給與他們這樣的編碼
假如字元的出現頻率如圖所示,按照這樣的編碼表示的話,總位數如圖,一共2100bits,更加節省空間了
貪心策略:頻率小的字元,優先入隊。
步驟:
1.將每一個字元作為節點,以出現頻率大小作為權重,將其都放入 優先隊列 中(一個最小堆);
2.每次出隊兩個節點並創建一個父節點,使其權值為剛剛出隊的節點的權值和,並且為兩個節點的父節點(合並)。然後將這個樹入隊。
3.重復操作2,直到隊列中只有一個元素(此時這個元素表示形式應該為一個樹)時,完成創建。
創建好了樹,該怎麼編碼呢?
我們對一個哈夫曼樹,從父節點開始的所有節點,往左邊標0,右邊標1。那麼到達葉子節點的順次編碼就可以找到了。
C:字元集合
Q:優先隊列
EXTRACT-MIN:傳入一個隊列,出隊最小的元素
INSERT:將z插入到Q中
當for循環結束之後,此時隊列中只有一個元素,就是我們需要的哈夫曼樹,最後返回此樹即可。
假設T樹已經是一個最優的樹,假設x、y的頻率小於等於最低處的a、b,然後交換x、a,y、b。
計算代價是否發生變化。
比如這里比較 T 變成 T 』 後代價是否變化,發現代價變小或不變。
同理T』到T』』,又因為T本來假設就是最優的,所以只能相等
所以T』』也應該符合條件,即貪婪演算法,每次取最小的兩個節點出來這種做法是正確的
『伍』 程序員都應該精通的六種演算法,你會了嗎
對於一名優秀的程序員來說,面對一個項目的需求的時候,一定會在腦海里浮現出最適合解決這個問題的方法是什麼,選對了演算法,就會起到事半功倍的效果,反之,則可能會使程序運行效率低下,還容易出bug。因此,熟悉掌握常用的演算法,是對於一個優秀程序員最基本的要求。
那麼,常用的演算法都有哪些呢?一般來講,在我們日常工作中涉及到的演算法,通常分為以下幾個類型:分治、貪心、迭代、枚舉、回溯、動態規劃。下面我們來一一介紹這幾種演算法。
一、分治演算法
分治演算法,顧名思義,是將一個難以直接解決的大問題,分割成一些規模較小的相同問題,以便各個擊破,分而治之。
分治演算法一般分為三個部分:分解問題、解決問題、合並解。
分治演算法適用於那些問題的規模縮小到一定程度就可以解決、並且各子問題之間相互獨立,求出來的解可以合並為該問題的解的情況。
典型例子比如求解一個無序數組中的最大值,即可以採用分治演算法,示例如下:
def pidAndConquer(arr,leftIndex,rightIndex):
if(rightIndex==leftIndex+1 || rightIndex==leftIndex){
return Math.max(arr[leftIndex],arr[rightIndex]);
}
int mid=(leftIndex+rightIndex)/2;
int leftMax=pidAndConquer(arr,leftIndex,mid);
int rightMax=pidAndConquer(arr,mid,rightIndex);
return Math.max(leftMax,rightMax);
二、貪心演算法
貪心演算法是指在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,他所做出的僅是在某種意義上的局部最優解。
貪心演算法的基本思路是把問題分成若干個子問題,然後對每個子問題求解,得到子問題的局部最優解,最後再把子問題的最優解合並成原問題的一個解。這里要注意一點就是貪心演算法得到的不一定是全局最優解。這一缺陷導致了貪心演算法的適用范圍較少,更大的用途在於平衡演算法效率和最終結果應用,類似於:反正就走這么多步,肯定給你一個值,至於是不是最優的,那我就管不了了。就好像去菜市場買幾樣菜,可以經過反復比價之後再買,或者是看到有賣的不管三七二十一先買了,總之最終結果是菜能買回來,但搞不好多花了幾塊錢。
典型例子比如部分背包問題:有n個物體,第i個物體的重量為Wi,價值為Vi,在總重量不超過C的情況下讓總價值盡量高。每一個物體可以只取走一部分,價值和重量按比例計算。
貪心策略就是,每次都先拿性價比高的,判斷不超過C。
三、迭代演算法
迭代法也稱輾轉法,是一種不斷用變數的舊值遞推新值的過程。迭代演算法是用計算機解決問題的一種基本方法,它利用計算機運算速度快、適合做重復性操作的特點,讓計算機對一組指令(或一定步驟)進行重復執行,在每次執行這組指令(或這些步驟)時,都從變數的原值推出它的一個新值。最終得到問題的結果。
迭代演算法適用於那些每步輸入參數變數一定,前值可以作為下一步輸入參數的問題。
典型例子比如說,用迭代演算法計算斐波那契數列。
四、枚舉演算法
枚舉演算法是我們在日常中使用到的最多的一個演算法,它的核心思想就是:枚舉所有的可能。枚舉法的本質就是從所有候選答案中去搜索正確地解。
枚舉演算法適用於候選答案數量一定的情況。
典型例子包括雞錢問題,有公雞5,母雞3,三小雞1,求m錢n雞的所有可能解。可以採用一個三重循環將所有情況枚舉出來。代碼如下:
五、回溯演算法
回溯演算法是一個類似枚舉的搜索嘗試過程,主要是在搜索嘗試過程中尋找問題的解,當發現已不滿足求解條件時,就「回溯」返回,嘗試別的路徑。
許多復雜的,規模較大的問題都可以使用回溯法,有「通用解題方法」的美稱。
典型例子是8皇後演算法。在8 8格的國際象棋上擺放八個皇後,使其不能互相攻擊,即任意兩個皇後都不能處於同一行、同一列或同一斜線上,問一共有多少種擺法。
回溯法是求解皇後問題最經典的方法。演算法的思想在於如果一個皇後選定了位置,那麼下一個皇後的位置便被限制住了,下一個皇後需要一直找直到找到安全位置,如果沒有找到,那麼便要回溯到上一個皇後,那麼上一個皇後的位置就要改變,這樣一直遞歸直到所有的情況都被舉出。
六、動態規劃演算法
動態規劃過程是:每次決策依賴於當前狀態,又隨即引起狀態的轉移。一個決策序列就是在變化的狀態中產生出來的,所以,這種多階段最優化決策解決問題的過程就稱為動態規劃。
動態規劃演算法適用於當某階段狀態給定以後,在這階段以後的過程的發展不受這段以前各段狀態的影響,即無後效性的問題。
典型例子比如說背包問題,給定背包容量及物品重量和價值,要求背包裝的物品價值最大。