---
# System prepended metadata

title: Greedy
tags: [APCS Preparation]

---

---
tags: APCS Preparation
---
# Greedy 
## 什麼是Greedy?
* 只考慮目前狀態的最佳選擇，之前所選擇的解答不會影響到後面所選的解答
* 不斷選擇局部最佳解 -> 全部選取結束後就獲得整體的最佳解
## Greedy的解題步驟
### Step.1 定義貪婪準則
根據此貪婪準則，不斷找出目前小問題的最佳解
### Step.2 不斷重複貪婪準則，直到解決問題為止

## 工作排程問題
### Problem A
有$n$個工作可以執行，給定開始與結束時間，時間從0開始，開始與結束時間都是整數，只有一台機器可以執行且每次只能執行一份工作，工作一旦開始必須做完，已知不用考慮切換時間，問：執行完後最多有幾個工作被完成？

Sample Input
5
1 10
2 4 
3 6 
2 5 
4 9
 
Sample Output
2

#### 原則：結束最早的工作先做！
``` cpp
#include<iostream>
#include<cstdlib>
using namespace std;
struct work
{
    int start;
    int end;
};
bool cmp(work a,work b)
{
    if(a.end == b.end)
    {
        return a.start<b.start;
    }
    
    return a.end<b.end;
}
int main()
{
    int n;
    while(cin>>n)
    {
        work arr[n];
        for(int i = 0; i < n; i++)
        {
            cin>>arr[i].start;
            cin>>arr[i].end;
        }
        
        sort(arr,arr+n,cmp);
        
        int ans = 1;
        int cur_time = arr[0].end;
        
        for(int j = 1; j < n; j++)
        {
            if(arr[j].start >= cur_time)
            {
                ans++;
                cur_time = arr[j].end;
            }
            
        }
        
        
        cout<<ans<<endl;
        
    }
}

```

### Problem B
有$n$個工作可以執行，給定開始與結束時間，工作時間可能重疊，時間從0開始，開始與結束時間都是整數，有$n$台機器可以執行且每次只能執行一份工作，工作可到每台機器去執行，且工作一旦開始必須做完，已知不用考慮切換時間，問：執行完後最少有幾台機器才能完成所有工作

Sample Input
5
1 10
2 4 
3 6 
2 5 
4 9
 
Sample Output
4

```cpp
#include<iostream>
#include<cstdlib>
using namespace std;
struct work
{
    int start;
    int end;
};
bool cmp(work a,work b)
{
    if(a.start == b.start)
    {
        return a.end<b.end;
    }
    
    return a.start<b.start;
}
int main()
{
    int n;
    while(cin>>n)
    {
        work arr[n];
        for(int i = 0; i < n; i++)
        {
            cin>>arr[i].start;
            cin>>arr[i].end;
        }
        
        sort(arr,arr+n,cmp);
        
        int ans = 1;
        int m[n];
        for (int x = 0;x<n;x++)
        {
            m[x] = -1;
        }
        
        m[0] = arr[0].end;
        
        for(int j = 1; j < n; j++)
        {
            for(int k=0;k<n;k++)
            {
                if(m[k]<=arr[j].start)
                {
                    m[k] = arr[j].end;
                    break;
                }
            }
            
        }
        
        int arc_ans = 0;
        for(int r=0;r<n;r++)
        {
            if(m[r] == -1)
                arc_ans++;
        }
        
        cout<<"arc:"<<arc_ans<<endl;
        ans = n-arc_ans;
        
        cout<<ans<<endl;
        
    
        
    }
}
```

### Problem C
有$n$個工作從開始就進入到機器，給予$n$個工作的執行所需時間，只有一台機器可以執行且每次只能執行一份工作，工作一旦開始必須做完，請計算完成工作的最小平均等待時間？

Sample Input
5
10
4
6
5
9

Sample Output
10.4

```cpp
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;
int main()
{
    int n;
    while(cin>>n)
    {
        int arr[n];
        for(int i=0;i<n;i++)
        {
            cin>>arr[i];
        }
        
        sort(arr,arr+n);
        
        int now_wait = 0;
        int total = 0;
        for(int j=0;j<n;j++)
        {
            int tmp = 0;
            for(int k=0;k<j;k++)
                tmp+=arr[k];
            now_wait = tmp;
            total += now_wait;
        }
        
        double avg = (double)(total)/n;
        cout<<avg<<endl;
    }
}


```

