Ôn Thi Đại Họcby ToanKhonTech
Kiến thức trọng tâmLớp 11Lớp 12

Kĩ thuật lập trình và thuật toán (định hướng CS)

Tìm kiếm tuần tự, nhị phân; sắp xếp chọn, nổi bọt, chèn bằng Python và C++ kèm vết chạy; độ phức tạp thời gian, kiểm thử, lập trình có cấu trúc (định hướng CS).

Đây là trọng tâm của định hướng Khoa học máy tính (CS): phần II thường có một câu đúng/sai cho một chương trình sắp xếp hoặc tìm kiếm in song song Python và C++, hỏi trạng thái dãy sau từng lượt, số lần so sánh, đổi chỗ và độ phức tạp. Phần I cũng có thể hỏi độ phức tạp của một đoạn chương trình. Kĩ năng cốt lõi: chạy tay thuật toán chính xác.

Kiến thức trọng tâm

Bảng tổng hợp thuật toán

Thuật toán Ý tưởng Điều kiện Tốt nhất Xấu nhất
Tìm kiếm tuần tự Duyệt lần lượt từ đầu tới khi gặp Không cần sắp xếp O(1)O(1) O(n)O(n)
Tìm kiếm nhị phân So với phần tử giữa, bỏ một nửa Dãy đã sắp xếp O(1)O(1) O(log⁡n)O(\log n)
Sắp xếp chọn Mỗi lượt chọn phần tử nhỏ nhất đưa lên đầu đoạn chưa xếp O(n2)O(n^2) O(n2)O(n^2)
Sắp xếp nổi bọt Đổi chỗ cặp kề nhau sai thứ tự, phần tử lớn "nổi" về cuối O(n)O(n) nếu có dừng sớm O(n2)O(n^2)
Sắp xếp chèn Chèn từng phần tử vào đúng chỗ trong đoạn đầu đã xếp O(n)O(n) khi dãy đã tăng O(n2)O(n^2)

Tìm kiếm nhị phân

def nhi_phan(a, x):
    trai, phai = 0, len(a) - 1
    while trai <= phai:
        giua = (trai + phai) // 2
        if a[giua] == x:
            return giua
        if a[giua] < x:
            trai = giua + 1
        else:
            phai = giua - 1
    return -1

a = [2, 5, 9, 14, 20, 27, 33, 41, 48]
print(nhi_phan(a, 33), nhi_phan(a, 10))
#include <iostream>
#include <vector>
using namespace std;
int nhi_phan(vector<int>& a, int x) {
    int trai = 0, phai = a.size() - 1;
    while (trai <= phai) {
        int giua = (trai + phai) / 2;
        if (a[giua] == x)
            return giua;
        if (a[giua] < x)
            trai = giua + 1;
        else
            phai = giua - 1;
    }
    return -1;
}
int main() {
    vector<int> a = {2, 5, 9, 14, 20, 27, 33, 41, 48};
    cout << nhi_phan(a, 33) << " " << nhi_phan(a, 10);
    return 0;
}

Chương trình in 6 -1. Vết khi tìm x = 10:

Bước trai phai giua a[giua] So sánh với 10
1 0 8 4 20 lớn hơn → phai = 3
2 0 3 1 5 nhỏ hơn → trai = 2
3 2 3 2 9 nhỏ hơn → trai = 3
4 3 3 3 14 lớn hơn → phai = 2
3 2 trai > phai → dừng, trả về −1

Tìm x = 33 chỉ cần 2 bước (gặp 20 rồi 33). Với n phần tử, số bước tối đa khoảng log⁡2n+1\log_2 n + 1: 1000 phần tử chỉ cần tối đa 10 bước, trong khi tìm tuần tự có thể phải so sánh cả 1000 phần tử.

Ba thuật toán sắp xếp đơn giản

Cả ba chương trình in dãy sau mỗi lượt của vòng lặp ngoài, chạy với dãy 6 3 8 2 5.

Sắp xếp chọn

def sap_xep_chon(a):
    n = len(a)
    for i in range(n - 1):
        vt = i
        for j in range(i + 1, n):
            if a[j] < a[vt]:
                vt = j
        a[i], a[vt] = a[vt], a[i]
        for x in a:
            print(x, end=" ")
        print()

