一招制敵的貪心算法

摘要

本文主要介紹貪心算法。 貪心算法並不是一種特定的算法,而是一種策略,一種一招制敵的策略。每次都貪心選擇最好的,就是貪心算法。 所以貪心算法往往效率高,代碼短。常見的貪心問題:區間問題, Huffman樹等。

區間問題

最大不相交區間數

例題: HDU 2037 今年暑假不AC

在這裏插入圖片描述
題目大意就是給出n個節目,n個節目起止時間不同,要求選出最多的沒有時間衝突的節目。

這道題是一道經典的貪心問題。本質上就是求出最大不相交區間數。對於本題,區間端點可以相交。

做法:對所有節目按結束時間從小到大排序。然後枚舉每個區間是否和前一個區間有相交。如果沒有,節目數就加一。

C++代碼:

#include <iostream>
#include <algorithm>
using namespace std;

struct node{ 
    int l, r;
}arr[100005];

int cmp(node a, node b){
    return a.r < b.r;
}
int main(){
    int n;
    while(cin>>n, n){ 
        int l, r;
        for(int i = 0; i < n; i++){
            cin>>l>>r;
            arr[i].l = l;
            arr[i].r = r;
        }
        
        sort(arr, arr + n, cmp);
        
        int f = -2e9;
        int res = 0;
        for(int i = 0; i < n; i++){
            if(arr[i].l >= f){
                res ++;
                f = arr[i].r;
            }
        }
        cout<<res<<endl;
    }
    return 0;
}

區間覆蓋問題

例題 POJ 2376 Cleaning Shifts
在這裏插入圖片描述

題目大意:John有N頭牛,每頭牛都有不同的工作時間,John將一天分爲T個時間段,從1~T。他想讓每個時間段都有牛做靜潔工作,問最少需要幾頭牛。

也就說,找出最少的牛,這些牛的工作時間可以覆蓋1~T。

做法:將每頭牛按工作時間的起始時間從小到大進行排序,然後對於1~T時刻中未被覆蓋的時刻中的起始時刻,找出起始時間能覆蓋該時刻並且結束時間最晚的牛。

注意本題坑點:本題給出的是時刻而不是區間端點,所以除了第一頭牛的開始時間必須是1,此後的第i頭牛的起始時間最晚是第i-1頭牛的結束時間+1.
例如對於樣例:
4 10
1 3
3 5
4 7
8 10
答案是選擇[1 3] [4 7] [8 10]

首先,1~T時刻中 1時刻是未被覆蓋的時刻中的起始時刻,只有[1 3] 能夠覆蓋。

然後 4時刻變爲未被覆蓋的時刻中的起始時刻, 只有[4 7] 能夠覆蓋4時刻

最後 8時刻變爲未被覆蓋的時刻中的起始時刻, 只有[8 10] 能夠覆蓋8時刻

此時1~T時刻已經被覆蓋完了,結束。

所以最終答案是 3。

JAVA代碼:

import java.io.*;
import java.util.*;
import java.math.*;

public class Main{
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    
    static class node implements Comparable<node>{ //自定義類,保存起止時間
        int l, r;                               //需要按起始時間排序,所以實現Comparable接口,自定義排序。
        node(int l, int r){                   
            this.l = l;
            this.r = r;
        }
        public int compareTo(node b) {
            return this.l - b.l;
        } 
    }
    
    static node[] arr = new node[25005];
    
    static int Int(String s){
        return Integer.parseInt(s);
    }
    
    public static void main(String[] args) throws Exception{
    	
        int T, n;
        String[] s = in.readLine().split(" ");
        n = Integer.parseInt(s[0]);
        T = Integer.parseInt(s[1]);
        
        for(int i = 0; i < n; i++){
        	String[] s1 = in.readLine().split(" ");// 讀入
        	arr[i] = new node(Int(s1[0]), Int(s1[1]));
        }
        
        Arrays.sort(arr, 0, n - 1); // 排序
        
        int flag = 0, res = 0, start = 1; // // 剛開始1時刻是未被覆蓋的區間的起始時刻
     
        for(int i = 0; i < n; i++){
            int j = i;
			int r = -1000;
            
            if(arr[0].l > 1){
                break;
            }
            while(j < n &&(start != 1 &&arr[j].l <= start+1|| start == 1 && arr[j].l == 1)){
                r = Math.max(r, arr[j].r);
                j ++;
            }
            res ++;
            if(r < start){ // 如果r < start說明沒有符合條件的區間。直接break輸出-1
                break;
            }
            
            if(r >= T   ){ // 如果 r >= T 說明區間已經被完全覆蓋,break輸出答案
                flag = 1;
                break;
            }
            
            start = r; // 更新起始時刻
            i = j - 1; // 0 ~ j-1個區間已經遍歷過了,直接跳過。
        }
        if(flag != 1) res = -1;
        out.write(Integer.toString(res));
        out.flush();
    }
}

Huffman樹

Huffman樹也是貪心思想的經典體現,給定n個葉結點,用這n個葉結點構造一個二叉樹,Huffman樹是帶權路徑長度最小的二叉樹。

Huffman樹詳解視頻:
https://www.bilibili.com/video/BV18t411U7bj/?spm_id_from=333.788.videocard.4

Huffman樹的特點:權值越大的葉結點一定距離根節點越近,權值越小的葉節點一定距離根節點越遠。

構造Huffman樹:
爲了保證得到的帶權路徑長度最小,我們每次都要合併兩個最小的葉節點。

例題: POJ 3253
在這裏插入圖片描述
題目大意: John想將一塊木板切割成n塊,沒切割一次,就會發費當前切割的木板總長度的代價,求最小代價。

轉化爲Huffman樹, John最終切成的木塊長度就是葉節點。 所以只需要建個Huffman樹然後求出帶權路徑長度就行了。

使用優先隊列每次合併兩個最小的節點。

代碼:

import java.io.*;
import java.util.*;
import java.math.*;

public class Main{
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    
    static int Int(String s){
        return Integer.valueOf(s);
    }
    
    public static void main(String[] args) throws Exception{
    	int n = Int(in.readLine());
       	PriorityQueue<Integer> heap = new PriorityQueue<Integer>(); // 小根堆
    
        while(n --> 0){
            heap.add(Int(in.readLine()));
        }
    	
    	long res = 0;// 答案很大,用long存
    
    	while(heap.size() != 1)
    	{
    	    int min = heap.poll() + heap.poll();
    	    res += min;
    	    heap.add(min);
    	}
    	
    	out.write(res + "");
    	out.flush();
    }
}

還有很多其他類型的貪心問題,文本就不在一一列舉,做貪心主要就是看能不能貪對,想不出來怎麼貪心就做不對,想出來了代碼就很簡單,所以還是要多做題。

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