### Practice Problem Sets 
- [ ] [ZJ a567](https://https://zerojudge.tw/ShowProblem?problemid=a567)


## Huffman Coding
### Basic Understanding
[Huffman Coding - Greedy Algorithm](https://https://www.youtube.com/watch?v=dM6us854Jk0)

<iframe width="560" height="315" src="https://www.youtube.com/embed/dM6us854Jk0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

### Problem Statement
每次輸入數字$n$，$n$表示字元個數，輸入$n<26$，之後有$n$行分別是每一行為一個小寫英文字母與一個整數組成，小寫英文字母表示被編碼的字元，而整數出現的頻率，數值越大表示頻率越高

Output: 輸出每個小寫英文字母的編碼

Sample Input
5 
a 10
b 4
c 5
d 7
e 8

Sample Output
d 00
e 01
b 100
c 101
a 11

### 解題思路
* Greedy: 
    * 每一個字母照frequency從小到大排序
* Merge: 
     * 使用deque，一次取出frequency（可能為字母也有可能是子    樹最小的前兩項並合併成為新的子樹
    * 將新的子樹放回deque，按照上述步驟不斷排序到只剩一項（也代表已經合併完成為止）
    
* DFS:
    * Traversal the whole tree
    * 左邊為0，右邊為1

### Practice Code


```    cpp
#include <iostream>
#include<algorithm>
#include<deque>
using namespace std;
struct node
{
    int id; //節點的編號
    char ch; //字母
    int w;//frequency
    bool t; //判斷此節點是否為字元
    int le; //左子節點
    int ri; //右子節點
};
node hf[101];

deque<node> tmp;
char code[10];
bool cmp(node a,node b)
{
    return a.w<b.w;
}

void dfs(int id, int level)
{
    if(!hf[id].t)
    {
        code[level] = '0'; //往左邊為0
        dfs(hf[id].le,level+1);
        code[level] = '1'; //往右邊為1
        dfs(hf[id].ri,level+1);
    }
    else
    {
        //dfs終止：到達字母的葉
        cout<<hf[id].ch<<" ";
        for(int i=0;i<level;i++)
        {
            cout<<code[i];
        }
        cout<<endl;
    }
}


int main()
{
    int n,w,num;
    char c;
    while(cin>>n)
    {
        tmp.clear();
        for(int i=0;i<n;i++)
        {
            cin>>c>>w;
            hf[i].id = i;
            hf[i].ch = c;
            hf[i].w = w;
            hf[i].t = true;
            tmp.push_back(hf[i]);
        }
        
        num = n; //從第n項開始發放合併的結果
        sort(tmp.begin(),tmp.end(),cmp); //按照frequencies排序
        while(tmp.size()>1)
        {
            node a,b,s;
            //取出前面freqency最小的兩項
            a = tmp.front();
            tmp.pop_front();
            b = tmp.front();
            tmp.pop_front();
            
            //合併成好新的s
            s.id = num;
            s.t = false;
            s.le = a.id;
            s.ri = b.id;
            s.w = a.w+b.w;
            hf[num] = s;
            tmp.push_back(s);
            sort(tmp.begin(),tmp.end(),cmp);
            num++;
        }
        //當全部合併完畢會跳出while迴圈
        
        //遍歷求所有結果
        dfs(tmp[0].id,0);
    }
    
}

```
### Practice Problem Sets
- [x] [ZJ d371](https://zerojudge.tw/ShowProblem?problemid=d371) 
- [x] [ZJ b606](https://zerojudge.tw/ShowProblem?problemid=b606)

### APCS

![](https://i.imgur.com/QCBIfxK.png)
![](https://i.imgur.com/xB2ppSq.png)
- [x] [ZJ c471](https://zerojudge.tw/ShowProblem?problemid=c471)

```cpp
#include<iostream>
#include<cstdlib>
using namespace std;
struct node
{
    long long w;
    long long f;
};
bool cmp(node a,node b)
{
    return a.w*b.f < b.w*a.f; //代價小的排上面
    
}
int main()
{
    int n;
    while(cin>>n)
    {
        node item[n];
        for(int i=0;i<n;i++)
        {
            cin>>item[i].w;
        }
        
        for(int i=0;i<n;i++)
        {
            cin>>item[i].f;
        }
        
        
        sort(item,item+n,cmp);
        //cout<<"items"<<endl;
        /*for(int i=0;i<n;i++)
        {
            cout<<item[i].w<<" ";
        }
        cout<<"\n";
        */
        long long cost = 0;
        long long int acc = item[0].w; //頂上累積的量
        for(int i=1;i<n;i++)
        {
            cost += acc*item[i].f;
            acc+=item[i].w;
        }
        
        cout<<cost<<endl;
        
        
    }
    
}

```

## 可分割背包

假設有$n$個物品及一個背包，已知背包的負重能力與每個物品的價值與重量，可以將物品只取部分到背包，求在背包的負重範圍內放入背包所有物品的最大價值。

Input
每次輸入數字 $n$ ，$n$ 表示物品個數，之後有 $n$ 行，每一行兩個整數$w$與$v$，$w$表重量，$v$表價值。最後輸入一個整數$k$，表示背包的負重能力。

Output
輸出最大價值

Sample Input
5
3 10
3 4
1 5
2 7
3 8
5

Sample Output
18.66667

### 解題想法
按照**單位重量由大到小**排序，依序放到背包裡面
放到背包裝滿為止，最後一個**可以是被拆開的**
( ie. 可以放$2/3$ 顆蘋果到書包裡，這是合法的 )

``` cpp
#include<iostream>
#include<cstdlib>
using namespace std;
struct item
{
    int w;
    int v;
    double v_perw;
};
bool cmp(item a,item b)
{
    return a.v_perw > b.v_perw;
}
int main()
{
    int n;
    while(cin>>n)
    {
        item arr[n];
        for(int i=0;i<n;i++)
        {
            cin>>arr[i].w>>arr[i].v;
            arr[i].v_perw = (double)arr[i].v/(double)arr[i].w;
            //cout<<arr[i].v_perw<<endl;
        }
        int max;
        cin>>max;
        double sum = 0;
        sort(arr,arr+n,cmp);
        
        for(int j=0;j<n;j++)
        {
            if(arr[j].w <= max) //全放
            {
                max -= arr[j].w;
                sum += (double)arr[j].v;
                //cout<<"max:"<<max<<endl;
                
            }
            
            else//切著放
            {
                double tmp = (double)max/(double)arr[j].w;
                tmp *= arr[j].v;
                
                sum += tmp;
                break;
            }
            
            
            
        }
        cout<<sum<<endl;
        
        
        
    }
}

```
