BÀI TẬP BÀI 2: GIẢI THUẬT THAM LAM

Bài 71Một nông dân đang muốn trồng hoa vào khu vườn của mình. Để cho khu vườn trở nên thật màu sắc ông quyết định trồng nhiều loài hoa khác nhau vào khu vườn. Mỗi loài hoa có mội cách trồng khác nhau do đó ông sẽ trồng từng loài hoa vào các ngày liên tiếp nhau. Cháu của ông rất mong chờ được thấy tất cả loài hoa trong khu vườn đều nở hoa trông sẽ tuyệt vời như thế nào. Tuy nhiên mỗi loài hoa lại có thời gian phát triển từ lúc trồng tới lúc nở hoa khác nhau.

Lựa chọn tham lam là trồng cây có thời gian phát triển lâu nhất trước.

Code python

def date_all_flowers_bloom(a):# a is a list of the development time of the flowers
  a.sort(reverse=True)
  k,result,n=1,0,len(a)
  while k<=n:
    if a[k-1]+k>result:
      result=a[k-1]+k
    k+=1
  return result

Bài 72. Anh Lộc làm nghề phụ hồ cho một công ty xây dựng, Anh nhận thấy rằng mỗi loại gạch đều có độ cứng khác nhau. Giả sử rằng viên gạch có độ cứng k chỉ có thể chịu được tối đa k viên gạch khác chồng lên nó, nếu nhiều hơn thì nó sẽ bị vỡ. Anh Lộc muốn lấy gạch và xếp chúng chồng lên nhau thành một chồng gạch cao nhất có thể mà không để vỡ viên gạch nào. Hãy tìm và in ra màn hình xem anh Lộc có thể xếp chồng gạch có độ cao lớn nhất là bao nhiêu.

Lựa chọn tham lam là xếp viên gạch có độ cứng lớn nhất trước.

Code python

def stack_bricks(a):
    a.sort(reverse=True)
    stiffness,k,n=a[0],1,len(a)
    for i in range(1,n):
        stiffness,k=stiffness-1,k+1
        if stiffness>a[i]:
            stiffness=a[i]
        if stiffness==0:
            return k
    return n

Phần bài tập này mình làm trong khóa học của funix dịch từ phần bài tập trong khóa học Data structures and algorithms của coursera. Chú ý phần bài tập này của coursera trả phí mua khóa học thì mới có. Hoặc bạn có thể mua khóa học của funix có dịch sẵn rất tiện lợi và có mentor giúp đỡ.

Link chi tiết bài tập của cousera https://drive.google.com/file/d/1sVmxysl6m9TSlr-ZWX-ifRwVyaPM8M-S/view?usp=sharing

Lab 2.1. Đổi tiền bằng thuật toán tham lam.

Tìm số lượng đồng xu tối thiểu cần thiết để đổi giá trị input (một số nguyên) thành tiền xu với mệnh giá 1, 5 và 10

Lựa chọn tham lam là đổi tờ tiền cần đổi sang các tờ tiền có mệnh giá lớn nhất có thể và nhỏ hơn hoặc bằng tờ tiền cần đổi.

Code python:

def MONEY_CHANGE(m):
    M,R=[10,5,1],[]
    for i in M:
        while m>=i:
            R.append(i)
            m=m-i
        if m==0:
            return (R,len(R))

Ngoài ra ta có thể tối ưu code trên bằng một số phân tích số học như sau.

$m=10.q_1+r_1,r_1=5.q_2+r_2,m=10.q_1+5.q_2+r_2=5(2.q_1+q_2)+r_2$ 

Suy ra số lượng xu đổi ít nhất là $m//10+r_1//5+r_2=m//10+(m\%10)//5+m\%5$

Lab 2.3. Tối đa doanh thu từ vị trí đặt quảng cáo trực tuyến

Bạn có n quảng cáo cần bố trí trên một trang mạng nổi tiếng. Với từng quảng cáo, bạn biết người quảng cáo sẽ sẵn sàng chi trả bao nhiêu tiền cho một cú nhấp chuột vào quảng cáo. Bạn đã thiết lập n vị trí trên trang mạng và ước tính số lượng cú nhấp chuột mỗi ngày vào từng quảng cáo ở mỗi vị trí. Mục tiêu của bạn là phân bổ các quảng cáo ở các vị trí khác nhau để tối đa doanh thu.

Lựa chon tham lam là đưa quảng cáo có số tiền lớn nhất cho một cú nhấp chuột vào vào vị trí có số lượng cú nhấp chuột nhiều nhất.

