1. 程式人生 > >《演算法導論》第十六章——貪心演算法

《演算法導論》第十六章——貪心演算法

  雖然寫這個部落格主要目的是為了給我自己做一個思路記憶錄,但是如果你恰好點了進來,那麼先對你說一聲歡迎。我並不是什麼大觸,只是一個菜菜的學生,如果您發現了什麼錯誤或者您對於某些地方有更好的意見,非常歡迎您的斧正!

貪心演算法並不保證能得到最優解,但對於很多問題確實可以得到最優解。

貪心演算法,如它的名字一樣,十分“貪心”。如果說,動態規劃是一位深謀遠慮的智者,那麼貪心演算法就是一個目光短淺的山賊,它總是選取看起來最佳的選擇,卻不為以後做考慮。
比如我有重量為60,50,30,50這樣4顆依次擺放的鑽石,它們的價值依次為6,5,3,5,我現在書包最多能裝重量為100的東西,動態規劃得到的最優解就是拿兩顆50的,但是貪心演算法在看到60的時候就會把60放入書包,最後只能裝到90。

16.1活動選擇問題

簡單地講,就是有一個公用教室,很多班級要用它來搞活動,比如(1)班想下午(3)點到下午5點借用教室,(2)班想晚上7點到晚上9點借用教室,那就不會產生衝突,但是如果現在(3)班想在晚上6點晚上8點借用教室,那就會與(2)班造成衝突,兩者必須有一者放棄。又比如(4)班要借用的時間段為下午5點到晚上7點。現在要選擇這個教室可以給哪幾個班用,使得最多的班的要求能夠達到滿足。那顯然管理員應該優先借給(1)班、(2)班與(4)班。

現在我們的活動集為S(i是班號,si是起始時間,fi是結束時間):

在這裡插入圖片描述

兩種安排為{1,4,8,11}、{2,4,9,11}

活動選擇問題的最優子結構

c[i,j]表示集合Si,j的最優解的大小。

在這裡插入圖片描述

貪心選擇

選擇最早結束的那個活動,這樣就有更多時間留給別的活動。(如果最早結束的活動有很多個,就選擇任意一個)。

迭代貪心演算法

感覺書中的虛擬碼不是很看得懂,就用白話文說一下我的理解吧。
首先我們的活動已經按照結束時間排好序了,然後我們要把第一個,也就是最早結束的活動加入到我們的安排中,從第二個活動開始遍歷,只要下一個的開始時間晚於上一個的結束時間,就把它加入到我們的安排列表中。

我們用陣列b來記錄要加入安排的活動。這是我自己寫的虛擬碼

b[1]=true
j=1
i=2 to n
if(s[i]≥f[j]) //下一個的開始時間晚於上一個的結束時間
b[i]=true//加入安排
j=i
else
b[i]=false

16.2貪心演算法原理

步驟:
1.確定問題的最優子結構
2.設計一個遞迴演算法
3.證明如果我們做出一個貪心選擇,則只剩下一個子問題
4.證明貪心選擇總是安全的
5.設計一個遞迴演算法實現貪心策略
6.將遞迴演算法轉換為迭代演算法

貪心選擇性質

當進行選擇時,我們直接做出在當前問題中看來最優的選擇,而不必考慮子問題的解。

最優子結構

如果一個問題的最優解包含其子問題的最優解,則稱此問題具有最優子結構性質。這個性質是能否應用動態規劃和貪心演算法的關鍵要素。

貪心對動態規劃

0-1揹包問題
假如我有一隻只能裝載150kg的包,現在這裡有一些物品:
物品 1  2  3  4  5  6  7
重量 35  30 60   50   40 10  25   用陣列w[]表示
價值 10  40 30  50  35  40  30   用陣列v []表示

我們有三種策略可以選擇

1、每次裝的都選擇價效比最高的,也就是價值/重量最大
2、每次裝都選擇重量最輕的
3、每次裝都選擇價值最高的

我們採用第一種方式。

16.3赫夫曼編碼

赫夫曼編碼可以很有效地壓縮資料:通常可以節省20%~90%的空間,具體壓縮率依賴於資料的特性。

在這裡插入圖片描述

來看上面一組資料,加入我們使用定長編碼,則a=000,b=001…f=101,這種方法需要3x100x1000=300000個二進位制位來編碼檔案。

