You are on page 1of 61

KỸ THUẬT LẬP TRÌNH

Bài 2. Cấu trúc dữ liệu và giải thuật


Nội dung

 Tổng quan
 Một số giải thuật
 Một số cấu trúc dữ liệu
1. Tổng quan
 Bài toán thực tế thường phức tạp
 Để xây dựng chương trình - giải quyết bài toán
Phải xác định được
• Các dữ liệu liên quan
• Các thao tác cần thiết để giải quyết bài toán
Một số công cụ nâng cao

 Con trỏ
 Quản lý bộ nhớ
 Hàm và tham số
 Overloading
Con trỏ - Pointer

 Tổ chức bộ nhớ máy tính

• Địa chỉ
• Đánh địa chỉ:
• địa chỉ tuyệt đối,
• tương đối

• Bộ nhớ thực
• Bộ nhớ ảo
Con trỏ - Pointer

 Khái niệm
• Biến
• Giá trị các biến được lưu trữ trong bộ nhớ máy tính .Có thể truy cập tới
các giá trị đó qua tên biến, đồng thời cũng có thể qua địa chỉ của chúng
trong bộ nhớ
 Con trỏ
• Thực chất là 1 biến mà nội dung của nó là địa chỉ của 1 đối tượng khác
(biến, hàm, nhưng không phải 1 hằng số)
• Có nhiều kiểu biến với
kích thước khác nhau =>
nên có nhiều kiểu con trỏ
VD: Con trỏ int => biến hay hàm kiểu int
Con trỏ - Pointer

 Khai báo con trỏ

=> ta có con trỏ NULL


 Sử dụng con trỏ
• Để sử dụng => toán tử lấy địa chỉ &

• Để truy cập nội dung do biến con trỏ trỏ tới => toán tử lấy nội dung *
Con trỏ - Pointer
Ví dụ
Con trỏ - Pointer
Chú ý về biến con trỏ
 Một con trỏ chỉ có thể trỏ tới 1 đối tượng cùng kiểu
 Toán tử 1 ngôi * và & có độ ưu tiên cao hơn các toán tử số học
 Có thể viết *p cho mọi nơi có đối tượng mà nó trỏ tới xuất hiện

 Có thể gán nội dung 2 con trỏ cho nhau => cả hai con trỏ cùng
trỏ tới 1 đối tượng
Con trỏ - Pointer
Phép toán trên con trỏ
 Cộng hoặc trừ con trỏ p với 1 số nguyên n
=> trả về 1 con trỏ cùng kiểu, là địa chỉ mới trỏ tới 1 đối tượng khác nằm
cách đối tượng đang bị trỏ n phần tử
 Trừ 2 con trỏ => cho ta khoảng cách (số phần tử) giữa 2 con trỏ
 KHÔNG có phép cộng, nhân, chia 2 con trỏ
 Có thể dùng các phép gán, so sánh các con trỏ
=> Chú ý đến sự tương thích về kiểu
Con trỏ - Pointer
Phép toán trên con trỏ
 Ví dụ

• Giả sử các địa chỉ ban đầu tương ứng của 3 con trỏ là 100, 200 và 300
=> kết quả ?

• Nếu tiếp tục


Con trỏ - Pointer

 Chú ý
• ++ và -- có độ ưu tiên cao hơn * nên *p++ tương đương với *(p++)
Con trỏ - Pointer
Con trỏ void*
 Là con trỏ không định kiểu => Nó có thể trỏ tới bất kì một loại
biến nào.
Thực chất một con trỏ void chỉ chứa một địa chỉ bộ nhớ mà
không biết rằng tại địa chỉ đó có đối tượng kiểu dữ liệu gì
=> không thể truy cập nội dung của một đối tượng thông qua con
trỏ void.
 Để truy cập được đối tượng thì trước hết phải ép kiểu biến trỏ
void thành biến trỏ có định kiểu của kiểu đối tượng
Con trỏ - Pointer
Con trỏ void*
Con trỏ - Pointer
Con trỏ và mảng array

 Giả sử ta có int a[30];


thì &a[0] là địa chỉ phần tử đầu tiên của mảng đó, đồng thời là địa
chỉ của mảng.
 Trong C, tên của mảng chính là 1 hằng địa chỉ = địa chỉ của phần
tử đầu tiên
a = &a[0];
 Chú ý: a là hằng => không hợp lệ khi thực hiện a++
Con trỏ

 Con trỏ trỏ tới con trỏ