Code python:

def Maximum_Scalar_Product(A,B):
    result=0
    while len(A)>0:
        am,bm=max(A),max(B)
        result+=am*bm
        A.remove(am)
        B.remove(bm)
    return result

Gọi cặp $(a_i,b_i), i=1,...,n$ lần lượt là giá trị lớn nhất của A và B 

$S=a_i.b_q+a_q.b_j, S'=a_i.b_i+a_q.b_q,S'-S=(a_i-a_q).(b_i-b_q) \geq 0$

Thời gian chạy của thuật toán là $O(n^2)$

Ngoài ra ta có thể quy bài toán trên về bài toán sắp xếp hai dãy.

Lab 2.4. Bạn có trách nhiệm thu thập chữ ký của tất cả những người thuê nhà trong một tòa nhà nào đó. Với mỗi người thuê nhà, bạn biết thời gian họ có mặt ở nhà. Bạn muốn thu thập toàn bộ chữ ký sao cho bạn chỉ cần đến tòa nhà ít lần nhất có thể. Mô hình toán học cho bài toán này như sau: Cho một tập hợp các đoạn trên một đường và mục tiêu của bạn là đánh dấu ít điểm nhất có thể trên một đường sao cho mỗi đoạn chứa ít nhất một điểm.

Mô tả bài toán 

Cho một tập hợp n đoạn $\left \{[𝑎_0, 𝑏_0], [𝑎_1, 𝑏_1], . . . , [𝑎_{𝑛−1}, 𝑏_{𝑛−1}]\right \}$ với các tọa độ số nguyên trên một đường, hãy tìm số điểm m tối thiểu sao cho mỗi đoạn chứa ít nhất 1 điểm. Hay, tìm tập hợp các số nguyên $X$ nhỏ nhất sao cho trên mỗi đoạn $[𝑎_𝑖, 𝑏_𝑖]$ có một điểm $x \in X$ thỏa mãn $a_i  \leq  x \leq b_i.$

Code python

import sys
from collections import namedtuple
Segment=namedtuple('segment','start end')#Segment là đoạn
def optimal_points(Segments):
    points=[]
    Segments.sort(key=lambda x: x[1])
    point=Segments[0].end
    points.append(point)
    for s in Segments:
        if s.start>point:
            point=s.end
            points.append(point) 
    return points
if __name__=='__main__':
    input=sys.stdin.read()
    n,*data=map(int,input.split())
    Segments=list(map(lambda x: Segment(x[0],x[1]),zip(data[::2],data[1::2])))
    points=optimal_points(Segments)
    print(len(points))
    for p in points:
        print(p,end=' ')

Lab 2.5. Tối đa số lượng giải thưởng trong một cuộc thi

Giới thiệu bài toán

Bạn sắp tổ chức một cuộc thi thú vị cho trẻ em. Quỹ giải thưởng bạn có là n chiếc kẹo. Bạn muốn dùng những chiếc kẹo này để trao cho 𝑘 vị trí dẫn đầu trong cuộc thi với điều kiện là vị trí cao hơn sẽ nhận được nhiều kẹo hơn. Để khiến nhiều đứa trẻ vui vẻ nhất có thể, bạn sẽ phải tìm ra giá trị k lớn nhất.

Code python

import sys
def optimal_summands(n):
    summands = []
    l=1
    while n>2*l:
        summands.append(l)
        n,l=n-l,l+1
    summands.append(n)
    return summands
if __name__=='__main__':
    input=sys.stdin.read()
    n=int(input)
    summands=optimal_summands(n)
    print(len(summands))
    for i in summands:
        print(i,end=' ')


Xem tiếp >>

Bài 2: GiẢI THUẬT THAM LAM

$\bullet$ Video coursera.org

1. Định nghĩa (vietjack.com)

 Giải thuật tham lam là giải thuật tìm kiếm, lựa chọn giải pháp tối ưu  địa phương ở mỗi bước hi vọng tìm được giải pháp tối ưu toàn cục.
Tức là lựa chọn giải pháp tốt nhất ở thời điểm hiện tại và sau đó giải quyết bài toán con sinh ra từ lựa chọn đó. Điều này có thể không tối ưu toàn cục vì không xem xét lại các quyết định cũ.

Bài toán con là một bài toán tương tự và có kích thước nhỏ hơn bài toán ban đầu. 

Các thành phần chính của giải thuật tham lam.

