1. 程式人生 > 實用技巧 >貪心演算法(一)

貪心演算法(一)

貪心演算法

一、什麼叫貪心演算法

貪心演算法(又稱貪婪演算法)是指,在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,他所做出的是在某種意義上的區域性最優解。

貪心演算法不是對所有問題都能得到整體最優解,關鍵是貪心策略的選擇,選擇的貪心策略必須具備無後效性,即某個狀態以前的過程不會影響以後的狀態,只與當前狀態有關。

二、貪心演算法使用範圍

1、原問題複雜度過高;

2、求全域性最優解的數學模型難以建立;

3、求全域性最優解的計算量過大;

4、沒有太大必要一定要求出全域性最優解,“比較優”就可以。

三、貪心選擇性質

所謂貪心選擇性質是指所求問題的整體最優解可以通過一系列區域性最優的選擇,換句話說,當考慮做何種選擇的時候,我們只考慮對當前問題最佳的選擇而不考慮子問題的結果。這是貪心演算法可行的第一個基本要素。貪心演算法以迭代的方式作出相繼的貪心選擇,每作一次貪心選擇就將所求問題簡化為規模更小的子問題。對於一個具體問題,要確定它是否具有貪心選擇性質,必須證明每一步所作的貪心選擇最終導致問題的整體最優解。

當一個問題的最優解包含其子問題的最優解時,稱此問題具有最優子結構性質。問題的最優子結構性質是該問題可用貪心演算法求解的關鍵特徵。

四、貪心演算法的實現框架

從問題的某一初始解出發:
while (朝給定總目標前進一步)
{
利用可行的決策,求出可行解的一個解元素。
}
由所有解元素組合成問題的一個可行解;

五、例題分析

(1)摘果子

題目描述

在一個果園裡,多多已經將所有的果子打了下來,而且按果子的不同種類分成了不同的堆。多多決定把所有的果子合成一堆。

每一次合併,多多可以把兩堆果子合併到一起,消耗的體力等於兩堆果子的重量之和。可以看出,所有的果子經過n-1n1次合併之後, 就只剩下一堆了。多多在合併果子時總共消耗的體力等於每次合併所耗體力之和。

因為還要花大力氣把這些果子搬回家,所以多多在合併果子時要儘可能地節省體力。假定每個果子重量都為11,並且已知果子的種類 數和每種果子的數目,你的任務是設計出合併的次序方案,使多多耗費的體力最少,並輸出這個最小的體力耗費值。

例如有33種果子,數目依次為11,22,99。可以先將11、22堆合併,新堆數目為33,耗費體力為33。接著,將新堆與原先的第三堆合併,又得到新的堆,數目為1212,耗費體力為1212。所以多多總共耗費體力=3+12=15=3+12=15。可以證明1515為最小的體力耗費值。

輸入格式

共兩行。
第一行是一個整數n(1\leq n\leq 10000)n(1n10000),表示果子的種類數。

第二行包含nn個整數,用空格分隔,第ii個整數a_i(1\leq a_i\leq 20000)ai(1ai20000)是第ii種果子的數目。

輸出格式

一個整數,也就是最小的體力耗費值。輸入資料保證這個值小於2^{31}231。

輸入輸出樣例

輸入 #1
3 
1 2 9 
輸出 #1
15

說明/提示

對於30%的資料,保證有n \le 1000n1000:

對於50%的資料,保證有n \le 5000n5000;

對於全部的資料,保證有n \le 10000n10000。

來源:https://www.luogu.com.cn/problem/P1090

思路:

通過題中我們便發現只要每次都將最小的兩堆果子合併就能得出最小的體力耗費,那我們就需要先把每堆果子進行排序,之後將第一堆和第二堆合併成新的一堆果子並記錄下來體力的耗費,之後再排序,重複將第一堆和第二堆合併成新的果子。但是我們發現每次合併果子就要排序,會導致時間複雜度很大,所以我們可以考慮線性表,將新生成的果子堆和後面的每堆果子進行比較,講新生成的果子插入到第一個比他大的果子堆前面便排好序了,這樣的時間復度大大減小。

程式碼:

#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
    int n;
    cin >> n;
    int fruit[10000];
    int strength = 0;
    for (int i = 0;i < n;++i)
    {
        cin >> fruit[i];
    }
    sort(fruit, fruit + n);
    for (int i = 0;i < n - 1;++i)
    {
        fruit[i + 1] += fruit[i];
        strength += fruit[i + 1];
        int temp = fruit[i + 1];
        int j = i + 2;
        for (;j < n;++j)
        {
            if (temp < fruit[j]) break;
        }
        for (int k = i + 1;k < j-1;++k)
        {
            fruit[k] = fruit[k + 1];
        }
        fruit[j - 1] = temp;
    }
    cout << strength;
}