貪心算法2

在求最優解問題的過程中 ,依據某種貪心標準,從問題的初始狀態出發,直接去求解每一步的最優解,通過若干次的貪心選擇,最終得出整個問題的最優解,這種求解方法就是談心算法。

從貪心算法的定義可以看出,貪心法並不是從整體上考慮問題,它所做出的選擇只是在某中意義上的局部最優解,而由問題自身的特性決定了該題運用貪心算法可以得到最優解。

例題1:均分紙牌(NOIP2002tg

問題描述:有N堆紙牌,編號分別爲12n。每堆上有若干張,但紙牌總數必爲n的倍數.可以在任一堆上取若干張紙牌,然後移動。移牌的規則爲:在編號爲1上取的紙牌,只能移到編號爲2的堆上;在編號爲n的堆上取的紙牌,只能移到編號爲n-1的堆上;其他堆上取的紙牌,可以移到相鄰左邊或右邊的堆上。現在要求找出一種移動方法,用最少的移動次數使每堆上紙牌數都一樣多。例如:n=44堆紙牌分別爲: 9 8 17 6 移動三次可以達到目的:從③取4張牌放到④ 再從③區3張放到②然後從②去1張放到①。

輸入:鍵盤輸入文件名。

文件格式:n n堆紙牌 1<=n<=100

a1,a2,a3,… an(n堆紙牌,每堆紙牌初始數,1<=ai<=10000)

輸出:輸出至屏幕。格式爲:所有堆數均達到相等時的最少移動次數。

輸入輸出樣例:a.in:     4

9         8 17 6

屏幕顯示:3

算法分析:設a[i]爲第I堆紙牌的張數(0<=I<=n),v爲均分後每堆紙牌的張數,s爲最小移動次數。

我們用貪心法,按照從左到右的順序移動紙牌。如第I堆的紙牌數不等於平均值,則移動一次(即s1),分兩種情況移動:

1.若a[i]>v,則將a[i]-v張從第I堆移動到第I+1堆;

2.若a[i]<v,則將v- a[i]張從第I+1堆移動到第I堆。

爲了設計的方便,我們把這兩種情況統一看作是將a[i]-v從第I堆移動到第I+1堆,移動後有a[i]=v; a[I+1]=a[I+1]+a[i]-v.

在從第I+1堆取出紙牌補充第I堆的過程中可能回出現第I+1堆的紙牌小於零的情況。

n=3,三堆指派數爲1 2 27 ,這時v=10,爲了使第一堆爲10,要從第二堆移9張到第一堆,而第二堆只有2張可以移,這是不是意味着剛纔使用貪心法是錯誤的呢?

我們繼續按規則分析移牌過程,從第二堆移出9張到第一堆後,第一堆有10張,第二堆剩下-7張,在從第三堆移動17張到第二堆,剛好三堆紙牌都是10,最後結果是對的,我們在移動過程中,只是改變了移動的順序,而移動次數不便,因此此題使用貪心法可行的。

源程序:

var I,n,s:integer; v:longint;   a:array[1..100] of longint;

f:text; fil:string;

begin   realn(fil);

        assign(f,fi); reset(f);

        readln(f,n); v:=0;

        for I:=1 to n do begin

read(f,a[i]), inc(v,a[i]);

                         end;

        v:=v div n;   {每堆牌的平均數}

        for I:=1 to n-1 do

          if a[i]<>v then   {貪心選擇}

                        begin   inc(s);   {移牌步數記數}

                              a[I+1]=a[I+1]+a[i]-v;{使第I堆牌數爲v}

                         end;

          writeln(s);

end.

    利用貪心法解題,需要解決兩個問題:

    一是問題是否適合用貪心法求解。我們看一個找幣的例子,如果一個貨幣系統有三種幣值,面值分別爲一角、五分和一分,求最小找幣數時,可以用貪心法求解;如果將這三種幣值改爲一角一分、五分和一分,就不能使用貪心法求解。用貪心法解題很方便,但它的適用範圍很小,判斷一個問題是否適合用貪心法求解,目前還沒有一個通用的方法,在信息學競賽中,需要憑個人的經驗來判斷何時該使用貪心算法。

    二是確定了可以用貪心算法之後,如何選擇一個貪心標準,才能保證得到問題的最優解。在選擇貪心標準時,我們要對所選的貪心標準進行驗證才能使用,不要被表面上看似正確的貪心標準所迷惑,如下面的例子。

例題2:設有n個正整數,將它們連接成一排,組成一個最大的多位整數。

例如:n=3時,3個整數13312343,連成的最大整數爲34331213

又如:n=4時,4個整數7134246,連成的最大整數爲7424613

輸入:n

      N個數

輸出:連成的多位數

算法分析:此題很容易想到使用貪心法,在考試時有很多同學把整數按從大到小的順序連接起來,測試題目的例子也都符合,但最後測試的結果卻不全對。按這種標準,我們很容易找到反例:12121應該組成12121而非12112,那麼是不是相互包含的時候就從小到大呢?也不一定,如12123就是12312而非12123,這種情況就有很多種了。是不是此題不能用貪心法呢?

其實此題可以用貪心法來求解,只是剛纔的標準不對,正確的標準是:先把整數轉換成字符串,然後在比較a+bb+a,如果a+b>b+a,就把a排在b的前面,反之則把a排在b的後面。

源程序:

Var s:array[1..20] of string;

     T:string;I,k,j,n:longint;

Begin

     Readln(n);

     For i:=1 to n do

        Begin   read(k);

                Str(k,s[i]);

         End;

     For i:=1 to n-1 do

         For j:=i+1 to n do

           If s[i]+s[j]<s[j]+s[i] then

              Begin   t:=s[i];

                      S[i]:=s[j];

                      S[j]:=t;

              End;

      For i:=1 to n do write(s[i]);

End.

貪心算法所作的選擇可以依賴於以往所作過的選擇,但決不依賴於將來的選擇,也不依賴於子問題的解,因此貪心算法與其他算法相比具有一定的速度優勢。如果一個問題可以同時用幾種方法解決,談心算法應該是最好的選擇之一。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章