• Bản thân con trỏ cũng là 1 biến, vì vậy nó cũng có địa chỉ và có thể dùng 1
con trỏ khác để trỏ tới địa chỉ đó
• Ví dụ
Con trỏ

 Một số chủ đề khác


• con trỏ và xâu ký tự
• mảng các con trỏ

 Chú ý:
• Nhiều ngôn ngữ không khuyến cáo dùng con trỏ
Quản lý bộ nhớ
Bộ nhớ động
 Bộ nhớ tĩnh: khai báo mảng, biến và các đối tượng khác một
cách tường minh trước khi thực hiện chương trình.
 Trong thực tế gặp các tình huống:
• không thể xác định trước được kích thước bộ nhớ cần thiết để làm việc
• đối tượng có kích thước thay đổi linh hoạt

 => cần có khả năng cấp phát động


Quản lý bộ nhớ
Bộ nhớ động
 Việc dùng bộ nhớ động cho phép xác định bộ nhớ cần thiết
trong quá trình thực hiện của chương trình, đồng thời giải
phóng chúng khi không còn cần đến để dùng bộ nhớ cho việc
khác
 Trong C - các hàm malloc, calloc, realloc và free
 Trong C++ - các hàm new và delete
Quản lý bộ nhớ
Bộ nhớ động
 Để yêu cầu cấp phát bộ nhớ:
<biến trỏ> = new <kiểu dữ liệu>;
hoặc <biến trỏ> = new <kiểu dữ liệu>[Số phần tử];
 Giải phóng bộ nhớ
delete ptr; // xóa 1 biến đơn
delete [] ptr; // xóa 1 biến mảng
 Chú ý:
• Bộ nhớ động được quản lý bởi hệ điều hành, được chia sẻ giữa hàng loạt
các process, vì vậy có thể không đủ bộ nhớ. Khi đó toán tử new sẽ trả về
con trỏ NULL
Quản lý bộ nhớ
Bộ nhớ động
 Cấp phát bộ nhớ động cho mảng 2 chiều ~ ma trận
• C1: Dùng mảng 1 chiều

• C2: Con trỏ của con trỏ


Hàm và truyền tham số

 Chương trình thường được cấu trúc thông qua các hàm. Mỗi
hàm là một module nhỏ trong chương trình,có thể được gọ

 Có ngôn ngữ phân biệt procedure, function và có ngôn ngữ chỉ


dùng function
 Cú pháp phổ biến
Hàm và truyền tham số

 Tên hàm duy nhất - trong phạm vi


 Truyền tham số:
• truyền tham trị: là truyền giá trị của biến
• truyền tham chiếu: truyền địa chỉ ô nhớ của biến
Hàm và truyền tham số

 Hàm với giá trị tham số ngầm định

• Thường yêu cầu: các tham số có default value - ở cuối cùng


Overloading - đa năng hóa
Đa năng hóa hàm
 Lập trình OOP
 Cung cấp nhiều hơn một định nghĩa cho tên hàm đã cho trong
cùng một phạm vi.
 Trình biên dịch sẽ lựa chọn phiên bản thích hợp của hàm hay
toán tử dựa trên các tham số mà nó được gọi
 Bài toán thực tế thường phức tạp
 Để xây dựng chương trình - giải quyết bài toán
Phải xác định được
• Các dữ liệu liên quan
• Các thao tác cần thiết để giải quyết bài toán
 Ví dụ
• Bài toán: Quản lý hồ sơ sinh viên nhập học

• Cần quản lý thông tin nào ?

• Cần thực hiện thao tác nào ?


2. Giải thuật
Giải thuật
khái niệm
 Là một tập các chỉ thị để thực hiện một tác vụ nhất định
 Các đặc trưng của giải thuật
• Đầu vào (Input)
• Đầu ra (Output)
• Chính xác/đúng đắn (Precision)
• Hữu hạn (Finiteness) - dừng
• Đơn trị (Uniqueness)
• Tổng quát (Generality)
Một số giải thuật thường sử dụng
Tìm kiếm

 Bài toán tìm kiếm


• Input
• Tập các phần tử có cấu trúc, trường khóa
• Khóa cần tìm
• Process
• Tìm phần tử có khóa trùng khóa cần tìm
• Output
• Vị trí phần từ cần tìm nếu có
Tìm kiếm

 Phần tử dữ liệu có cấu trúc


• Khóa
• Các trường dữ liệu khác
 Khóa