sap_xep_chon([6, 3, 8, 2, 5])
#include <iostream>
#include <vector>
using namespace std;
void sap_xep_chon(vector<int> a) {
    int n = a.size();
    for (int i = 0; i < n - 1; i++) {
        int vt = i;
        for (int j = i + 1; j < n; j++)
            if (a[j] < a[vt])
                vt = j;
        swap(a[i], a[vt]);
        for (int x : a) cout << x << " ";
        cout << endl;
    }
}
int main() {
    sap_xep_chon({6, 3, 8, 2, 5});
    return 0;
}

Sắp xếp chèn

def sap_xep_chen(a):
    for i in range(1, len(a)):
        k = a[i]
        j = i - 1
        while j >= 0 and a[j] > k:
            a[j + 1] = a[j]
            j = j - 1
        a[j + 1] = k
        for x in a:
            print(x, end=" ")
        print()

sap_xep_chen([6, 3, 8, 2, 5])
#include <iostream>
#include <vector>
using namespace std;
void sap_xep_chen(vector<int> a) {
    for (int i = 1; i < (int)a.size(); i++) {
        int k = a[i];
        int j = i - 1;
        while (j >= 0 && a[j] > k) {
            a[j + 1] = a[j];
            j = j - 1;
        }
        a[j + 1] = k;
        for (int x : a) cout << x << " ";
        cout << endl;
    }
}
int main() {
    sap_xep_chen({6, 3, 8, 2, 5});
    return 0;
}

Sắp xếp nổi bọt xem ở Dạng 2. Vết của cả ba thuật toán trên dãy 6 3 8 2 5:

Sau lượt Chọn Nổi bọt Chèn
1 2 3 8 6 5 (đổi 6 và 2) 3 6 2 5 8 (8 về cuối) 3 6 8 2 5 (chèn 3)
2 2 3 8 6 5 (3 đã đúng chỗ) 3 2 5 6 8 3 6 8 2 5 (8 giữ nguyên)
3 2 3 5 6 8 (đổi 8 và 5) 2 3 5 6 8 2 3 6 8 5 (chèn 2 lên đầu)
4 2 3 5 6 8 2 3 5 6 8 2 3 5 6 8 (chèn 5)

Độ phức tạp thời gian

Độ phức tạp đánh giá số phép toán cơ bản theo kích thước dữ liệu n, viết dạng O(⋅)O(\cdot): bỏ hằng số, giữ số hạng bậc cao nhất. Hai vòng lặp lồng nhau → nhân; hai đoạn nối tiếp → lấy đoạn lớn hơn.

n = 8
s = 0
for i in range(n):
    for j in range(i, n):
        s = s + 1
d = 0
k = 1
while k < n:
    k = k * 2
    d = d + 1
print(s, d)

In 36 3. Vòng lồng chạy n+(n−1)+⋯+1=n(n+1)2n + (n-1) + \dots + 1 = \dfrac{n(n+1)}{2} lần → O(n2)O(n^2). Vòng while nhân đôi k nên chạy khoảng log⁡2n\log_2 n lần → O(log⁡n)O(\log n). Cả đoạn: O(n2)O(n^2).

Độ phức tạp Tên gọi Ví dụ
O(1)O(1) Hằng số Truy cập a[i], hoán đổi hai biến
O(log⁡n)O(\log n) Lôgarit Tìm kiếm nhị phân
O(n)O(n) Tuyến tính Tìm tuần tự, tính tổng dãy
O(n2)O(n^2) Bình phương Sắp xếp chọn, nổi bọt, chèn

Kiểm thử và lập trình có cấu trúc

  • Kiểm thử bằng nhiều bộ dữ liệu: trường hợp thông thường, biên (phần tử ở đầu, ở cuối, dãy một phần tử, dãy rỗng), đặc biệt (số âm, các giá trị bằng nhau, dãy đã sắp xếp). Kiểm thử phát hiện lỗi nhưng không chứng minh chương trình hết lỗi.
  • Làm mịn dần: chia bài toán lớn thành các bước nhỏ, chi tiết hóa từng bước cho tới khi viết được thành lệnh.
  • Mô đun hóa: tách mỗi nhiệm vụ thành một hàm (nhập, xử lí, in kết quả) → dễ đọc, dễ kiểm thử, dùng lại được; gom các hàm hay dùng thành thư viện.