+ Rút gọn thành các bài toán con

  • Thực hiện một số bước ban đầu 
  • Rút gọn thành bài toán tương tự nhưng ở dạng nhỏ hơn
  • Ta gọi là bài toán con
Nước đi an toàn
  • Một nước đi gọi là an toàn nếu nó phù hợp với một số giải pháp tối ưu
  • Không phải tất cả các bước đi đầu tiên đều là an toàn
  • Thường thì lựa chọn tham lam không an toàn

Một lựa chọn tham lam được gọi là nước đi an toàn nếu có một giải pháp tối ưu phù hợp với lựa chọn này.

Phương pháp tổng quát của giải thuật tham lam.
Bài toánchọn tham lamNước đi an toànBài toán con
  1. Ban đầu lựa chọn một tham lam
  2. Chứng minh nó là một nước đi an toàn
  3. Rút gọn thành một bài toán con
  4. Giải bài toán con 
  5. Lặp lại cho đến khi giải được bài toán
Ưu điểm của giải thuật tham lam (noron.vn)
  • Dễ triển khai giải thuật tham lam cho một bài toán nào đó
  • Dễ phân tích Big-O hơn các giải thuật khác
Nhược điểm của giải thuật tham lam
  • Khó chọn nước đi tham lam
  • Khó chứng minh nước đi tham lam là đúng
  • Hầu như giải thuật tham lam đều không chính xác

2. Ví dụ

Một chiếc ôtô khi đổ đầy bình xăng có thể đi được xa nhất là $L(km).$ Hãy tìm số lần đổ xăng ít nhất để đi từ điểm $A$ đến điểm $B$ biết rằng có $n$ trạm xăng $x_1\leq x_2\leq ...\leq x_n$ dọc theo đường đi từ $A$ đến $B$. Lưu ý không tính số lần đổ xăng ở điểm xuất phát.

Phân tích một số lựa chọn tham lam như sau:

  • Đổ xăng tại trạm xăng gần nhất
  • Đổ xăng tại trạm xăng xa nhất có thể đến được
  • Đi cho tới khi hết xăng và hy vọng có cây xăng để đổ

Theo đề bài yêu cầu tìm số lần đổ xăng ít nhất nên lựa chọn tham lam đổ xăng tại trạm xăng xa nhất có thể đến được là tối ưu.

Giải thuật tham lam cho bài toán đổ xăng
  1. Bắt đầu tại A
  2. Đổ xăng tại trạm xăng xa nhất có thể đến được G
  3. Biến G thành A mới
  4. Đi từ A mới tới B với số lần đổ xăng ít nhất
      $A=x_0 \leq x_1 \leq ... \leq x_n \leq x_{n+1}=B$
$\text{MinReffils}(x,n,L)$
$\text{numReffil } \leftarrow 0, \text{ currentReffil } \leftarrow 0$
$\text{while currentReffil } \leq n:$
    $\text{lastReffil } \leftarrow \text{ currenReffil }$
    $\text{while currenReffil } \leq n \text{ and } x[currenReffil+1]-x[lastReffil] \leq L:$
        $\text{currenReffil } \leftarrow \text{ currenReffil }+1$
        $\text{if currentReffil }== \text{ lastReffil}:$
            $\text{return IMPOSSIBLE}$
        $\text{if currentReffil } \leq n:$
            $\text{numReffil } \leftarrow \text{ numReffil }+1$
$\text{return numReffil}$

Trường hợp cụ thể của bài toán đổ xăng
Một chiếc ôtô khi đổ đầy bình xăng có thể đi được xa nhất là 400km. Hãy tìm số lần đổ xăng ít nhất để đi từ điểm $A$ đến điểm $B$ biết rằng có 4 trạm xăng $x_1<x_2<x_3<x_4$ dọc theo đường đi từ $A$ đến $B$, khoảng cách từ điểm xuất phát tới các trạm xăng và đích lần lượt là 200km,375km,550km,750km,950km. Lưu ý không tính số lần đổ xăng ở điểm xuất phát. 
Code python
Giả sử $A=x_0 \leq x_1 \leq x_2 \leq ... \leq x_n \leq x_{n+1}=B$
def MinRefills(x,n,L):
    numRefills,currentRefill=0,0
    while currentRefill<=n:
        lastRefill=currentRefill
        while currentRefill<=n and x[currentRefill+1]-x[lastRefill]<=L:
            currentRefill+=1
        if lastRefill==currentRefill:
            return IMPOSSIBLE
        if currentRefill<=n:
            numRefills+=1
    return numRefills