• So sánh được
• Thường là số
Tìm kiếm

 Các giải thuật tìm kiếm


• Tìm kiếm tuần tự
• Tìm kiếm nhị phân
Sắp xếp

 Bài toán sắp xếp


• Input:
• Tập các phần tử, với trường khóa
• Process
• So sánh sắp xếp các phần tử
• Output
• Danh sách các phần tử có thứ tự tang dần/giảm dần theo trường khóa
Sắp xếp

 Một số giải thuật


• Insertion sort
• Selection sort
• Bubble sort
• Merge sort
• Heap sort
• Quick sort, ...
Đệ quy
Recursive
 Mô tả đệ quy
• Mô tả theo cách phân tích đối tượng thành nhiều thành phần mà trong số
các thành phần có thành phần mang tính chất của chính đối tượng được
mô tả - Mô tả đối tượng thông qua chính nó
Đệ quy

 Ví dụ
• Mô tả tập số tự nhiên N
• 1 là số tự nhiên
• số tự nhiên cộng 1 cũng là số tự nhiên
• Giai thừa n!
• Không đệ quy: int factorial(int n)

n! = n * (n-1) * … * 1 {
• Đệ quy: if (n==0)
n! = 1 nếu n=0 return 1;
n*(n-1)! nếu n>0 else
return (n * factorial(n - 1));
}
Đệ quy

 Thực hiện
Đệ quy
Các thành phần của giải thuật
 Phần neo/dừng: trường hợp suy biến của đối tượng
• Ví dụ: 1 là số tự nhiên
cấu trúc rỗng là danh sách kiểu T,
0!=1,
 Phần qui nạp: mô tả đối tượng (giải thuật) thông qua chính đối
tượng (giải thuật) đó một cách trực tiếp hoặc gián tiếp
• Ví dụ: n! = n * (n –1)!
Đệ quy
Các loại giải thuật
 Đệ quy trực tiếp
• đệ quy tuyến tính
• đệ quy nhị phân
• đệ quy phi tuyến
 Đệ quy gián tiếp
• đệ quy tương hỗ
Đệ quy

 Đệ quy tuyến tính


P( ) { S , S* là các thao tác không đệ quy
If (B) thực hiện S;
else { thực hiện S* ; gọi P }
}
Ví dụ
• Tính n! (factorial)
• Tính S(n) = 1/(1*2) + 1/(2*3) + ... + 1/( n*(n+1) )
 Đệ quy nhị phân
P ( ) { S , S* là các thao tác không đệ quy
If (B) thực hiện S;
else {
thực hiện S*;
gọi P ; gọi P;
}
}
Ví dụ
• FIBO(n) tính số hạng n của dãy FIBONACCI
 Đệ quy phi tuyến
• Lời gọi đệ quy được thực hiện bên trong vòng lặp
P ( ) {
for (<giá tri đầu> to <giátrịcuối>) {
thực hiện S ;
if (điều kiện dừng) then thực hiện S*;
else gọi P;
S , S* là các thao tác không đệ quy
}
}
KieuDuLieu TenHamX(Thamso)
{
if(Dieu Kien Dung)
{
...;
 Đệ quy tương hỗ return Gia tri tra ve;
• Có 2 hàm fX, fY trong hàm }
...;
này có lời gọi hàm kia; điều return TenHamX(Thamso) <Lien ket hai
kiện dừng của 2 hàm có thể ham> TenHamY(Thamso);
}
giống hay khác nhau KieuDuLieu TenHamY(Thamso)
{
if(Dieu Kien Dung)
{
...;
return Gia tri tra ve;
}
...;
return TenHamY(Thamso)<Lien
ket hai ham>TenHamX(Thamso);
}
Xây dựng thuật toán đệ quy

1. Thông số hóa bài toán .


• Tổng quát hóa bài toán cụ thể cần giải thành bài toán tổng quát (một họ
các bài toán chứa bài toán cần giải )
• Tìm ra các thông số cho bài toán tổng quát
• Các thông số điều khiển: các thông số mà độ lớn của chúng đặc trưng cho
độ phức tạp của bài toán , và giảm đi qua mỗi lần gọi đệ quy
• Vídụ
• n trong hàm FAC(n) ;
• a , b trong hàm USCLN(a,b)
Xây dựng thuật toán đệ quy