假如我們採用變長編碼:a=0,b=101,c=100,d=111,e=1101,f=1100,則需要(45x1+13x3+12x3+16x3+9x4+5x4)x1000=224000位。

字首碼

我們注意到上面的編碼中:沒有任何碼字是其它碼字的字首。

將3個字元的檔案abc編碼為0·101·100=0101100,“·”表示連線操作。

二進位制碼001011101可以唯一解析為0·0·101·1101,解析碼為aabe

檔案的最優編碼方案總是對應一棵滿二叉樹。

x.freq表示x的頻率。

在這裡插入圖片描述
簡單的說,就是不停地從已有的結點中找到最小的兩個結點,然後連線起來。

這部分程式碼我不知道怎麼寫,應該說我太菜了,沒有寫成功,所以底下就只有活動選擇和0-1揹包問題的程式碼:

貪心演算法_活動安排.h

#pragma once
/*活動安排*/
void ActManage(int n, int s[], int f[], int b[]);

/*測試函式*/
void TestActManage();

貪心演算法_活動安排.cpp

#include "貪心演算法_活動安排.h"
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;

/*活動安排*/
void ActManage(int n, int s[], int f[], bool b[])
{
	b[1] = true;
	int j = 1;

	for (int i = 2; i < n; i++)
	{
		if (s[i] >= f[j])
		{
			b[i] = true;
			j = i;
		}
		else
			b[i] = false;
	}
}

/*測試函式*/
void TestActManage()
{
	int s[] = { 0,1,3,0,5,3,5,6,8,8,2,12 };/*12*/
	int f[] = { 0,4,5,6,7,9,9,10,11,12,15,16 };

	int n = sizeof(s) / sizeof(s[0]);

	bool* b = new bool[n];
	ActManage(n, s, f, b);
	
	for (int i = 1; i < n; i++)
	{
		if (b[i])cout << "活動" << i << "開始於" << s[i] << ",結束於" << f[i] << endl;
	}
	delete[] b;
}

主函式

#include "貪心演算法_活動安排.h"
#include <stdio.h>

int main()
{
	TestActManage();
	getchar(); 
	getchar(); 
	return 0;
}

執行結果

在這裡插入圖片描述

貪心演算法_01揹包問題.h

#pragma once

/*裝載方法*/
void BackageLoad(float cost[], bool b[], int w[], int weight, int n);

/*測試函式*/
void TestBackage();

貪心演算法_01揹包問題.cpp

#include "貪心演算法_01揹包問題.h"
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;

/*裝載方法*/
void BackageLoad(float cost[], bool b[], int w[], int weight, int n)
{
	int temp;
	bool flag = false;/*判斷是否還能再挑出物品*/

	while (weight >= 0)/*當袋子還能裝下的時候*/
	{
		float max = 0;
		for (int i = 0; i < n; i++)
		{
			if (!b[i] && cost[i] > max&&weight - w[i] >= 0)/*如果b[i]還沒有裝進去*/
			{
				max = cost[i];
				temp = i;
				flag = true;
			}
		}
		if (!flag)
			break;
		weight -= w[temp];
		b[temp] = true;
	}
}

/*測試函式*/
void TestBackage()
{
	int w[7] = { 35,30,60,50,40,10,25 };/*重量*/
	int v[7] = { 10,40,30,50,35,40,30 };/*價值*/
	float cost[7];/*價效比*/
	bool b[7];/*記錄是否裝進袋子裡*/
	int weight = 150;/*袋子承重*/
	int i;

	for (i = 0; i < 7; i++)
	{
		cost[i] = (float)v[i] / (float)w[i];
		b[i] = false;
	}

	BackageLoad(cost, b, w, weight, 7);

	for (i = 0; i < 7; i++)
	{
		if (b[i])
		{
			cout << "選擇物品" << i+1 << ",它重" << w[i] << ",價值為" << v[i] << endl;
		}
	}
}

主函式

#include "貪心演算法_01揹包問題.h"
#include <stdio.h>

int main()
{
	TestBackage();
	getchar(); 
	getchar(); 
	return 0;
}

執行結果

在這裡插入圖片描述

參考網站及部落格

曹磊的部落格
https://www.cnblogs.com/cao-lei/p/6896841.html