關於區間貪心的補全
之前在部落格裡總結過貪心演算法的相關注意概念,但是由於當時理解不夠,並沒有很好的總結區間貪心問題,所以在這裡做一個總結:
區間貪心演算法總的來說有兩大題型,一個是區間不相交問題,一個是區間選點問題;
其實第二種問題是第一種問題的子問題,並且對於貪心演算法中的概念一定要有所體會;
一、區間不相交
區間不相交問題就是對給定的一些開區間中,儘可能多的選擇開區間,使得這些開區間兩兩沒有交集;
對於這個問題,首先要理解重疊區間的概念,也就是對於下列圖片所給的情況:
當出現這鐘情況的時候,我們應該優先選擇I1,因為這樣的話就可以給其他區間騰出很多的空閒位置;
其次,當消除了所有子區間重疊問題的時候,我們會有如下的情況出現:
對於這種情況,我們採用的是對各個區間按照左端點的大小進行排序,此時就會形成上圖的情況,每個區間的有右邊節點有序;
程式碼如下所示:
#include<stdlib.h> #include<stdio.h> #include<algorithm> using namespace std; const int maxn=110; struct Inteval{ int x,y; }I[maxn]; bool cmp(Inteval a,Inteval b){ if(a.x!=b.x) return a.x>b.x;//左端從大到小進行排序 else return a.y<b.y;//有段從小到大進行排序 } int main(){ int n; while(scanf("%d",&n),n!=0){ for(int i=0;i<n;i++){ scanf("%d%d",&I[i].x,&I[i].y); } sort(I,I+n,cmp); int ans=1,lastX=I[0].x; for(int i=1;i<n;i++){ if(I[i].y<=lastX){ //該情況下為不相交區間 lastX=I[i].x; ans++; } } } system("pause"); }
其實最難理解的應該是程式碼中的處理,這裡給出詳細的解釋:
首先來看排序函式
bool cmp(Inteval a,Inteval b){ if(a.x!=b.x) return a.x>b.x;//左端從大到小進行排序 else return a.y<b.y;//有段從小到大進行排序 }
這裡之所以要在左端大小相同的情況下對右端進行遞增排序,是為了找出包含區間中的最小的子區間;
這樣進行排序的時候,就會變成存在包含關係的區間在一起,但是首位肯定是包含區間中的最小區間;
之後就是主體處理;
while(scanf("%d",&n),n!=0){ for(int i=0;i<n;i++){ scanf("%d%d",&I[i].x,&I[i].y); } sort(I,I+n,cmp); int ans=1,lastX=I[0].x; for(int i=1;i<n;i++){ if(I[i].y<=lastX){ //該情況下為不相交區間 lastX=I[i].x; ans++; } } }
這裡注意for迴圈,其實就是對陣列進行上述分析的第二部處理,從右向左,尋找不重合的區間(由於排序函式的操作,找到的必定是重合區間中的最小區間),迴圈從而使得每次選擇出來的都是最小的不重合的區間;
個人認為,區間不重疊的貪心思路主要體現在尋找最小的包含區間上;對於每一塊有重合情況的區間,我們只需要找出每一塊的重合區間中的最小區間,就可以組合成不重疊的區間,並且這些區間肯定數目最大,符合題意;
二、區間選點
區間選點可以視為第一種問題的衍生問題,目的是將給出開區間(注意,這裡是開區間)中選擇點,使得每個區間都至少有一個點;
該問題也涉及重疊區間的問題。
還是一樣,當上述情況發生的時候,我們如果點放在I1內,就會使得I2內也存在點;所以根本上來說,我們還是尋找重疊區間內最小的區間;
因此,我們採用的還是第一類問題的操作,但是需要注意的就是點的選取;
對於有序的序列,我們應該把點選在左端點,而不是右端點;
關於這個問題,可以這樣想:
對於上述情況,如果選取右端點,就會出現選擇兩個的情況,但是如果選取左端點,就只用選取一個;
所以,只需要對之前的程式碼進行修改,修改一個判定條件;
將 I[i].y<=lastX
修改為 I[i]<lastX
即可