Professional Documents
Culture Documents
Final Report of Design and Analysis of Algorithms
Final Report of Design and Analysis of Algorithms
Divide-And-Conquer
I would like to give our gratitude to Mr Nguyễn Chí Thiện for guilding and
assisting in many of your offline as well as online classes . Without your sincere
guidance, I would have a hard time to create and finish the project.
Although I have completed the project, with our lack of knowledge and
experiences there might be some flaws and false data in the process. So I would be
grateful to have your precious opinions and advices on the whole and details to more so
improve our awareness on the project as well as to get better on the future. I sincerely
thank you!
THE PROJECT WAS COMPLETED
AT TON DUC THANG UNVERSITY
I pledge that this is a product of our own project and is under the guidance of Mr.
Nguyễn Chí Thiện. The content of research, results in this subject is honest and not published
in any form before. The data in the tables used for the analysis, comment, and evaluation were
collected by the authors themselves from various sources indicated in the reference section. In
addition, many comments and assessments as well as data from other authors and organizations
have been used in the project, with references and annotations. If any fraud is found, I am fully
responsible for the content of my project. Ton Duc Thang University is not involved in any
copyright infringement or copyright infringement in the course of implementation (if any).
Ho Chi Minh City, month day 2020
Author
Signed
Tạ Huỳnh Công Huyên
Nguyễn Việt Phương
EVALUATION OF INSTRUCTING LECTURER
Confirmation of the instructor
__________________________________________________________
__________________________________________________________
__________________________________________________________
__________________________________________________________
__________________________________________________________
__________________________________________________________
___________________________________________________
+ Brute Force
+ Greedy technique
+ Dynamic programming
Brute force
1. Định nghĩa
Brute force là một cách tiếp cận đơn giản để giải quyết một vấn đề trực tiếp,
thường trực tiếp dựa trên cách tuyên bố vấn đề và định nghĩa của các khái niệm liên quan.
Và thông thường, Brute Force thực sự là chiến lược dễ áp dụng nhất.
Brute Force thường được xem xét việc áp dụng cách tiếp cận bạo lực cho vấn đề
sắp xếp:
A.Selection sort:
Brute Force là một trong những thuật toán đơn giản. Nó thực hiện công việc so sánh các
phần tử trong danh sách.
Ý Tưởng:
+ Danh sách chứa các phần tử sẽ được chia làm hai phần : Sorted và Unsorted
+ Chọn phần tử nhỏ nhất đưa về vị trí đầu tiên của mảng bên Sorted hiện tại và không cần quan
tâm đến nó nữa. Khi đó mảng Unsorted còn n-1 phần tử mảng ban đầu, tiếp tục xét và lặp lại
cho đến khi bên Unsorted còn lại duy nhất 1 phần tử
+Ưu điểm: Thuật toán chạy nhanh hơn khi mảng sắp xếp một phần ( ít phần tử)
+Nhược điểm: Hiệu suất không cao
Psesudo Code
Input: An array A[0..n − 1] of orderable
elements
Output: Array A[0..n − 1] sorted in non-
decreasing order
for i ← 0 to n − 2 do
min ← i
for j ← i + 1 to n − 1 do
if A[j ] < A[min] min ← j
swap A[i] and A[min]
Code:
def SelectSort(array):
n = len(array)
for i in range(n):
min = i
for j in range(i+1, n):
if (array[j] < array[min]):
min = j
temp = array[i]
array[i] = array[min]
array[min] = temp
return array
# Input
array =[3, 1, 5, 7, 11, 105, 55]
print(SelectSort(array))
start_time = time.time()
print("--- %s seconds ---" % (time.time() - start_time))
B.Bubble
sort
Bubble Sort được dùng để so sánh các phần tử liền kề của danh sách và trao đổi
chúng nếu chúng không theo thứ tự. Phương pháp này tìm phần tử lớn nhất trong mảng
đã cho và đặt nó ở vị trí cuối cùng và hoạt động từ phải sang trái, từ lớn nhất đến nhỏ
nhất.
Ý tưởng :
+ Ta So sánh từng cặp phần tử liền kề nhau ( 6 và 2 ) , bắt đầu từ 2 phần tử bên trái
+ Ta thấy được rằng 6 > 2 , nên ta switch vị trí 2 phần từ với nhau
+ Ta thực hiện cho tới khi hết mảng phần tử cần so sánh
+
Ưu
điểm:
- Code ngắn gọn nhất
+ Nhược điểm:
- Giải thuật này rất chậm, hiệu suất thấp( có thể nói là chậm nhất khi so với
các giải thuật cơ bản )
Pseudo code:
//Input: An array A[0..n − 1] of orderable elements
//Output: Array A[0..n − 1] sorted in nondecreasing
order
for i ← 0 to n − 2 do
for j ← 0 to n − 2 − i do
if A[j + 1] < A[j ] swap A[j ] and A[j + 1]
def bubbleSort(array):
for i in range(len(array)):
for j in range(0, len(array) -i -1):
if array[j] > array[j + 1]:
(array[j], array[j + 1]) = (array[j + 1], array[j])
#Input
array = [-9, -15, 5, 15, 100, 52]
bubbleSort(array)
print('Sorted Array :')
print(array)
C.
Sequential search
Sequential search là thuật toán dùng để tìm kiếm một phần tử cho trước trong
một array bằng cách duyệt qua toàn bộ danh sách.
Ý tưởng:
+ Ta duyệt lần lượt từng phần tử của danh sách đó cho đến lúc tìm thấy giá trị mong
muốn theo 1 thứ tự nhất định. ( trái qua phải hoặc phải qua trái )
+ Giải thuật sẽ tìm phần tử cần tìm theo thứ tự từ trái array qua chạy từ phần tử thứ 0 cho
tới khi hết array ( hoặc từ phần tử thứ n)
+ Ưu điểm :
- Giải thuật rất đơn giản khi hiện thực tìm trong một array nhỏ
- Có thể chạy trong array được sắp sếp theo thứ tự và cả không được sắp xếp theo
thứ tự
+ Nhược điểm:
- Không hiệu quả khi thực hiện một mảng array lớn
Pseudo Code
//Input: An array A of n elements and a search key
K
//Output: The index of the first element in A[0..n −
1] whose value is
// equal to K or −1 if no such element is found
A[n]← K
i←0
while A[i] = K do
i←i+1
if i<n return i
else return −1
Code
return -1
array = [ 5, 15, 12, 6, 20, 35, 50, 74]
sequentialsearch(array, 20)
import pylab
pylab.plot(array,"o-")
import pylab
Divide -and-Conquer
Divide-and-Conquer là thuật toán chung được biết đến rộng rãi và rất phổ biến.
-Công việc chia nhỏ và chinh phục:
1. Một bài toán được chia thành nhiều bài toán con cùng loại, lý tưởng nhất là có
kích thước bằng nhau.
2. Bài toán con được giải quyết (thường là đệ quy, mặc dù đôi khi một thuật toán
khác được sử dụng, đặc biệt là khi các bài toán con trở nên đủ nhỏ).
3. Nếu cần, các giải pháp cho các vấn đề con sẽ được kết hợp để có được giải pháp
cho vấn đề gốc
problem of size n
Subproblem 1 Subproblem 2
of size n/2 of size n/2
Solution to Solution to
subproblem 1 subproblem 2
Solution to the
original problem
A. Mergesort
- Mergesort là một ví dụ hoàn hảo về việc áp dụng thành công thuật toán divide
and conquer. Thuật toán này chia mảng cần sắp xếp thành 2 nửa và sắp xếp từng nửa của
mảng sau đó gộp lại thành 1 mảng lớn sau khi đã sắp xếp 2 nửa.
Ý tưởng:
+ Ta chia array thành 2 nửa
+ Sắp xếp
+ Ưu điểm:
- Code đơn giản, dễ hiểu
-Không tốn thêm bộ nhớ
+Nhược điểm:
- không thích hợp cho array lớn do chạy giải thuật sẽ chậm
Pseudo code :
Mergesort(A[0..n − 1])
//Sorts array A[0..n − 1] by recursive mergesort
//Input: An array A[0..n − 1] of orderable elements
//Output: Array A[0..n − 1] sorted in nondecreasing
order
if n > 1
copy A[0..n/2 − 1] to B[0..n/2 − 1]
copy A[n/2..n − 1] to C[0..n/2 − 1]
Mergesort(B[0..n/2 − 1])
Mergesort(C[0..n/2 − 1])
Merge(B, C, A) //see below
Code :
def mergeSort(arr):
if len(arr) > 1:
mid = len(arr)//2
L = arr[:mid]
R = arr[mid:]
mergeSort(L)
mergeSort(R)
i = j = k = 0
while i < len(L) and j < len(R):
if L[i] < R[j]:
arr[k] = L[i]
i += 1
else:
arr[k] = R[j]
j += 1
k += 1
while i < len(L):
arr[k] = L[i]
i += 1
k += 1
while j < len(R):
arr[k] = R[j]
j += 1
k += 1
def printList(arr):
for i in range(len(arr)):
print(arr[i], end=" ")
print()
if _name_ == '_main_':
arr = [12, 11, 13, 5, 6, 7]
print("Given array is", end="\n")
printList(arr)
mergeSort(arr)
print("Sorted array is: ", end="\n") ( ảnh chạy + runtime)
printList(arr)
B. Quick-sort
-Quick-sort Là một thuật toán chạy dựa trên việc phân chia mảng dữ liệu thành các nhóm phần
tử nhỏ hơn.
Ý tưởng:
+ Chọn phần tử chốt (key) từ mảng gốc. Key có thể là đầu mảng , cuối
mảng hoặc giữa mảng.
+ Chia thành 2 mảng : 1 mảng chứa các nhỏ hơn key bên tay trái và 1 mảng chứa
các phần tử lớn hơn key bên tay phải
+ Sau đó ta sẽ chọn tiếp key cho các mảng nhỏ nếu số phần tử lớn hơn hoặc bằng
2 và tiếp tục chia thành 2 mảng như bên trên
+
Ưu điểm:
- Chạy tương đối nhanh
- Không cần bộ nhớ phụ
+Nhược điểm:
- Dễ phát sinh trường hợp đặc biệt , không ổn định
Pseudo code :
quicSort(arr, l, pi -1)
quicSort(arr, pi+1, r)
arr = [10, 7, 8, 9, 1, 5]
n = len(arr)
quickSort(arr,0,n-1)
print ("Sorted array is:")
for i in range(n):
print ("%d" %arr[i]),
C.Binary Tree Traversals
Cây nhị phân T được định nghĩa là một tập hợp hữu hạn các nút hoặc rỗng
hoặc bao gồm một gốc và hai cây nhị phân rời rạc TL và TR được gọi là
cây con trái và phải của gốc.
Ví dụ:
Pseudo code :
Code :
# Recursive function to calculate height of given binary tree
def height(T):
if T is None:
return -1
# recur for left and right subtree and consider maximum depth
return 1 + max(height(T.left), height(T.right))
if_name_=='_main_':
T = Node(15)
T.left = Node(10)
T.right = Node(20)
T.left.left = Node(8)
T.left.right = Node(12)
T.right.left = Node(16)
T.right.eight = Node(25)
Prim’s Algorithm
Thuật toán Prim là một thuật toán tham lam để tìm cây bao trùm nhỏ nhất của một
đồ thị vô hướng liên thông. Nó tìm một tập hợp các cạnh của đồ thị tạo thành một cây
chứa tất cả các đỉnh, sao cho tổng trọng số của các cạnh của cây là nhỏ nhất.
Giống như Kruskal, Prim cũng tìm cây khung nhỏ nhất, tuy nhiên Prim phụ thuộc
vào đỉnh xuất phát của quá trình tìm kiếm.
Ý tưởng:
+ Nạp dần các đỉnh vào cây khung. Mỗi lần chọn một đỉnh chưa nạp sao cho
+ Lấy một đỉnh trong tree làm đỉnh bắt đầu ( Đỉnh D) . Ta sẽ chọn 1 đỉnh nối trực
+ Tiếp đó ta đã có cạnh AD, ta sẽ làm tương tự , chọn đỉnh có đường đi ngắn nhất
khi nối trực tiếp với đỉnh A hoặc D ( F ngắn nhất khi nối với D: 6)
+ Và ta sẽ làm liên tục cho tới khi nối thành công hết tất cả các đỉnh trong tree
Pseudo Code :
Prim(G)
//Prim’s algorithm for constructing a
minimum spanning tree
//Input: A weighted connected graph G =
V,E
Ý tưởng:
+ Ta sẽ sắp xếp và tăng dần trọng số của 2 đỉnh trong tree từ nhỏ nhất tới lớn nhất
và thêm lần lượt vào T=”Rỗng”
- Theo ví dụ thì ta sẽ có (A,D) = 5 và (C,E) = 5 nhỏ nhất , nên T= { (A,D),
(C,E)}
- Sau đó sẽ là (D,F)= 6 , thêm vào T
- Ta sẽ chạy tới khi đủ 6 đỉnh của tree.
-Kết quả của Ví dụ trên sẽ là:
T={ (A,D), (D,F), (A,B), (B,E), (E,C), (E,G) }
Pseudo Code :
Kruskal(G)
//Kruskal’s algorithm for constructing a minimum spanning tree
//Input: A weighted connected graph G =
V,E
) ≤ ... ≤ w(ei|E|
)
ET ← ∅; ecounter ← 0 //initialize the set of tree edges and its size
k ← 0 //initialize the number of processed edges
while ecounter < |V | − 1 do
k←k+1
if ET ∪ {eik
} is acyclic
ET ← ET ∪ {eik
}; ecounter ← ecounter + 1
return ET
Code :
from collections import defaultdict
# Class to represent a graph
class Graph:
def __init__(self, vertices):
self.V = vertices # No. of vertices
self.graph = [] # default dictionary
# to store graph
# function to add an edge to graph
def addEdge(self, u, v, w):
self.graph.append([u, v, w])
# A utility function to find set of an element i
# (uses path compression technique)
def find(self, parent, i):
if parent[i] == i:
return i
return self.find(parent, parent[i])
# A function that does union of two sets of x and y
# (uses union by rank)
def union(self, parent, rank, x, y):
xroot = self.find(parent, x)
yroot = self.find(parent, y)
# Attach smaller rank tree under root of
# high rank tree (Union by Rank)
if rank[xroot] < rank[yroot]:
parent[xroot] = yroot
elif rank[xroot] > rank[yroot]:
parent[yroot] = xroot
# If ranks are same, then make one as root
# and increment its rank by one
else:
parent[yroot] = xroot
rank[xroot] += 1
# The main function to construct MST using Kruskal's
# algorithm
def KruskalMST(self):
result = [] # This will store the resultant MST
# An index variable, used for sorted edges
i = 0
# An index variable, used for result[]
e = 0
# Step 1: Sort all the edges in
# non-decreasing order of their
# weight. If we are not allowed to change the
# given graph, we can create a copy of graph
self.graph = sorted(self.graph,
key=lambda item: item[2])
parent = []
rank = []
# Create V subsets with single elements
for node in range(self.V):
parent.append(node)
rank.append(0)
# Number of edges to be taken is equal to V-1
while e < self.V - 1:
# Step 2: Pick the smallest edge and increment
# the index for next iteration
u, v, w = self.graph[i]
i = i + 1
x = self.find(parent, u)
y = self.find(parent, v)
# If including this edge does't
# cause cycle, include it in result
# and increment the indexof result
# for next edge
if x != y:
e = e + 1
result.append([u, v, w])
self.union(parent, rank, x, y)
# Else discard the edge
minimumCost = 0
print "Edges in the constructed MST"
for u, v, weight in result:
minimumCost += weight
print("%d -- %d == %d" % (u, v, weight))
print("Minimum Spanning Tree" , minimumCost)
# Driver code
g = Graph(4)
g.addEdge(0, 1, 10)
g.addEdge(0, 2, 6)
g.addEdge(0, 3, 5)
g.addEdge(1, 3, 15)
g.addEdge(2, 3, 4)
# Function call
g.KruskalMST()
Dynamic programming:
+Dynamic programming là giải thuật dùng để giải các bài toán có các Bài toán
con chồng chéo lên nhau.
+Dynamic programming là một phương pháp để tối ưu hóa quá trình ra quyết định
nhiều giai đoạn.
+Nó là một kỹ thuật thiết kế thuật toán luôn được coi là để giải quyết vấn đề tối ưu
hóa.
Pseudo
//Input: Array C[1..n] of positive integers indicating the coin
values
//Output: The maximum amount of money that can be picked up
F[0]← 0; F[1]← C[1]
for i ← 2 to n do
F[i]← max(C[i] + F[i − 2], F[i − 1])
return F[n]
Ý tưởng :
1:Đầu tiên chúng ta sẽ chia số phần tử ( hay số xu) ra thành 2 nhóm :
- Nhóm có đồng xu cuối cùng : F(i-1)
- Nhóm không có đồng xu cuối cùng: Ci + F(i-2)
2: Chúng ta theo đề có: F(0) = 0 , F(1)=C1 , F[i] = max(C[i] + F[i − 2], F[i − 1]) , i>1
3: Dựa vào đề, ta sẽ tạo một table :
Code:
2: Warshall Algorithm
+ Thuật toán Warshall là một thuật toán để tìm đường đi ngắn nhất giữa tất cả các
cặp đỉnh trong một đồ thị có trọng số
+ Thuật toán dựa trên ý tưởng: Nếu có đường đi từ i tới k và từ k tới j nhỏ hơn đường đi
hiện tại từ i tới j thì ta sẽ cập nhật đường đi từ i tới j thành đường đi từ i tới k cộng với từ k tới j.
Ta gọi k là đỉnh trung gian của i và j. Như vậy sau khi thực hiện thuật toán, sẽ có một số cạnh
“ảo” được sinh ra, tức là các cạnh không nối trực tiếp giữa hai đỉnh.
Example:
Pseudo
//Input: The adjacency matrix A of a digraph with n vertices
//Output: The transitive closure of the digraph
R(0) ← A
for k ← 1 to n do
for i ← 1 to n do
for j ← 1 to n do
R(k)[i, j ] ← R(k−1)
[i, j ] or (R(k−1)
return R(n)
Code:
from collections import defaultdict
A = defaultdict(int)
#input
A[(1,2)] = 1
A[(2,1)] = 1
A[(2,3)] = 1
A[(3,2)] = 1
A[(4,5)] = 1
A[(5,4)] = 1
def warshall(A,n,u,v):
R = defaultdict(int)
#function to check vertices u,v are connected
for key in A.keys():
R[(0,) + key ] = A[key]
for k in range(1,n+1):
for i in range(1,n+1):
for j in range(1,n+1):
if (R[(k-1,i,j)] ==1 ) or (R[(k-1,i,k)] == 1 and R[(k-1,k,j)] ==1):
R[(k,i,j)] =1
return R[(n,u+1,v+1)]
print(warshall(A,5,0,2))
Knapsack Problem
Ví dụ: cho trước n vật có trọng lượng w1, ..., wn và có giá trị
v1, ..., vn và một gói dung lượng W, hãy tìm tập con các vật có giá trị lớn nhất vừa với ba lô.
Ý tưởng:
Đầu tiên chúng ta sẽ chia tất cả các vật thành 2 nhóm:
+ Nhóm đầu sẽ không có item cuối
F (i, j ) = F (i − 1, j ).
+ Nhóm thứ 2 sẽ có item cuối
F (i, j ) = vi + F (i − 1, j − wi).
Let F (i, j ) là giá trị của một giải pháp tối ưu. Khi đó chúng ta có:
-- 0 1 2 3 4 5
0 0 0 0 0 0 0
1 0 0 12 12 12 12
2 0 10 12 22
3 0
4 0
5 0
w1 = 2, v1= 12
w2 = 1, v2= 10
w3 = 3, v3= 20
w4 = 2, v4= 15
Pseudo Code
+ If we solve this by performing the The brute force algorithm, then it will compare each array
element with the rest of the array , and this is very time consuming.
+ Instead , If we presort the array then the algorithm will be faster , be less time-consuming
because the algorithm only needs to compare adjacent elements for uniqueness.