Dạng bài thường gặp trong đề thi

Dạng 1: Chọn bộ dữ liệu kiểm thử phát hiện lỗi

Xem lời giải

Chương trình in 9 0. Với [3, 9, 4] kết quả đúng, nhưng với dãy toàn số âm [-5, -2, -9] hàm trả về 0 (giá trị khởi tạo) thay vì −2. Lỗi: khởi tạo m = 0. Sửa: m = a[0]. Bộ dữ liệu đặc biệt (toàn số âm) mới lộ ra lỗi này.

Dạng 2: Câu đúng/sai về sắp xếp nổi bọt có dừng sớm

Xem lời giải
  • a) Đúng. Lượt 1 (j = 0…3): đổi (2, 1) → 1 2 3 5 4; (2, 3), (3, 5) giữ nguyên; đổi (5, 4) → 1 2 3 4 5.
  • b) Sai. Lượt 2 (j = 0…2) không đổi chỗ lần nào nên doi vẫn là False; chương trình in dòng thứ hai rồi dừng → 2 dòng.
  • c) Đúng. Lượt 1 có 4 phép so sánh, lượt 2 có 3 → 4+3=74 + 3 = 7.
  • d) Sai. Không dừng sớm thì chạy đủ 4 lượt (in 4 dòng), nhưng dãy đã tăng dần nên dòng cuối vẫn là 1 2 3 4 5. Dừng sớm chỉ giúp chạy nhanh hơn: trường hợp tốt nhất còn O(n)O(n).

Dạng 3: Xác định độ phức tạp

Xem lời giải

Phần 1: O(n2)O(n^2). Phần 2: m lần, mỗi lần O(log⁡n)O(\log n) → O(mlog⁡n)O(m \log n). Hai phần nối tiếp nên tổng là O(n2+mlog⁡n)O(n^2 + m \log n); nếu m không vượt quá n thì mlog⁡n≤n2m \log n \le n^2, độ phức tạp là O(n2)O(n^2).

Lỗi thường gặp

Mẹo làm bài

Tự kiểm tra nhanh

  1. Với dãy đã sắp xếp gồm 9 phần tử ở trên, tìm x = 48 bằng hàm nhi_phan cần so sánh với những phần tử nào?
Xem đáp án

20 (giua = 4) → 33 (giua = 6) → 41 (giua = 7) → 48 (giua = 8): 4 lần, trả về vị trí 8.

  1. Sắp xếp chọn dãy 6 3 8 2 5 (tăng dần) thực hiện bao nhiêu lần đổi chỗ thực sự (hai phần tử khác vị trí)?
Xem đáp án

2 lần: lượt 1 đổi 6 và 2, lượt 3 đổi 8 và 5. Lượt 2 và lượt 4 phần tử nhỏ nhất đã ở đúng chỗ.

  1. Dãy đã tăng dần gồm n phần tử. Sắp xếp chèn thực hiện bao nhiêu phép so sánh a[j] > k?
Xem đáp án

n−1n - 1 phép: mỗi lượt chỉ so sánh một lần với phần tử ngay trước rồi dừng → O(n)O(n).

  1. Đoạn for i in range(n): for j in range(n): ... lồng nhau có độ phức tạp gì? Nếu thay vòng trong bằng for j in range(5) thì sao?
Xem đáp án

O(n2)O(n^2). Vòng trong chạy 5 lần cố định → tổng 5n5n lần → O(n)O(n).

  1. Vì sao "chạy thử 10 bộ dữ liệu đều đúng" chưa chứng minh được chương trình không có lỗi?
Xem đáp án

Kiểm thử chỉ kiểm tra các trường hợp đã chọn; có thể còn trường hợp chưa thử (biên, đặc biệt) làm chương trình sai.

Kiểm tra lại kiến thức vừa ôn

Làm 10 câu luyện tập về kĩ thuật lập trình và thuật toán (định hướng cs) và xem lời giải ngay sau mỗi câu.

Luyện tập ngay

Đề có câu hỏi về chủ đề này