1. 程式人生 > >基礎演算法之貪心法、二分法及其他演算法思想和技巧

基礎演算法之貪心法、二分法及其他演算法思想和技巧

基礎演算法學習筆記(三)

1. 貪心法

貪心演算法是求解一類最優化問題的方法。它總是考慮在當前狀態下區域性最優解的策略,使全域性的結果達到最優。構造貪心策略並不困難,但是嚴謹的使用貪心演算法來求解最優化問題需要對採取的策略進行證明。下面簡單通過幾個程式感受一下貪心演算法。

1.1 簡單貪心

PAT B1020月餅
題目描述:給定所有種類月餅的庫存量、總售價、以及市場的最大需求量,請計算可以獲得的最大收益是多少。(銷售時允許取出一部分庫存)
輸入:庫存和總售價
輸出:最大收益(精確到小數點後兩位)
程式碼截圖:
在這裡插入圖片描述
在這裡插入圖片描述
思考:

  • 書中定義結構體時所有成員均使用double型別,為什麼?
    可以看到程式碼第28行在計算price時,存在一個強制型別轉換,因為price是一個double型別,而sell、store均為整型,若不進行強轉兩個int型變數計算結果也是int型,導致計算錯誤。所以書中在P119頁提示使用double定義變數,便於計算,避免程式執行錯誤。

  • 考慮銷售時不允許取出一部分庫存,即庫存不可拆分時,貪心法如何設計策略?
    若銷售時庫存不可拆分,則此時貪心演算法不一定可以計算出最優解。考慮一種情況: 若銷售時庫存不可拆分,則此時貪心演算法不一定可以計算出最優解。考慮一種情況:
    D = 35
    m1.store = 25,m1.sell =25;m2.store = 20,m2.sell = 20;m3.store =15,m3.sell = 15;
    此時單價相同,選擇m1銷售則結果錯誤;可以新增一條規則單價相同時選擇庫存最少的出售。但是再考慮一種情況:
    D = 40,其他不變。
    上述策略對當前情況不適用。 事實上該問題是0-1揹包問題,貪心演算法不一定能夠給出最優解,可以嘗試使用動態規劃進行求解。

1.2 區間貪心

問題描述:給定若干開區間,從中選擇儘可能多的兩兩之間沒有交集的開區間。例如:(1,3)、(2,4)、(3,5)、(5,7)最多有3個開區間(1,3)、(3,5)、(5,7)沒有相交。
輸入:若干開區間
輸出:兩兩沒有交集的開區間最大數量
程式碼截圖:
在這裡插入圖片描述
在這裡插入圖片描述
解題思路:
儘量為剩餘的若干區間留下儘可能多的未填充空間。

  • 首先將所有集合的左端點進行非遞增排序,找到左端點最大的元素,即為起點並記為lastLow。
  • 遍歷排序後的集合,找到第一個不相交的區間(即當前區間右端點<=lastLow),用當前區間左端點值更新lastLow,統計結果+1
  • 輸出統計結果

2. 二分法

2.1 二分查詢

題目描述:根據給定資料,查詢某一元素的位置
輸入:待查詢的元素
輸出:若存在該元素輸出其索引值,否則輸出-1
程式碼截圖:
在這裡插入圖片描述
二分查詢的時間複雜度是O(logn),是一個高效的查詢演算法。但是其有一定的限制性:

  • 要求給定集合必須是嚴格的有序排列。

  • 給定集合儲存時必須採用線性表的順序儲存結構實現。

2.2 快速冪

題目描述:計算ab % m
輸入:整數a、b、m
輸出:計算結果
程式碼截圖:
在這裡插入圖片描述

3. two pointers

3.1 什麼是two pointers

題目關鍵:要求集合是一個有序數列
輸入:a + b和的值m
輸出:每一個滿足 a + b = m的組合
程式碼截圖:
在這裡插入圖片描述
包括兩個佇列合併都會採用two pointers程式設計技巧,在下面的歸併排序演算法中會有所表現。

3.2 歸併排序

歸併排序的基本流程如下圖所示:
在這裡插入圖片描述
歸併排序遞迴實現方式程式碼截圖:
在這裡插入圖片描述
在這裡插入圖片描述
歸併排序演算法非遞迴實現方式程式碼截圖:
在這裡插入圖片描述
以上歸併排序演算法實現方式的不足:

  • 分解過程中粒度過細,分解至一個元素才返回進行排序合併 merge方法中,申請空間用於暫時儲存元素的排序結果,空間效率較低

  • 針對以上不足可以對演算法進行改進,首先在分解過程中,當元素數量小於臨界值時不再分解,直接進行排序操作;然後整體採用鏈式結構實現歸併排序演算法避免臨時空間的申請。

歸併排序演算法靜態連結串列實現方式程式碼截圖:
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
輸入規模為100000的情況下,原歸併排序演算法執行一次大約需要40ms,改進後的歸併排序演算法大約需要20ms。

3.3 快速排序

快速排序演算法流程圖:
在這裡插入圖片描述
快速排序演算法實現程式碼截圖:
在這裡插入圖片描述
在這裡插入圖片描述
思考:當集合本身有序(或大概有序)時,上述快速排序演算法的表現如何?
因為每一輪partition方法選擇的都是第一個元素作為主元,所以若集合本身有序,所有其他元素都會劃分至一個序列中(左側或右側),此時演算法時間複雜度是O(n2)。可以針對這一不足,對演算法主元的選擇進行改進(隨機選擇一個主元)。

改進後的快速排序演算法partition方法程式碼截圖:
在這裡插入圖片描述

4. 其他高效技巧和演算法

4.1 打表

打表是一種典型的用空間換時間的技巧(應試技巧),是指通過事先計算將結果並儲存得到一個常量表,在之後程式中需要用到時就可以通過查表直接獲得結果,進而優化了時間複雜度。
練習:PAT B1044火星數字

4.2 活用遞推

PAT B1040 有幾個PAT
題目描述:給定只含有P、A、T三個字母的字串,求共可以形成多少個PAT
輸入:長度<105的字串
輸出:PAT的數量 mod 1000000007
程式碼截圖:
在這裡插入圖片描述

4.3 隨機選擇演算法

題目描述:給定集合,選擇第K大的元素
輸入:K
輸出:第K大的元素值
程式碼截圖:
在這裡插入圖片描述
隨機選擇演算法的最壞時間複雜度為O(n2),但可以通過證明得到演算法的平均時間複雜度是O(n)。

5. 最大公約數和最小公倍數

兩個數字的最大公約數可以使用歐幾里得演算法計算得到,兩個數字的最小公倍數等於其乘積除以兩個數的最大公約數。
歐幾里得演算法:gcd(a, b) = gcd(b, a%b),證明略。
程式碼截圖:
在這裡插入圖片描述