x,L=[0,200,375,550,750,950],400
n=len(x)-2
print(MinRefills(x,n,L))

Thời gian chạy của thuật toán trên là O(n). Vì biến currentRefill chạy từ 0 đến n, biến này luôn tăng từ một đơn vị và biến numRefills thay đổi từ 0 đến  lớn nhất là n.

Bài toán phân nhóm

Cho một tập hợp $n$ điểm đã xắp thứ tự $x_1,x_2,...,x_n \in \mathbb{R}$. Hãy tìm cách phân nhóm ít nhất cho $n$ điểm trên với điều kiện bất kỳ 2 điểm nào trong cùng một nhóm có khoảng cách nhỏ hơn hoặc bằng 1.

Giải thuật tham lam cho bài toán phân nhóm

  1. Bắt đầu tại điểm ở ngoài cùng bên trái
  2. Loại bỏ tất cả các điểm nằm trên đường thẳng đơn vị khỏi tập hợp $n$ điểm
  3. Lặp lại quá trình trên điến khi không còn điểm nào trong tập hợp.
      $\text{Giả sử }x_1 \leq x_2 \leq ... \leq x_n$
$\text{PointsCoverSorted }(x_1,x_2,...,x_n)$
$R \leftarrow \left\{ \right\}, i \leftarrow 1$
$\text{while }i \leq n:$
    $[l,r] \leftarrow [x_i,x_i+1]$
    $R \leftarrow R \cup {[l,r]}$
    $i \leftarrow i+1$
    $\text{while } i \leq n \text{ and } x_i \leq r:$
        $i \leftarrow i+1$
$\text{return } R$

Code python

def PointsCoverSorted(x):
    R,i=[],0
    n=len(x)-1
    while i<=n:
        [l,r]=[x[i],x[i]+1]
        R.append([l,r])
        i+=1
        while i<=n and x[i]<=r:
            i+=1
    return len(R)

Thời gian chạy của thuật toán là O(n). 

Bài toán xếp ba lô

Bạn có một chuyến đi bộ đường dài và bạn không biết bao lâu thì tới đích. Vì vậy, để an toàn, bạn cần mang theo đủ thức ăn. Giả sử bạn có một chiếc ba lô có thể đựng được $W(kg)$ thực phẩm và $n$ vật phẩm nặng $w_1,w_2,...,w_n$ với giá trị lần lượt là $v_1,v_2,...,v_n$. Bạn hãy chọn thực phẩm bỏ vào ba lô để mang đi sao cho giá trị là cao nhất.

Giải thuật tham lam cho bài toán xếp ba lô

  1. Chọn i sao cho $\frac{v_i}{w_i}$ lớn nhất
  2. Nếu ba lô đựng được hết vật phẩm này thì lấy hết vật phẩm và quay lại bước 1
  3. Ngược lại thì lấy đủ số kg mà ba lô còn thiếu.
$\text{Knapsack}(W,w_1,v_1,...,w_n,v_n)$
$A \leftarrow [0,0,...,0], V \leftarrow 0$
$\text{Lặp lại n lần:}$
    $\text{Nếu } W=0:$
        $\text{return } (V,A)$
    $\text{Chọn i sao cho } w_i>0 \text{ và } \frac{v_i}{w_i} \text{ lớn nhất}$
    $a \leftarrow min(w_i,W)$
    $V \leftarrow V+a.\frac{v_i}{w_i}$
    $W_i \leftarrow W_i-a,A[i] \leftarrow A[i]+a,W \leftarrow W-a$
$\text{return } (V,A)$

Code python

def knapsack(W,w,v):
    V,n=0,len(w)
    A=[0]*n
    i=1
    while i<=n:
        if W==0:
            return (V,A)
        i_max=0
        for j in range(1,n):
            if v[i_max]*w[j]<=v[j]*w[i_max] and w[j]>0:
                i_max=j
        a=min(w[i_max],W)
        V=V+a*(v[i_max]/w[i_max])
        w[i_max],A[i_max],W=w[i_max]-a,A[i_max]+a,W-a 
        i+=1
    return (V,A)

Thời gian chạy của thuật toán là $O(n^2)$

Xem tiếp >>

Bài 6: NGĂN XẾP VÀ HÀNG ĐỢI

 1. Ngăn xếp. Định nghĩa Ngăn xếp: Là một loại dữ liệu trừu tượng và các thao tác có thể dùng: Push(data): Thêm data vào ngăn xếp Top(): Tìm...