You are on page 1of 62

kiểm tra giữa kỳ: ôn tất cả các buổi học, sd tài liệu giấy, làm bài trên giấy,

dạng những câu hỏi ngắn như câu hỏi lý thuyết hoặc viết 1 chương trình
ngắn, tìm chiều cao tìm số nút
thi cuối kỳ trắc nghiệm, sử dụng tài liệu giấy (dự kiến không dc sd)
nắm vững lý thuyết,công thức, cấp độ độ phức tạp của các thuật toán,câu hỏi
lý thuyết, nắm được những giải thuật chính,40-50c
BÀI TẬP :
Bài tập 1. Đệ quy
Hiện thực giải thuật đệ quy Euclid tính ước số chung lớn nhất của 2 số
nguyên dương.
#include<stdio.h>

int UCLN(int a, int b){

if(b == 0)

return a;

if ( a%b==0)

return b;

return UCLN (b,a%b);

int main(){

int a,b;

printf("Nhap so nguyen duong a=", a);

scanf("%d",&a);

printf("Nhap so nguyen duong b=", b);

scanf("%d",&b);

printf("UCLN(%d,%d)=%d",a,b,UCLN(a,b));
}
1. Viết hàm
void Swap(List *list, int p1, int p1) {
....
}
thực hiện việc hoán đổi 2 phần tử ở vị trí p1 và p2 của danh sách đặc
(ArrayList).
Ví dụ:
list=(1,2,3,4), p1=0, p2=2 => list = (3,2,1,4)
list=(1,2,3,4), p1=0, p2=3 => list = (4,2,3,1)
list=(1,2,3,4), p1=1, p2=1 => list = (1,2,3,4)
1.

void Swap(List *list, int p1, int p2) {

int temp=0;

temp=list->data[p1];

list->data[p1] = list->data[p2];

list->data[p2] = temp;
}
2. Viết hàm
void Reverse(List *list) {
...
}
thực hiện việc đảo ngược vị trí các phần tử trong danh sách.
Ví dụ:
list=(1,2,3,4) => list=(4,3,2,1)
list=(1) => list=(1)
2.
void Reverse(List *list) {
int i;
for (i=0; 2*i<list->length-1;i++)
Swap(list , i , list->length- i-1); }
Buổi 1:
Chương 1: Tổng quan giải thuật ứng dụng trong kinh doanh
Nội dung học:
20% thi giữa kì
20% tiểu luận nhóm
50% thi cuối kỳ trắc nghiệm
tổng cộng 7 chương
-Vai trò của tổ chức dữ liệu, tại sao phải tổ chức dữ liệu?
+dữ liệu: là những số liệu rời rạc của một sự vật hiện tượng cần quản lý.cd:
nhân viên: mã, họ tên, địa chỉ, sdt,lương…
+Thao tác trên dữ liệu: tìm kiếm, sửa đổi, xóa, biến đổi dữ liệu, lập báo cáo…
-Mối quan hệ giữa cấu trúc dữ liệu và giải thuật:

+Phương án 1: sử dụng mảng 1 chiều


+Phương án 2: sử dụng mảng hai chiều
+phương án 3: sử dụng 3 mảng độc lập
+phương án 4: sử dụng cấu trúc lớp phức hợp
+tổ chức cấu trúc dữ liệu trước, sau đó xây dựng giải thuật xử lý hoặc cấu
trúc dữ liệu quyết định giải thuật
-Đặc trưng của giải thuật:
+Tính xác định: mỗi một yêu cầu sẽ có 1 giải thuật xác định của nó
+Tính dừng (hữu hạn): sau một khoảng tgian nào đó công cụ tìm kiếm trả về
kết quả. tgian trả về càng nhanh càng tốt,mong muốn trả về kết quả hữu hạn
không phải vô hạn (tính then chốt)
+Tính đúng đắn: Từ đúng tuyệt đối chuyển sang đúng tương đối (tính then
chốt)
+Tính phổ dụng: đã được chuẩn hóa và được nhiều người sử dụng hay chưa
+Tính khả thi: giải 1 pt bậc 2 cần siêu máy tính -> không khả thi, chi phí bỏ ra
khi thực hiện giải thuật, tính khả thi có khả năng thay đổi theo thời gian.
-Diễn đạt giải thuật:
+Dạng lưu đồ( sơ đồ khối)
+Dạng ngôn ngữ tự nhiên ( ngôn ngữ liệt kê từng bước)
+Ngôn ngữ lập trình
+dạng mã giả: một loại ngôn ngữ, không yêu cầu bắt buộc như ngôn ngữ lập
trình
Ví dụ 3: tìm phần tử lớn nhất trong mảng A
A[6]={2 3 5 4 6 10} – Giải thuật timMax(A, n)
Input: Mảng A, gồm n số nguyên
Output: Giá trị lớn nhất của A
Max <-A[0]
for i <-1 to n − 1 do
if A[i] >Max then
Max<- A[i]
return Max
(i chạy từ số 3 trở đi,chạy lần lượt qua các phần tử, nếu A[i] lớn hơn max gán
A[i] cho max nhỏ hơn hoặc bằng thì giữ nguyên…)
-Các kiểu dữ liệu cơ bản:
+chỉ lưu các giá trị đơn, không lưu được các đối tượng dữ liệu phức hợp, chỉ
có thể lưu trữ những giá trị đơn
-Kiểu dữ liệu trừu tượng(abstract data type):
+là một mô hình toán kết hợp với các phép toán trên mô hình này, là sự trừu
tượng các dữ liệu cơ bản và các thủ tục
-kiểu dữ liệu có cấu trúc:
+mảng, chuỗi, tập tin, bản ghi…
vd: mã sinh viên: chuỗi ký tự, tên sinh viên: chuỗi ký tự,ngày sinh: date time,
điểm thi: interger…
-Thiết kế giải thuật:
+b1: phân tích thiết kế
+b2: lập trình

+Chiến lược thiết kế:


● chia để trị (divide and conquer): module hóa và giải quyết bài toán
1.Từ trên xuống (Top-Down Design).
2.Tinh chỉnh từng bước: biểu diễn ý tưởng bằng nn tự nhiên, cụ thể
từng phần, thay đổi bằng ngôn ngữ chương trình, cuối cùng ta có chương
trình
● quy hoạch động (dynamic programming)
● quay lui (backtracking): vét cạn
● tham lam (greedy method): AI
+ngôn ngữ lập trình: pascal, c/c++,c#,java
-phân tích giải thuật:
+độ phức tạp không gian(space complexity): dung lượng bộ nhớ mà thuật
toán đòi hỏi
+Độ phức tạp thời gian(time complexity): thời gian thực hiện thuật toán
-Phân tích thời gian thực hiện giải thuật:
+phụ thuộc vào các yếu tố: dữ liệu vào,tốc độ thực hiện các phép toán của
máy tính (phần cứng),trình biên dịch
+Gọi n là kích thước của dữ liệu vào, thời gian thực hiện của giải thuật có thể
biểu diễn là một hàm của n: hàm T(n)
-tiến trình phân tích thời gian thực hiện giải thuật:
+b1: ptich kích thước dữ liệu vào
+b2: phân tích(toán học) tìm ra giá trị trung bình, và giá trị xấu nhất cho mỗi
đại lượng cơ bản
-Ký pháp để đánh giá độ phức tạp của giải thuật: (xem slide)
-xác định độ phức tạp tính toán:
+thời gian thực hiện không phụ thuộc vào số lượng phần tử đầu vào, độ phức
tạp bằng hằng số O(1),thời gian là như nhau
+độ phức tạp logarit: O(logN) chậm hơn độ phức tạp hằng số khi tăng kích
thước đầu vào.Binary search: O(log2N)
+Độ phức tạp tuyến tính O(N): khi tăng kích thước đầu vào bao nhiêu, thời
gian tăng bấy nhiêu, tăng theo kích thước của N.(cao hơn logarit)
+Độ phức tạp O(NlogN): thấp hơn O(N^2)
+O(N^2)
-Các quy tắc tổng quát:

-một số vidu:
+thuật toán bubblesort: sắp xếp dãy,
+biến temp: biến tạm
-Phân tích các hàm đệ quy:
+hàm tính giai thừa:

+hạn chế sử dụng pp đệ quy


-sự phân lớp của giải thuật:

-các trường hợp đánh giá độ phức tạp:


+tốt nhất
+xấu nhất
+trung bình

Trắc nghiệm:
CÂU HỎI VÀ BÀI TẬP CHƯƠNG 1
Câu 1: Cấu trúc dữ liệu là …
d. Cách lưu trữ dữ liệu trong bộ nhớ truy cập ngẫu nhiên (RAM), sao cho nó
có thể được sử dụng một cách hiệu quả
Câu 2: Bằng cách chạy thử 1 thuật toán với 1 bộ dữ liệu, ta có thể khẳng
định điều gì?
b. Khẳng định thuật toán sai nếu cho kết quả sai
Câu 3: Hàm thể hiện độ phức tạp có dạng thường gặp là gì?
a. log2n, n, nlog2n
b. n^2 , n^3
c. 2^n, 3^n , n! , n^n
d. Cả ba câu trên đều đúng
Câu 4: Khi đánh giá độ phức tạp của câu lệnh If ta cần đánh giá điều gì?
c. Độ phức tạp của việc kiểm tra điều kiện và độ phức tạp của câu lệnh bên
trong thân If
Câu 5: Độ phức tạp trong trường hợp xấu nhất của thuật toán sau là gì?
boolean S(int A[], int n, int X)
{ for (int i = 0; i<n; i++)
{ if (A[i] == X)
return true;
}
return false;
}
b. O(n)
Câu 6: Thời gian thực hiện của một thuật toán được tính toán ra kết quả
là T(n) = 2*n + n*logn. Ký hiệu O của độ phức tạp thuật toán trên là gì?
c. O(nlogn)
Câu 7: Phát biểu nào sau đây sai?
c. O(2n) có độ phức tạp nhỏ hơn O(3n+5)
Câu 8: Độ phức tạp của thuật toán tính giá trị của biểu thức 1+2+3+...+n
là gì?
b. O(n)
Câu 9: Độ phức tạp của thuật toán tính giá trị của biểu thức n²+35n+6 là
gì?
b. O(n^2)
Câu 10: Thuật ngữ nào dưới đây được sử dụng để mô tả một thuật toán
có độ phức tạp là O(n)?
b. Độ phức tạp tuyến tính (tuần tự)
Câu 11: Biểu diễn công thức (n - 2)*(n - 4) sử dụng ký hiệu O để biểu
diễn độ phức tạp là gì?
d. Cả ba câu trên đều sai
Câu 12: Thường ta coi T(n) là thời gian thực hiện chương trình trong
trường hợp xấu nhất trên dữ liệu đầu vào có kích thước n, tức T(n) là
gì?
d. Thời gian lớn nhất để thực hiện chương trình đối với mọi dữ liệu vào có
cùng kích thước n
Câu 13: Công cụ nào sau đây được dùng để diễn đạt thuật toán?
a. Ngôn ngữ lập trình
b. Lưu đồ (sơ đồ khối)
c. Ngôn ngữ tự nhiên
d. Cả ba câu trên đều đúng
Câu 14: Phát biểu nào sau đây là sai khi chọn để điền vào dấu … trong
phát biểu sau: "Thuật toán là một dãy hữu hạn các thao tác, trong đó
…"?
c. Thứ tự thao tác không quan trọng
Câu 15: Cho biết kết quả khi thực hiện thuật toán sau với a[]= {-3, -3, 15,
-3}; n= 4; x= -3:
int FindX(int a[], int n, int x)
{ int i;
for (i= n-1; i >= 0; i--)
if (a[i]==x) return (i);
return (-1);
}
=> =3
Câu 16: Quá trình giải quyết bài toán được thực hiện bằng việc chia bài
toán lớn thành các bài toán nhỏ hơn để giải quyết, được áp dụng theo
chiến lược nào sau đây?
a. Chia để trị
Câu 17: Cho mảng các số nguyên A[] và thuật toán sau, cho biết độ
phức tạp T(n) của thuật toán là gì?
Alg1(int A[], int n)
{ for (k= 0; k<n-1, k++)
for (j=k; j<=k+1;j++)
A[j] = A[k] + 1
}
=> O(n^2)
Câu 18: Lý thuyết thuật toán quan tâm điều gì?
d. Cả ba câu trên đều đúng
Câu 19: Giả sử ta có hai thuật toán P1 và P2 với thời gian thực hiện
tương ứng là T1(n) =100n^2 và T2(n) = 5n^3. Với n < 20, thuật toán nào
sẽ thực hiện nhanh hơn?
d. Thuật toán P2
Câu 20: Trong đoạn chương trình có hai vòng lặp for lồng nhau, qui tắc
nào sau đây là chính xác để đánh giá độ phức tạp của thuật toán?
b. Qui tắc nhân
CHƯƠNG 2:
-Hàm đệ quy:
+Hàm (phương thức) được gọi là đẹ quy nếu quá trình thực hiện nó tự gọi
đến chính mình
+đệ quy trực tiếp: nằm bên trong hàm (phương thức)(*)
+đệ quy gián tiếp: Nếu hàm(phương thức) gọi tới hàm(phương thức) khác,
mà hàm (phương thức) này lần lượt gọi tới hàm (phương thức) ban đầu.
+Tính xác định
+tính hữu hạn( tính dừng): hàm đệ quy hay gặp khó khăn, không biết liệu hàm
có dừng hay không hoặc chạy bao lâu mới dừng (điểm yếu).Để thực hiện
dừng, phải có 2 phần: phần đệ qui(yêu cầu gọi đẹ qui, thực hiện lại giải thuật
ở cấp độ thấp hơn,thường được đặt trong một điều kiện), phần không đệ qui
(phần cơ sở-phần làm dừng tính đệ qui,gồm các trường hợp không cần thực
hiện lại giải thuật, dừng trực tiếp giải quyết được bài toán)
+tính đúng đắn
+ưu điểm: mạnh ở tính lặp,diễn giải đệ quy có thể ngắn gọn hơn, định nghĩa
một tập hợp rất lớn các đối tượng bằng 1 số các câu lệnh hữu hạn
+khuyết điểm: không xác định được bước lặp (không xác định được tính
dừng), gây tràn đệ quy (tràn bộ nhớ đệ quy)=>máy tính báo lỗi
=>tìm cách xử lý (khử đệ quy), chỉ khi nào không thể dùng giải thuật lặp ta
mới nên sử dụng thuật toán đệ qui.(đệ qui: Mạnh, dễ gọi nhưng khó quản lý,
có thể gây tràn bộ nhớ nếu máy tính yếu,vòng lặp quá lớn)
-cơ chế hoạt động của đệ qui:
+hoạt động theo cơ chế LIFO(last in first out) ( khác cơ chế FIFO)
+Dùng stack để lưu vết dữ liệu và chỉ thị lệnh,

+biến cục bộ: được gọi lại khi đang lưu giữ trạng thái
-Phân loại đệ qui:
+đệ qui tuyến tính: có duy nhất 1 lời gọi hàm gọi lại chính nó
+đệ qui nhị phân: trong thân hàm có 2 lời gọi hàm gọi lại chính nó một cách
tường minh
+đệ qui phi tuyến: Trong thân của hàm có lời gọi hàm gọi lại chính nó được
đặt bên trong vòng lặp
+đệ qui tương hỗ: Trong thân của hàm này có lời gọi hàm đến hàm kia và
trong thân của hàm kia có lời gọi hàm tới hàm này.
-Khử đệ qui:
+nên dùng vòng lặp vì đệ qui rất tốn bộ nhớ, xử lý chậm: khử đệ qui bằng giai
thừa, số fibonacci
BUỔI 3:
Nắm dc 5 chương cslt
chương 1:
CHƯƠNG 2:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
int main()
{

printf("CHAO MUNG DEN VOI NGON NGU C");


getch();
return 0;

++x:
x++:
printf: yêu cầu nhập
scanf:

tính tổng a và b nhập từ bàn phím:


#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>
int main()
{

int a,b;
printf("nhap 2 so a va b: ");
scanf("%d%d",&a,&b);
printf("tong a cong b: %d ",a+b);
return 0;

}
Chương 3:
Cấu trúc điều kiện:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>

int main()
{
float a,b,x;
printf ("nhap hai so a,b: ");
scanf("%f%f",&a,&b);
if (a==0)
if (b==0) printf("vo so nghiem.\n");
else printf("vo nghiem.\n");
else{
x=-b/a;
printf("mot nghiem x=%8.2f\n",x);
}
return 0;
}

câu lệnh for:


#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>

int main()
{
int i,n;
printf("nhap vao so n: ");
scanf("%d",&n);
for (i=1;i<=n;i++)
printf("%d",i);
return 0;
}

câu lệnh while:


#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>

int main()
{
int k, dem=0;
scanf("%d",&k);
while (k!=0)
{
dem++;
scanf("%d",&k);
}
printf("so luong cac so nguyen da nhap la: %d",dem);
getch();
}
CHƯƠNG 4
Chương trình con:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>

int Tong(int a,int b);

int main()
{
int a=2912, b=1706;
int sum= Tong(a,b);
printf("tong la: %d",sum);
return 0;
}
int Tong(int a, int b)
{
return a+b;
}

chụp màn hình máy tính màu đen, nhập vào dòng đầu tiên “chương trình của
nguyễn văn a”, trả lời câu hỏi lý thuyết, câu hỏi thực hành
bài 1:Viết chương trình tính giai thừa của số tự nhiên n, với n được
nhập từ bàn phím slide 13
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>

int Giaithua(int n)
{
if((n==1)||(n==0)) { return 1;}
return Giaithua(n-1)*n;
}
int main(){
int Giaithua(int n);
}

bài 2: slide 19
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>

long Fibonaci (int n)


{
if(n==0 || n==1)
return 1;
return Fibonaci(n-1) + Fibonaci(n-2);
}

int main(){
int a,b;
printf("chuong trinh tinh fibonaci cua: le tu quynh nhi \n");
a=5;/*thay bang lenh print yeu cau nhap so a*/
b= Fibonaci (a);
printf("%d",b);
getch();
return 0;
}
—------------
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>

long Fibonaci (int n)


{
if(n==0 || n==1)
return 1;
return Fibonaci(n-1) + Fibonaci(n-2);
}

int main(){
int a,b;
printf("chuong trinh tinh fibonaci cua: le tu quynh nhi \n");
printf("nhap a: ");
scanf("%d",&a);
b= Fibonaci (a);
printf("%d",b);
getch();
return 0;
}
—------------
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>

long Fibonaci (int n)


{
if(n==0 || n==1)
return 1;
return Fibonaci(n-1) + Fibonaci(n-2);
}

int main(){
int a,b;
printf("chuong trinh tinh fibonaci cua: le tu quynh nhi \n");
printf("nhap a: ");
scanf("%d",&a);
b= Fibonaci (a);
printf("giai thua fibonaci thu %d la: %d",a,b);
getch();
return 0;
}

bài 3: Viết chương trình tính tổng của n số tự nhiên đầu tiên, với n được
nhập từ bàn phím(slide 17)
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>

long TongS (int n)


{
if(n==0)
return 0;
return ( TongS(n-1) + n );
}
int main(){
int n,S;
printf("chuong trinh tinh tong n so tu nhien cua: le tu quynh nhi \n");
printf("nhap n: ");
scanf("%d",&n);
S=S*(n-1)+n;
printf("tong %d so tu nhien dau tien: %d",n,S);
getch();
return 0;
}

PHẦN CÂU HỎI:


Câu 1: Tại sao phải khử đệ quy?
⇨ Vì đệ quy tốn nhiều bộ nhớ, làm hệ thống xử lý chậm.
Câu 2: Có thể khử đệ quy bằng phương pháp nào?
⇨ Có 2 phương pháp: Vòng lặp và Mảng
Câu 3: Trình bày đặc trưng của thuật toán đệ qui quay lui (back tracking)?
⇨ sử dụng phương pháp vét cạn
Câu 4: Cho bài toán có thể giải bằng thuật toán đệ quy hoặc thuật toán lặp.
Cả 2 thuật toán đều có độ phức tạp là O(n). Trong trường hợp này tại sao nên
chọn thuật toán lặp?
⇨ Nên chọn vòng lặp vì nó ít gây ra lỗi hơn
Câu 5: Tìm một từ trong từ điển là một kiểu thuật toán đệ qui đúng hay sai?
⇨ Đúng
Câu 6: Hai vấn đề cần xem xét khi cài đặt thuật toán đệ quy là gì?
● Kích thước của biến cục bộ

● Độ sâu tối đa của hàm đệ quy (vì nó đưa vào stack). Độ sâu ít thì nhanh hơn

bài 4: Viết chương trình chuyển đổi cơ số 10 sang cơ số 2


#include <stdio.h>
#include <math.h>

int main()
{
printf("chuong trinh cua le tu quynh nhi\n");
int n,t;
printf("nhap so: ");
scanf("%d",&n);

void H10toH2(int n)
{
if (n <= 0) return;
int t = n%2;
H10toH2(n/2);
printf("%d",t);

Bài 5: Viết chương trình tính 𝑪𝒏 𝒌


#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>

int C(int k, int n) {


if (k == 0 || k == n) return 1;
if (k == 1) return n;
return C(k - 1, n - 1) + C(k, n - 1);
}
int main (){
int n,k,C;
printf("chuong trinh cua le tu quynh nhi\n");
printf("nhap n: ");
scanf("%d",&n);
printf("nhap k: ");
scanf("%d",&k);
printf("to hop: ",C);

—-----------------------------------------------
#include<iostream>
using namespace std;
int C(int k, int n) {
if (k == 0 || k == n) return 1;
if (k == 1) return n;
return C(k - 1, n - 1) + C(k, n - 1);
}
int main(){
int n, k;
cout << "Nhap k: ";
cin >> k;
cout << "Nhap n: ";
cin >> n;
cout << "To hop bang: " << C(k, n);

return 0;
}

Bài 6: Viết chương trình minh họa bài toán Tháp Hà Nội bằng đệ quy
Bài 7: Viết chương trình tính ước số chung lớn nhất của 2 số. Minh họa quá
trình hoạt động của giải thuật.

Bài 8: Viết chương trình sắp xếp mảng số nguyên tăng dần. Minh họa quá
trình hoạt động của giải thuật.
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>

void sapxep(int a[],int n){


if(n==1) return;
else
{
sapxep(a,n-1);
if(a[n-1]<a[n-2])
{
int temp = a[n-1];
a[n-1]=a[n-2];
a[n-2]=temp;
sapxep(a,n-1);
}
}
}
int main()
{
int a[5]= {4,3,7,6,9};
int i,n=5;
printf("CHUONG TRINH SAP XEP MANG CUA LETUQUYNHNHI");
sapxep (a,5);
printf("\nket qua sap xep: ");
for(i=0;i<n;i++)
printf("%d, ",a[i]);
getch();
return 0;
}

Bài 14:
#include <conio.h>
#include <stdio.h>
int max(int a[],int n)
{
if(n==1) return a[0];
if (a[n-1]>max(a,n-1)) return a[n-1];
return max(a,n-1);
}
int min(int a[],int n)
{
if (n==1) return a[0];
if (a[n-1]<min(a,n-1)) return a[n-1];
return min(a,n-1);
}
int main()
{
int a[50],n,i;
printf("CHUONG TRINH TIM MAX VA MIN CUA 1 MANG SO
NGUYEN CUA LETUQUYNHNHI\n");
printf("so luong phan tu: ");
scanf ("%d",&n);

if(n<=0) printf("so luong phan tu ban nhap khong hop le");


else
{
for(i=0;i<n;i++)
{
printf("a[%d]= ",i);
scanf("%d",&a[i]);
}
printf("\n max= %d",max(a,n));
printf("\n min= %d",min(a,n));
}
}

BUỔI 5: CHƯƠNG 3 : DANH SÁCH

-Danh sách là tập hợp các phần tử có kiểu dữ liệu xác định và giữa chúng có
một mối quan hệ nào đó. Số phần tử của danh sách gọi là chiều dài của DS.
Danh sách có chiều dài bằng 0 gọi là DS rỗng
-Danh sách đặc là danh sách mà không gian bộ nhớ lưu trữ các phần tử
được đặt liên tiếp nhau.
+elem A[spt] <=> int A[10] Ví dụ: int a[10]; int float b[20];
+Mảng được quản lý bằng một địa chỉ trong bộ nhớ máy tính và các phần tử
được bố trí tuần tự liên tiếp theo chỉ số. bắt đầu từ số 0, địa chỉ đối với danh
sách đặt không thay đổi, kích thước cố định, nội dung có thể thay đổi. dữ liệu
liên tiếp cô đặc lại với nhau gọi là danh sách đặc
+Ưu điểm: – Mật độ sử dụng 100% – Dễ dàng truy xuất đến từng phần tử:
+Nhược điểm: – Độ phức tạp thuật toán thêm/bớt phần tử vào/ra danh sách
là khá cao T(n)max=O(n) (insert vào tốn chi phí lớn) – Lãng phí khi trong danh
sách có nhiều phần tử cùng giá trị (không loại bỏ được các phần tử trùng
nhau)
+khai báo cấu trúc danh sách đặc:
● cấu trúc phức hợp: vd: sinh viên có mssv, tên, địa chỉ…
● khai báo:
#define MAXLIST 200
typedef struct DanhSachKe{
int num (chiều dài hiện tại của dsach liền kề);
int nodes[MAXLIST];
}List;
● Khởi tạo danh sách:
void Init(List &plist){ (đọc và ghi lại dữ liệu)
plist.num=0;
}
● xác định số nút (số phần tử)
int ListSize(List plist){
return plist.num;
}
● Kiểm tra danh sách rỗng
int IsEmpty(List plist){
return (plist.num==0); // Nếu rỗng trả về giá trị 1 (true), ngược lại trả về
giá trị 0 (false)
}
● Kiểm tra danh sách đầy
int IsFull(List plist){
return (plist.num==MAXLIST); // Nếu đầy trả về giá trị 1 (true), ngược lại
trả về giá trị 0 (false)
}
● Truy xuất một phần tử trong danh sách
int Retrieve(đọc)(List plist, int pos){
if(pos<0 || pos>=ListSize(plist))
{
printf("Vi tri %d khong hop le",pos); return -1;}
else
{ if(IsEmpty(plist))
printf("danh sach bi rong!!!");
else
return plist.nodes[pos];
}
}
● Thêm một phần tử vào danh sách:
void Insert(List &plist, int pos, int x){ int i;
if (pos<0 || pos>plist.num)
{ printf("Vi tri chen khong hop le !"); return; }
else{ if(IsEmpty(plist) ){
printf("Danh sach rong !");
return; }
else{
for (i=plist.num-1;i>=pos;i-- )
{ plist.nodes[i+1]=plist.nodes[i]; }
plist.nodes[pos]=x;
plist.num++; }
}
}

vdchuong3:
#include <stdio.h>
#include <conio.h>
#include <string.h>

#define MAXLIST 100


typedef struct DanhSachDac{
int num;
int nodes[MAXLIST];
}List;

//Khoi tao
void Init(List &plist){
plist.num=0;
}
// Xac dinh so nut cua danh sách:
int ListSize(List plist){
return plist.num;
}
// Kiem tra danh sách rong:
int IsEmpty(List plist){
return (plist.num==0);//Ham tra ve mot gia tri, neu =1 tuc la ds
rong, =0 la ds khong rong
}
// Kiem tra danh sách day
int IsFull(List plist){
return (plist.num==MAXLIST);
}
//Nhap danh sach
void Input(List &plist)
{
int i;
do
{
printf("\nNhap so phan tu: ");
scanf("%d",&plist.num); //1D array: scanf("%d",&n);
} while (plist.num<0);// neu so phan tu <0 thi yeu cau nhap lai

if(IsFull(plist))// tuong duong voi if(IsFull(plist)==1)


{
printf("Danh Sach bi day!!!");
return;// thoat chuong trinh
}
else// danh sách chua day
{ for(i=0; i<plist.num; i++)
{
printf("Nhap phan tu thu %d: ",i+1);
scanf("%d",&plist.nodes[i]); // &A[i]
}
}
}
//Duyet danh sách (Xuat (in)) danh sach
void Output(List plist){
int i;
if(plist.num==0)// hoac su dung lenh: if(ListSize(plist)==0)
{ // hoac if (IsEmpty(list)==1)
printf("\n Danh sach khong co phan tu");
return;// thoát chuong trinh
}
for(i=0;i<plist.num;i++){
printf("%4d", plist.nodes[i]);
}
printf("\n");
}

//INSERT
void Insert(List &plist, int pos, int x)
{ int i;
if (pos<0||pos>plist.num){ printf("Vi tri chen khong hop le !"); return; }

else{ if(IsEmpty(plist) ){

printf("Danh sach rong !");


return; }

else {

for (i=plist.num-1;i>pos;i-- )
{ plist.nodes[i]=plist.nodes[i-1]; }
plist.nodes[pos]=x;
plist.num++; }

}
//truy xuat mot phan tu trong danh sach
int Retrieve(List plist, int pos){

if(pos<0 || pos>=ListSize(plist))
{ printf("Vi tri %d khong hop le",pos); return 0;}
else
{ if(IsEmpty(plist))

printf("danh sach bi rong");


else
return plist.nodes[pos];

}
//xoa mot phan tu ra khoi danh sach
int Remove(List &plist, int pos){

int i;
int x;
if(pos <0 || pos>=ListSize(plist))

printf("\n Vi tri xoa khong phu hop");

else{

if(IsEmpty(plist)) printf("\n Danh sach rong");


else{

x=plist.nodes[pos];
for(i=pos;i<ListSize(plist)-1;i++){

plist.nodes[i]=plist.nodes[i+1];

}
plist.num--;

return x;
}

}
//tim kiem 1 phan tu trong danh sach
int Search(List plist, int x){

int vitri=0;
while(plist.nodes[vitri]!=x && vitri<plist.num) vitri++;
if(vitri==plist.num) return -1;
return vitri;

}
//sap xep danh sach
void Sort1(List &plist){
int tam,i,j;
for(i=0;i<plist.num-1;i++)
for(j=i+1;j<plist.num;j++)
if(plist.nodes[i]>plist.nodes[j]) // Doi cho 2 phan tu nay
{
tam=plist.nodes[i];
plist.nodes[i]=plist.nodes[j];
plist.nodes[j]=tam;
}
}
//sap xep danh sach 2
void SelectionSort(List &plist){
int i,j,vitrimin,min;
for(i=0;i<plist.num-1;i++){
min=plist.nodes[i];
vitrimin=i;
for(j=i+1;j<plist.num;j++){
if(min >plist.nodes[j]){
min=plist.nodes[j];
vitrimin=j;
}
}
plist.nodes[vitrimin]=plist.nodes[i];
plist.nodes[i]=min;
}
}
//Hàm chính
int main(int argc, char** argv) {

//khai báo các bien quan lý danh sách


List plist; //bien tro den nút dau tiên trong danh sách
Init(plist); //khoi tao danh sách liên ket ban dau chua có nút nào
printf("CHUONG TRINH CUA LE TU QUYNH NHI");
//nhap danh sach ke
printf("Nhap danh sach:");
Input(plist);
//Xuat danh sách
printf("Hien thi (xuat) danh sach: ");
Output(plist);
int m,n;
printf("nhap vi tri va gia tri muon chen: ");
scanf("%d%d",&m,&n);
Insert(plist,m,n);
printf("danh sach sau khi chen: ");
Output(plist);
//truy xuat
int b,l;

printf("nhap vi tri can truy xuat: ");


scanf("%d",&l);
printf("phan tu thu %d la: ",&l);
printf("%d",b);

//danh sach sau khi xoa


Remove(plist,2);
printf("danh sach sau khi xoa la: ");
Output(plist);
int found = Search(plist,4);
if(found>0){
printf("tim thay tai vi tri %d",found);
}
else
printf("khong tim thay trong danh sach");
Sort1(plist);
printf("danh sach sau khi sap xep 1: ");
Output(plist);
SelectionSort(plist);
printf("danh sach sau khi sap xep 2: ");
Output(plist);
return 0;
}

họ và tên , mssv, điểm


- Danh sách liên kết:
+kn:
● Danh sách liên kết là một cấu trúc dữ liệu dùng để lưu trữ một
tập các phần tử rời rạc có thể co giãn một cách linh động.
● Kích thước của danh sách liên kết không cần định nghĩa trước,
nó tự động thay đổi khi số phần tử trong danh sách thay đổi.
● Không giới hạn số lượng phần tử
● Dễ dàng thực hiện thao tác: thêm, sửa , xóa
● Truy xuất dữ liệu kiểu tuần tự
+sự khác biệt giữa danh sách liên kết và mảng:
-các loại danh sách liên kết:
+danh sách liên kết đơn
+danh sách liên kết đôi
+danh sách liên kết vòng
-Thêm node vào cuối danh sách:
+insert vào cuối danh sách phức tạp hơn đầu danh sách
+phead lúc nào cũng ở đầu, không di chuyển
+ptempt ban đầu ở cùng với phead, sau đó ptempt đi lần lượt xuống dưới

bỏ ngày sinh, trộn 2 bài qlsv với danh sách liên kết
*NGĂN XẾP STACK:
#define MAXSTACK 100
struct stack{
int top;
int nodes[MAXSTACK];
};
typedef struct stack Stack;
-tác vụ IsEmpty(Stack s){
return (s.top==-1)
}
-tác vụ push:
void Push(Stack &s,int x){
s.nodes[++(s.top)]=x;
}
-tác vụ pop:
int Pop(Stack &s){
if(IsEmpty(s)){
printf(“Stack bi rong”); return 0
}
return s.nodes[s.top–];
}
-khai báo cấu trúc stack:
typedef struct node
{
DataType info;
struct node * next;
} Node;
typedef Node* NODEPTR;
typedef NODEPTR STACk;
STACK s;
-hiện thực stack bằng danh sách liên kết:
+khởi tạo stack:
void Init(STACK &s){
s=NULL;
}
+kiểm tra stack rỗng:
int IsEmpty(STACK s){
return (s==NULL);
}
+thêm một phần tử vào stack:
void Push(STACK &s, DataType x){
NODEPTR p = new Node;
p->info=x;
p->next=s;
s=p;
}
+lấy một phần tử ra khỏi stack:
int Pop(STACK &s,DataType &x)
{
if(isEmpty(s))
return 0;
NODEPTR p=s;
x=p->info;
s=s->next;
delete p;
return 1;
}
-giới thiệu về stack: cơ chế lifo, push và pop
-giới thiệu về queue (hàng đợi): cơ chế fifo, front và rear
+xử lí dịch vụ của ngân hàng, máy in
+insert: thêm nút mới vài cuối hàng đợi (luôn đưa vào rear), front không đổi
rear tăng, tối đa hàng đợi, nếu hàng đợi bị đầy k insert dc nữa
+remove: dùng để xóa (đưa vào front), front tăng rear không đổi, hàng đợi k bị
rỗng mới lấy ra được
+con trỏ front và rear ban đầu dc gán là -1, nếu có phần tử dc gán vào thì
front và rear mới nhảy lên vị trí 0
=>xu hướng rear tăng lên, nếu rear chạm đáy, dùng mảng vòng để hiện thực
hàng đợi
+mảng vòng:
● khai báo cấu trúc:
#define MAXQUEUE 100
struct queue{
int front , rear;
int nodes[MAXQUEUE];
}
● tác vụ khởi động:
void Init(struct queue &pq){
pq.front=pq.rear=MAXQUEUE-1;
}
● tác vụ kiểm tra hàng đợi trống:
int IsEmpty(struct queue &pq){
return (pq.front == pq.rear);
}
● tác vụ thêm vào hàng đợi:
void Insert(struct queue &pq, int x){
if(pq.rear==MAXQUEUE-1)
pq.rear = 0;
else
pq.rear++;
if(pq.rear ==pq->front)
printf(“hang doi bi day”);
else
pq.nodes[pq.rear]=x;
}
● Lấy một phần tử ra khỏi hàng đợi:
int Remove(struct queue &pq){
if(IsEmpty(pq))
printf(“hang doi bi day”);
else{
if(pq.front==MAXQUEUE-1)
pq.front=0;
else
pq.front++;
return pq.nodes[pq.front];
}
}
+dùng danh sách liên kết hiện thực hàng đợi
-Hàng đợi có ưu tiên:
CHƯƠNG 4: CÂY - TREE
-nút vuông: toán tử, nút tròn toán hạng
-nút lá: nút tận cùng, không có con nào phía dưới
-nút trong: internal node
-cây nhị phân đầy đủ:đầy đủ từ mức 1 đến mức h-1
-cây nhị phân gần đầy: đầy đủ nhưng mức h không đủ->sd mảng 1c không
phù hợp, nên sử dụng con trỏ
-chỉ có 1 trường dữ liệu là số nguyên và 2 con trỏ pleft, pright
typedef struct nodetype{
int data;
struct nodetype *pleft;
struct nodetype *pright;
}Node;
typedef Node * NODEPTR;
-duyệt cây nhị phân: phép xử lý các nút trên cây, duyệt qua 1l
+ Duyệt cây theo thứ tự trước (Preorder) (thông dụng nhất, hay còn được gọi
là Node left right):
● N trước LR(NLR): nếu duyệt xong F về C, kiểm tra C có cây con bên
phải không,các nút chỉ ghi 1 lần duy nhất, mỗi lần qua 1 nút là kiểm tra
2 cửa trái và phải

void preorder(NODEPTR root) - NLR


{
if (root!=NULL) {
cout << root->data <<“ “;
preorder(root->pLeft);
predorder(root->pRight);
}
}

+ Duyệt cây theo thứ tự giữa (Inorder)


● N giữa (LNR): tới nút không mở cửa liền mà đi vòng qua xuống con bên
trái,

void inorder(NODEPTR root)


{
if (root) {
inorder(root->pLeft);
cout << root->data<<“ “;
indorder(root->pRight);
}
}

+ Duyệt cây theo thứ tự sau (Postorder):


● N sau (LRN):

void postorder(NODEPTR root)


{
if (root) {
postorder(root->left);
postdorder(root->right);
count <data<<“ “;
}
}
-Cây nhị phân tìm kiếm-BST
+định nghĩa: là 1 cây mà trong cây mỗi nút có 1 giá trị khác biệt, không
có cây nào trùng hay lặp lại giá trị đó, các con bên trái nhỏ hơn nút cha,
bên phải lớn hơn nút cha, cây nhị phân không phải muốn insert vị trí
nào cũng được, trước khi insert là cây bst thì sau insert cũng phải là
cây bst, lớn hơn quẹo phải, nhỏ hơn quẹo trái
● insert 50 vào cây (nếu x insert vào trùng với phần tử nào trong
cây thì thoát không insert nữa):
int InsertNode(NODEPTR &root, int x)
{
if(root!=NULL) { if(root->data==x) //neu da co x trong cay
return -1;
if (root->data>x)
return InsertNode(root->pleft,x);
else
return InsertNode(root->pright,x) ;
}
root=CreateNode(x) ;
if (root==NULL)
return 0;
root->data=x;
root->pleft=root->pright=NULL;
return 1;
}

+tìm một nút có khóa X:


NODEPTR Search(NODEPTR root, int x)
{
if (root==NULL) return NULL;
if(root->data<x)
Search(root->pright,x);
else
if(root->data>x)
Search(root->pleft,x);
else
return root;
}
hàm không dùng đệ qui:
NODEPTR Search2(int X, NODEPTR root)
{
NODEPTR p = root; while (p != NULL)
{
if(X == p->Data) return p;
else
if(x < p->Data) p = p->pLeft
else
p = p->pRight;
}
return NULL;
}
-xóa 1 nút có khóa x:
+trường hợp 1: nút có khóa x trên cây là nút lá: xóa đơn giản

+TH2: nút có khóa x trên cây có 1 nút con(trái hoặc phải): tương đối không
phức tạp
+TH3:nút x có một cây hai con trái và phải
● cách 1: hủy gián tiếp lấy 15 dán lên 18, tìm nút lớn nhất bên trái

về nhà tìm cách gọi hàm delete


tuần sau có thể ktra giữa kỳ trên giấy (t6 hoặc t4)
● Cách 2: tìm giá trị nhỏ nhất bên nhánh tay phải (tương tự tìm giá trị lớn
nhất bên nhánh tay trái)
-giải phóng cây BST: đi xuống nhánh bên dưới xóa lần lượt lên trên
void MakeEmpty (NODEPTR &root);
{ if (root) // if (root!=NULL)
{
MakeEmpty(root->pLeft);
MakeEmpty(root->pRight);
delete root ;
}
}
-cây tổng quát: cây m phân (m có thể là nhị, tam, tứ…), biểu diễn cây m phân
bằng liên kết động(tương tự nhị phân), nhị phân có 3 trường, tam phân có ít
nhất 4 trường, cây m phân có m+1 trường, mối liên kết: n(m-1)+1
+biểu diễn cây tổng quát bằng cây nhị phân, qui tắc: giữ nút trái nhất làm con
trái, các nút con còn lại biến thành con phải, cháu phải…(con nằm ở vị trí trái
nhất là con cả 1)

typedef struct TreeNode * PtrToNode;


struct TreeNode
{int info;
PtrToNode FirstChild; // con caả
PtrToNode NextSibling; //em kê
};
+Duyệt theo chiều sâu(thực hiện đệ qui):phép duyệt NLR(T),phép duyệt
LNR(T),phép duyệt LRN(T)
+duyệt cây theo mức: sử dụng hàng đợi
(không tìm hiểu sâu về cây tổng quát)

làm bài tập chương 4:


PHẦN CÂU HỎI:
Câu 1: Trong phép duyệt cây nhị phân có 15 nút theo thứ tự LRN, nút gốc có
thứ tự bao nhiêu?
=>15
Câu 2: Giả sử T là một cây nhị phân có 14 nút. Chiều cao tối thiểu của T là
bao nhiêu?
=>4
Câu 3: Trong một cây nhị phân hoàn chỉnh, với chiều cao là 3 thì số nút tối
thiểu trên cây T là bao nhiêu?
=>7
Câu 4: Cây nhị phân khác rỗng là cây như thế nào?
=>Mỗi nút có không quá 2 nút con
Câu 5: Cho cây nhị phân, phép duyệt theo thứ tự giữa (LNR) cho kết quả là
một dãy tăng dần, Cây nhị phân đó là loại cây nhị phân nào?
=>Nhị phân tìm kiếm
Câu 6: Khái niệm nào sau đây là đúng khi mô tả cây nhị phân đầy đủ?
=>Là cây mà mọi nút có mức nhỏ hơn hoặc bằng chiều cao h-1 đều có đúng 2 nút
con.Là cây nhị phân mà nút trong đều có đúng 2 nút con
Câu 7: Cho cây được biểu diễn như sau: (a ( b (d, e (i, j)), c (f, g (k), h)). Cho
biết mức của nút g là bao nhiêu?
=>3
Câu 8: Cho cây được biểu diễn như sau: (a ( b (d, e (i, j)), c (f, g (k), h)). Cho
biết bậc của nút g là bao nhiêu?
=>1
Câu 9: Cây nhị phân có mức bằng bao nhiêu?
=>cả 3 câu đều sai( phụ thuộc vào số nút,bậc cây nhị phân bằng 2)
Câu 10: Cho cây được biểu diễn như sau: (a ( b (d, e (i, j)), c (f, g (k), h)). Cho
biết độ dài đường đi từ gốc đến nút k là bao nhiêu?
=>3
Câu 11: Cho cây nhị phân tìm kiếm được tạo từ dãy số sau: 81 69 35 49 80
91 41 44 93 51. Cho biết thứ tự duyệt cây theo thứ tự RLN là dãy số nào sau
đây?
=>93 91 80 51 44 41 49 35 69 81
Câu 12: Khi xóa một nút có 2 cây con trên cây nhị phân tìm kiếm có mấy
bước?
=>3 bước
(tìm giá trị lớn nhất bên nhánh tay trái, lột nhãn đó dán vào x, cắt y)
Câu 13: Phần tử thế mạng cho nút cần xóa khi xóa một nút có 2 cây con trên
cây nhị phân tìm kiếm là phần tử nào?
=>Nút nhỏ nhất cây con phải hoặc nút lớn nhất cây con bên trái
Câu 14: Cho cây nhị phân tìm kiếm được tạo từ dãy số sau: 42 23 74 11 65
58 94 36. Để tìm khóa 60 có tồn tại trên cây hay không cần bao nhiêu phép so
sánh?
=>4
(so sánh 60 từ gốc đi xuống,
Câu 15: Cho cây nhị phân tìm kiếm được tạo từ dãy số sau: 42 23 74 11 65
58 94 36. Cho biết cây con trái có mấy nút?
=>3

CHƯƠNG 5: ĐỒ THỊ-GRAPH

1.Định nghĩa và các khái niệm


- biểu diễn mạch điện
-yêu cầu trình tự
-truyền thông mạng máy tính
-luồng giao thông
-sơ đồ đường phố
-chu trình(cycle): là đường đi khép kín, có đỉnh đầu trùng với đỉnh cuối
-bậc của đỉnh trong đồ thị được đến bằng số cạnh kề của nó, c có bậc là 3, f
có bậc 0 hay còn gọi là đỉnh cô lập

-đồ thị liên thông: tất cả các cặp nút luôn có con đường nối với nhau
-đồ thị có trọng số: mỗi cung được gán một giá trị số đặc trưng

2.Biểu diễn đồ thị


-có bao nhiêu nút thì ma trận có bấy nhiêu hàng và cột
-biểu diễn đồ thị bằng ma trận kề
-biểu diễn đồ thị bằng danh sách kề: thứ tự trước sau trong một danh sách
không quan trọng

3.Duyệt đồ thị
-tìm kiếm theo chiều sâu (DFS): đi tới không được thì phải lùi lại
-tìm kiếm theo chiều rộng (BFS):

CHƯƠNG 6: CÁC GIẢI THUẬT SẮP XẾP

1.Tại sao phải sắp xếp dữ liệu?


-khái niệm sắp xếp:

-khái niệm nghịch thế: trong mảng từ trái qua phải xuất hiện cặp số mà con số
bên phải nhỏ hơn số bên trái thì gọi là một cặp nghịch thế : i<j và Mi>Mj. danh
sách có thứ tự sẽ không chứa nghịch thế
2.Phương pháp nổi bọt (bubble sort)
-ý tưởng: làm cho nổi bật, nhẹ thì nổi lên trên, nhỏ nhất nổi lên trên đầu
-trong quá trình sắp xếp sẽ đi từ trái qua phải, người bên trái lớn hơn người
bên phải thì đó là nghịch thế => đảo vị trí
-có 3 bước chính:
+Bước 1 : i = 0; // lần xử lý đầu tiên
+Bước 2 : j = N-1; //Duyệt từ cuối dãy ngược về vị trí i
● Trong khi (j > i) thực hiện:
● nếu M[j]<M[j-1]: M[j]↔M[j-1 ];//xét cặp phần tử kế cận
● j = j-1;
+Bước 3 : i = i+1; // lần xử lý kế tiếp
+Nếu i >=N-1: Hết dãy. Dừng
+Ngược lại : Lặp lại Bước 2.
-cài đặt giải thuật:
void BubbleSort(int M[], int n)
{
int i, j;
for (i = 0; i < n - 1; i++)
{
for (j = n - 1; j > i; j--)
{
if (M[j] < M[j - 1])// nếu có nghịch thế
{
int temp = M[j];
M[j] = M[j - 1];
M[j - 1] = temp;
}}}}

Bài tập: Hãy giải thích từng bước quá trình chạy Bubble Sort cho danh
sách dưới đây:
int[] M = {10,15,2,8,7};
BubbleSort(M);
giải:
void BubbleSort(int M[], int n)
{
int i, j;
for (i = 0; i < n - 1; i++)
{
for (j = n - 1; j > i; j--)
{
if (M[j] < M[j - 1])// nếu có nghịch thế
{
int temp = M[j];
M[j] = M[j - 1];
M[j - 1] = temp;
}}}}
int[] M = {10,15,2,8,7};
BubbleSort(M);
for1: (for1- lần 1)Khởi tạo i=0, kiểm tra i<n-1 ⇔i<5-1⇔0<4 đúng
for 2: (for2- lần 1)Khởi tạo j=n-1=4, kiểm tra j>i⇔ 4>0=>đúng
If: M[j] <M[j-1] <=> M[4]<M[3]
7<8 đúng, hoán đổi vị trí của hai phần tử này, ta có:
M = {10,15,2,7,8};

-khi chạy tới i=2 và j=3 thì trạng thái lúc đó ntn
-Độ phức tạp:
+xấu nhất và trung bình: O(n*n)
+tốt nhất: O(n)
-khuyết điểm:
+Không nhận diện được tình trạng dãy đã có thứ tự hay có thứ tự từng phần.
+Các phần tử nhỏ được đưa về vị trí đúng chỗ rất nhanh, trong khi các phần
tử lớn lại được đưa về vị trí đúng rất chậm.
3.Phương pháp chọn(Selection Sort)
-ý tưởng: mô phỏng một trong những cách sắp xếp tự nhiên nhất trong thực
tế, một mảng có i->n, tìm từ Ni đến N-1 vị trí nào nhỏ nhất thì đưa về vị trí i,
dãy hiện hành tiến về bên phải và thu nhỏ lại
-các bước:
+Bước 1 : i = 0;
+Bước 2 : Tìm phần tử M[min] nhỏ nhất trong dãy hiện hành từ M[i] đến M[N-
1]
+Bước 3 : Nếu min ≠ i, Hoán vị M[min] và M[i]
+Bước 4 : Nếu i < N-1 thì i = i+1; Lặp lại Bước 2
+Ngược lại: Dừng. //N-1 phần tử đã nằm đúng vị trí.
-câu lệnh:
void SelectionSort(int M[], int n)
{
int min;
for(int i=0;i<n-1;i++)
{
min = i;
for(int j=i+1;j<n;j++)
{
if (M[j] < M[min])
min = j;
}
if(min!=i)
{
int temp = M[i];
M[i] = M[min];
M[min] = temp;
}
}
}
Bài tập: Hãy giải thích từng bước quá trình chạy Selection Sort cho danh
sách dưới đây (sau khi chạy for1 lần thứ 3 kết quả ntn)
Int M[] = {10,15,2,8,7};
SelectionSort(M);
for1 lần 1: 2,10,15,8,7
for1 lần 2: 2,7,10,15,8
for1 lần 3: 2,7,8,10,15
-đánh giá giải thuật: số lượng phép so sánh không phụ thuộc vào tình trạng
của dãy số ban đầu
+tốt nhất:
● số lần so sánh: N(N-1)/2
● số lần gán: 0 (min hoán vị với i)
+xấu nhất:
● số lần so sánh: N(N-1)/2
● số lần gán: 3N (mảng bị sắp xếp ngược)
4.Phương pháp chèn(Insertion sort)
-mảng bên tay trái: được sắp xếp nội bộ (từ nhỏ đến lớn), xuất hiện nghịch
thế thì đánh dấu điểm đó,rút ra từ mảng bên phải chèn vào mảng bên trái, sau
khi insert vào vị trí thích hợp thì danh sách tăng lên, chèn vào mảng đã được
sắp xếp => chèn trực tiếp
-phương pháp chèn trực tiếp:
● Xét dãy n phần tử: M0, M1,…, Mn-1
● Xem dãy gồm 1 phần tử là M0 dãy có thứ tự
● Thêm M1 vào dãy có thứ tự M0 sao cho dãy mới M0,M1 là dãy có thứ
tự. Nếu M1 <M0 ta đổi chỗ M1 với M0.
● Thêm M2 vào dãy có thứ tự M0, M1 sao cho dãy mới M0, M1, M2 là
dãy có thứ tự
● Tiếp tục như thế đến n-1 bước ta sẽ có dãy có thứ tự M0, M1, …., Mn-1
-i xuất phát bằng 1 (khác các phương pháp khác)

-phương trình:
void InsertionSort(int M[],int n)
{
//lưu vị trí cần chèn
int pos=0;
int x;
//xem đoạn M[0] đã sắp
for (int i=1;i<n;i++)
{
x=M[i];//lưu trữ giá trị M[i] tránh bị ghi đè khi dời chỗ các phần tử
for(pos=i;(pos>0) &&( M[pos-1]>x); pos–)
{
M[pos]=M[pos-1];
}
M[pos]=M[pos -1];
}
M[pos]=x;//chèn x vào dãy
}
}
-đánh giá giải thuật:
+Các phép so sánh xảy ra trong mỗi vòng lặp tìm vị trí thích hợp pos, và mỗi
lần xác định vị trí đang xét không thích hợp, sẽ dời chỗ phần tử M[pos] tương
ứng.
+Giải thuật thực hiện tất cả N-1 vòng lặp tìm pos, do số lượng phép so sánh
và dời chỗ này phụ thuộc vào tình trạng của dãy số ban đầu, nên chỉ có thể
ước lượng trong từng trường hợp
5.Phương pháp đổi chỗ(Interchange sort)
-Để sắp xếp một dãy số, ta có thể xét các nghịch thế có trong dãy và làm triệt
tiêu dần chúng đi.
-Ý tưởng chính:
+ Xuất phát từ đầu dãy, tìm tất cả nghịch thế chứa phần tử này, triệt tiêu
chúng bằng cách đổi chỗ phần tử này với phần tử tương ứng trong cặp
nghịch thế.
+ Lặp lại xử lý trên với các phần tử tiếp theo trong dãy
-Bước 1 : i = 0;// bắt đầu từ đầu dãy
-Bước 2 : j = i+1;//tìm các phần tử M[j] < M[i], j>i
-Bước 3 :
Trong khi j ≤ N thực hiện
Nếu M[j]<M[i]: M[i]↔M[j];//xét cặp phần tử M[i],
M[j]
j = j+1;
-Bước 4 : i = i+1;
Nếu i < n: Lặp lại Bước 2.
Ngược lại: Dừng.
-trong một dãy số chưa được sắp xếp, tồn tại ít nhất một cặp số ở tình trạng
nghịch thế, tìm cách triệt tiêu dần,xuất phát từ đầu dãy
-phương trình:
void InterchangeSort(int[] M, int n) { int i, j; for (i = 0; i < n - 1; i++) { for (j = i +
1; j < n; j++) { if (M[j] < M[i]) { int temp = M[i]; M[i] = M[j]; M[j] = temp; } } } }
6.Phương pháp sắp xếp nhanh(Quick sort)
Ðể sắp xếp dãy M0, M1, ..., Mn-1 giải thuật QuickSort dựa trên việc phân
hoạch dãy ban đầu thành hai phần :
Dãy con 1: Gồm các phần tử M0.. Mi có giá trị không lớn hơn pivot
Dãy con 2: Gồm các phần tử Mi .. Mn-1 có giá trị không nhỏ hơn pivot với
pivot là giá trị của một phần tử tùy ý trong dãy ban đầu. Sau khi thực hiện
phân hoạch, dãy ban đầu được phân thành 3 phần:
1. Mk < pivot , với k = 0..i
2. Mk = pivot , với k = i..j
3. Mk > pivot , với k = j..N-1

Trong đó dãy con thứ 2 đã có thứ tự, Nếu các dãy con 1 và 3 chỉ có 1 phần
tử thì chúng cũng đã có thứ tự, khi đó dãy ban đầu đã được sắp. Ngược lại,
nếu các dãy con 1 và 3 có nhiều hơn 1 phần tử thì dãy ban đầu chỉ có thứ tự
khi các dãy con 1, 3 được sắp. Ðể sắp xếp dãy con 1 và 3, ta lần lượt tiến
hành việc phân hoạch từng dãy con theo cùng phương pháp phân hoạch dãy
ban đầu vừa trình bày .
Giải thuật phân hoạch dãy Ml, Ml+1, ., Mr thành 2 dãy con:
• Bước 1 : Chọn tùy ý một phần tử M[k] trong dãy là giá trị mốc, l ≤ k ≤ r: pivot =
M[k]; i = l; j = r;
• Bước 2 : Phát hiện và hiệu chỉnh cặp phần tử M[i], M[j] nằm sai chỗ :
• Bước 2a : Trong khi (M[i]pivot) j--;
• Bước 2c : Nếu i<= j // M[i] ≥ pivot ≥ M[j] mà M[j] đứng sau M[i] Hoán vị
(M[i],M[j]);
• Bước 3 : Nếu i < j: Lặp lại Bước 2.//chưa xét hết mảng Nếu i ≥ j: Dừng
-lấy số giữa làm pivot,bên phải nhỏ hơn hoặc bằng pivot thì dừng lại,trái nhỏ
hơn pivot, phải lớn hơn pivot
chạy tour lần 1: 3 5 8 1 4 12 8 23 33
-con trỏ từ bên trái chạy qua,i từ trái chạy qua,j từ phải chạy về, khi nào 2 con
trỏ đụng nhau thì dừng lại 1, và đảo vị trí cho nhau
-chương trình:
void QuickSort(int M,int left, int right)
{
if (left >= right) return;
int pivot = M[(left + right) / 2];
int i = left, j = right ;
do{
while (M[i] < pivot) i++;
while (M[j] > pivot) j--;
if (i <= j){
int temp = M[i];
M[i] = M[j];
M[j] = temp;
i++;
j--;
}
} while (i < j);
QuickSort(M,left, j);
QuickSort(M,i,right);
}
btn: quản lý sinh viên trên danh sách liên kết

CHƯƠNG 7: TÌM KIẾM

1.Khái niệm và vai trò của việc tìm kiếm dữ liệu


-Tìm kiếm là việc ta muốn lọc ra một hay một số đối tượng (phần tử) “có
những đặc tính nào đó” từ một tập dữ liệu có trước để xử lý.

2.Một số giải thuật tìm kiếm phổ biến


*Tìm kiếm Tuyến Tính
-để đơn giản hóa bài toán ta đặt giả thuyết:
+Dữ liệu cho tìm kiếm là một mảng các số nguyên (có thể được sắp xếp hoặc
không)
+Dữ liệu tìm kiếm : (khóa) giá trị số nguyên
-mã giả:
Bước 1: i = 0 bắt đầu từ phần tử đầu tiên của dãy
Bước 2: So sánh M[i] với x, có 2 khả năng :
+ M[i] = x : Tìm thấy. Dừng
+ M[i] ≠ x : Sang Bước 3.
Bước 3 : i = i+1 xét tiếp phần tử kế trong mảng
Nếu i >=N: Hết mảng, không tìm thấy. Dừng
Ngược lại: Lặp lại Bước 2.
-code:
for (i=0;i=N;i++)
{
if(M[i]==x)
printf (“không tìm thấy tại vị trí %d”,i);
}
if(i>=N)
printf(“không tìm thấy”);
(với x là biến khóa nhập vào cho tìm kiếm)
-hàm linearsearch (tìm kiếm tuyến tính):
int LinearSearch(int M,int x, int n)
{
int i;
for (i = 0; (i < n) && (M[i] != x); i++);
if (i == n) return -1;// tìm hết mảng nhưng không có x
else
return i; // M[i] là phần tử có khoá x
}
hoặc cách viết khác:
int LinearSearch(int M[], int x, int n) {
int i;
for (i = 0; i < n; i++)
{
if(M[i] == x)
return i; // M[i] là phần tử có khoá x
}
return -1;// tìm hết mảng nhưng không có x
}
=>tìm x=5 trong Int M[] = { 25,20,9,5,7,4,13,17 }; , thì tới khi i=3, trả về i và
thoát ra ngay lập tức không chạy tiếp như chương trình biến tạm, ở trường
hợp này hiệu quả hơn(nếu tìm thấy)
hoặc cách viết khác:
int LinearSearch(int M, int x, int n)
{
int tam = -1;
for (int i = 0; i < n; i++)
{
if(M[i] == x) tam = i; // M[i] là phần tử có khoá x
} return tam;//tam=-1 nếu tìm hết mảng nhưng không có x
}
=>tìm x=5 trong Int M[] = { 25,20,9,5,7,4,13,17 }; , thì tới khi i=3,sau khi tìm
thấy rồi i vẫn tăng tiếp tục thực hiện vòng for,biến tạm chạy 8 lần
-đánh giá giải thuật:

=>Vậy giải thuật tìm tuyến tính có độ phức tạp tính toán cấp n: T(N) = O(N)
*tìm kiếm nhị phân:
-Đặc trưng dữ liệu: Đã được sắp xếp theo một thứ tự nào đó (nhỏ-> lớn)
-Tính chất của dữ liệu được sắp là :
Mi-1<= Mi<=Mi+1
=> Vậy nếu x> Mi thì x chỉ có thể xuất hiện trong đoạn [Mi+1 ,MN-1] của dãy,
ngược lại nếu x< Mi thì x chỉ có thể xuất hiện trong đoạn [M0 ,Mi-1] của dãy -
Giải thuật tìm nhị phân áp dụng nhận xét trên đây với tư tưởng chia để trị để
tìm cách giới hạn phạm vi tìm kiếm sau mỗi lần so sánh x với một phần tử
trong dãy.
-cài đặt giải thuật:
Bước 1: left = 0; right = N-1; // tìm kiếm trên tất cả các phần tử
Bước 2:
mid = (left+right)/2; // lấy mốc so sánh
So sánh M[mid] với k, có 3 khả năng :
M[mid] = k: Tìm thấy. Dừng
M[mid] > k: Tìm tiếp x trong dãy con M[left] .. M[mid -1] : right =mid - 1;
M[mid] < k: Tìm tiếp x trong dãy con M[mid +1] .. M[right] : left = mid + 1;
Bước 3: Nếu left <= right : Còn phần tử chưa xét tìm tiếp.
Lặp lại Bước 2.
Ngược lại: Dừng; //Ðã xét hết tất cả các phần tử, không tìm thấy.
-đoạn chương trình:
int BinarySearch(int M[],int n, int x)
{
int left = 0, right = n-1;
int mid;
do
{
mid = (left + right) / 2;
if (x == M[mid]) return mid;//Thấy x tại mid
else if (x < M[mid])
right = mid - 1;
else
left = mid + 1;
} while (left <= right);
return -1; // Tìm hết dãy mà không có x
}
vd: int M[] = {1, 5, 15, 19, 25, 27, 29, 31,33,45,55,88,100};
int kq= BinarySearch(M,13, 19);
tìm phần tử X=19

=>chưa sắp xếp theo thứ tự,sắp xếp


xong mới chạy, chạy ra mid=5,left=4,right=5
-đánh giá giải thuật:

You might also like