2. Tìm các trường hợp neo cùng giải thuật giải tương ứng
• Trường hợp suy biến của bài toán tổng quát
• Các trường hợp tương ứng với các gía trị biên của các biến điều khiển
• VD: FAC(1) =1
• USCLN(a,0) = a
3. Tìm giải thuật giải trong trường hợp tổng quát bằng phân rã bài
toán theo kiểu đệ quy
VD: Bài toán Tháp Hà Nội

 Luật:
• Di chuyển mỗi lần một đĩa
• Không được đặt đĩa lớn lên trên đĩa nhỏ
VD: Bài toán Tháp Hà Nội

 Hàm đệ quy: Chuyển n đĩa từ A sang C qua trung gian B


• Chuyển n-1 đĩa trên đỉnh của cột A sang cột B
• Chuyển 1 đĩa (cuối cùng) của cột A sang cột C
• Chuyển n-1 đĩa từ cột B sang C qua tg A
VD: Bài toán Tháp Hà Nội

 Thông số hóa bài toán


• Xét bài toán ở mức tổng quát nhất: chuyển n (n>=0) đĩa từ cột A sang cột
C lấy cột B làm trung gian .
• THN(n,A,B,C) -> với 64 đĩa gọi THN(64,A,B,C)
• n sẽ là thông số quyết định bài toán –n là tham số điều khiển
 Trường hợp suy biến và cách giải
• Với n =1 : THN (1,A,B,C)
• Giải thuật giải bt THN (1,A,B,C)-thực hiện chỉ 1 thao tác cơ bản: Chuyển 1
đĩa từ A sang C (ký hiệu là Move (A , C))
• THN(1,A,B,C) ≡ { Move( A, C ) }
• THN(0,A,B,C) ≡ { φ}
VD: Bài toán Tháp Hà Nội

 Bài toán THN (k,A,B,C): chuyển k đĩa từ cột A sang cột C lấy cột
B làm trung gian
1. Chuyển (k -1) đĩa từ cột A sang cột B lấy cột C làm trung gian
THN (k -1,A,C,B) (bài toán THN với n = k-1,A= A , B = C , C = B )
2. Chuyển 1 đĩa từ cột A sang cột C : Move ( A, C ) (thao tác cơ bản ).
3. Chuyển (k - 1 ) đĩa từ cột B sang cột C lấy cột A làm trung gian
THN( k -1,B,A,C) ( bài toán THN với n = k-1 , A = B , B = A , C = C )
 Với n>1
THN(n,A,B,C) ≡
{
THN (n -1,A,C,B) ;
Move ( A, C ) ;
THN (n -1,B,A,C) ;
}
Khử đệ quy

 Đệ quy
• Ưu điểm: gọn gàng, dễ hiểu, dễ viết code
• Nhược điểm: tốn không gian bộ nhớ và thời gian xử lý
=> Khử đệ quy ~ Thay thế bằng giải thuật không đệ quy
Một số giải thuật khác

 Quay lui
 Nhánh cận
 Quy hoạch động
3. Cấu trúc dữ liệu
Các bài toán thực tế thường rất phức tạp
 Phải xác định được
• Các dữ liệu liên quan đến bài toán => cấu trúc dữ liệu
• Các thao tác cần thiết để giải quyết bài toán => giải thuật
Cấu trúc dữ liệu

 Đặc trưng cho 1 kiểu dữ liệu


 Là cách tổ chức và thao tác một cách có hệ thống trên dữ liệu

 Cấu trúc dữ liệu mô tả về:


• Các dữ liệu cấu thành
• Mối liên kết về mặt cấu trúc giữa các dữ liệu đó
 Cung cấp các thao tác trên dữ liệu đó
Các kiểu dữ liệu và cấu trúc
Các kiểu dữ liệu

Kiểu dữ liệu cơ bản (primitive Kiểu dữ liệu có cấu trúc


data type) (structured data type)
 Đại diện cho các dữ liệu  Được xây dựng từ các kiểu
giống nhau, không thể phân dữ liệu (cơ bản, có cấu trúc)
chia nhỏ hơn được nữa khác
 Thường được các ngôn ngữ  Có thể được các ngôn ngữ
lập trình định nghĩa sẵn lập trình định nghĩa sẵn hoặc
 Ví dụ do lập trình viên tự định
C/C++: int, long, char, bool... nghĩa
Thao tác trên các số nguyên:
+ - * / ...
Một số kiểu dữ liệu cơ bản

 Số
 Ngày tháng
 Ký tự, xâu

You might also like