Tư duy Tính toán

Tìm kiếm & Sắp xếp

Trường ĐH Công nghệ – Đại học Quốc gia Hà Nội

Nội dung

  1. Động lực & tình huống thực tế
  2. Thuật toán tìm kiếm
    • Tìm kiếm tuyến tính (Linear Search)
    • Tìm kiếm nhị phân (Binary Search)
  3. Thuật toán sắp xếp
    • Bubble Sort
    • Selection Sort
    • Insertion Sort
    • Merge Sort
    • Quick Sort

1.
Động lực & tình huống thực tế

Tìm kiếm trong đời sống hằng ngày

Từ lúc thức dậy đến khi vào lớp, bạn đã “tìm kiếm” bao nhiêu lần?

  • Tìm điện thoại trong phòng.
  • Tìm file bài tập trên máy tính.
  • Tìm email của giảng viên.
  • Tìm môn học trên LMS.

Sắp xếp trong hoạt động hằng ngày

Gần đây bạn đã sắp xếp những gì?

  • Email theo thời gian hoặc người gửi.
  • Ảnh theo ngày chụp.
  • Playlist nhạc theo ca sĩ.
  • Bảng điểm từ cao → thấp.
  • Tin nhắn theo mức ưu tiên.

Thử thách thực sự

  • Q1: Tìm tên của bạn trong danh sách 5000 sinh viên?
  • Q2: Khác biệt gì khi tìm kiếm trên:
    • Danh sách chưa sắp xếp?
    • Danh sách đã sắp xếp?

2.
Thuật toán tìm kiếm

Thuật toán tìm kiếm

  • Tìm kiếm là một trong những tác vụ cơ bản mà máy tính thực hiện.
  • Thuật toán tìm kiếm được thiết kế để:
    • Kiểm tra xem một phần tử có tồn tại hay không.
    • Hoặc truy xuất phần tử từ nguồn dữ liệu.
  • Hai yếu tố quan trọng khi chọn thuật toán:
    • Danh sách đã sắp xếp hay chưa.
    • Các phần tử duy nhất hay có trùng lặp.

Các loại tìm kiếm

Loại Mô tả Ví dụ
Tuần tự Duyệt lần lượt từng phần tử. Linear Search
Theo khoảng Chia dãy thành các khoảng để thu hẹp vùng tìm kiếm. Binary Search (trên dãy đã sắp xếp)

Tìm kiếm tuyến tính

Linear Search

  • Dữ liệu có thể chưa sắp xếp.
  • Bắt đầu từ phần tử đầu tiên trong mảng / danh sách.
  • So sánh lần lượt với giá trị cần tìm V.
  • Dừng khi:
    • Tìm thấy phần tử phù hợp.
    • Hoặc đã duyệt hết danh sách mà không tìm thấy.

Tìm kiếm tuyến tính

Ví dụ

Mảng chưa sắp xếp, cần tìm V = 40.

Mảng ban đầu:

0 1 2 3 4 5 6 7 8 30 11 70 40 41 14 57 52 25

Tìm kiếm tuyến tính

Ví dụ

Bước 1: So sánh V với phần tử đầu tiên.

0 1 2 3 4 5 6 7 8 30 11 70 40 41 14 57 52 25 V ≠ 30

Nếu V không trùng phần tử đầu tiên, ta chuyển sang phần tử tiếp theo và lặp lại quá trình cho đến khi tìm thấy hoặc duyệt hết mảng.

Tìm kiếm tuyến tính

Ví dụ

0 1 2 3 4 5 6 7 8 30 11 70 40 41 14 57 52 25 V ≠ 11 0 1 2 3 4 5 6 7 8 30 11 70 40 41 14 57 52 25 V ≠ 70

Tìm kiếm tuyến tính

Ví dụ

0 1 2 3 4 5 6 7 8 30 11 70 40 41 14 57 52 25 V = 40 Kết quả: chỉ số 3

Khi tìm thấy phần tử bằng V, thuật toán trả về chỉ số của phần tử đó (ở đây là 3).

Nếu không tìm thấy phần tử?

Tìm vị trí của V = 50.

0 1 2 3 4 5 6 7 8 30 11 70 40 41 14 57 52 25 V ≠ 25 → Result = -1

Nếu phần tử cần tìm không xuất hiện trong danh sách, thuật toán thường trả về một giá trị đặc biệt để báo thất bại. Trong ví dụ này, giá trị trả về là -1 khi V không có trong mảng.

Linear search – Cài đặt

Một vị trí xuất hiện

Trả về chỉ số của lần xuất hiện đầu tiên, hoặc -1 nếu không có.


# Tìm kiếm tuyến tính – Độ phức tạp O(n)
def linear_search(arr, value):
    for i in range(len(arr)):
        if arr[i] == value:  # tìm thấy phần tử
            return i
    return -1  # không tìm thấy
          

⚠️ Tệ nhất phải kiểm tra n phần tử.

Nếu có nhiều lần xuất hiện?

  • Ví dụ 3: V = 70, mảng chứa nhiều số 70.
  • Thuật toán ở trên dừng ngay khi thấy 70 đầu tiên.
  • Nhưng đôi khi ta muốn tất cả vị trí chứa giá trị đó.

Linear search – Cài đặt

Nhiều vị trí xuất hiện

Trả về danh sách các chỉ số của các phần tử phù hợp.


# Tìm kiếm tuyến tính cho nhiều lần xuất hiện – Độ phức tạp O(n)
def linear_search_all(arr, value):
    indices = []
    for i in range(len(arr)):
        if arr[i] == value:
            indices.append(i)
    return indices
          
  • Tập hợp tất cả chỉ số phù hợp vào một list.
  • Trả về list rỗng nếu không có phần tử nào.

Tìm kiếm nhị phân

Binary Search

  • Làm việc trên mảng đã sắp xếp.
  • Ý tưởng: luôn so sánh ở giữa đoạn đang xét.
  • Nếu value < middle → chỉ tìm ở nửa trái.
  • Nếu value ≥ middle → chỉ tìm ở nửa phải.
  • Tiếp tục chia đôi cho đến khi tìm thấy hoặc không còn đoạn hợp lệ.

Tìm kiếm nhị phân

Ví dụ

Cho một mảng đã được sắp xếp. Tìm: V = 57.

0 1 2 3 4 5 6 7 8 11 14 25 30 40 41 52 57 70

Tìm kiếm nhị phân

Ví dụ

Bước 1: Khởi tạo left = 0, right = 8.
Xét vị trí mid = (left + right) / 2 = 4.

0 1 2 3 4 5 6 7 8 11 14 25 30 40 41 52 57 70 Arr[mid] = 40 < V left = mid + 1 = 5 right = 8 mid = (left + right) / 2 = 6

Tìm kiếm nhị phân

Ví dụ

Hiện tại: left = 5, right = 8, V = 57.

0 1 2 3 4 5 6 7 8 11 14 25 30 40 41 52 57 70 Arr[mid] = 52 < V left = mid + 1 = 7 right = 8 mid = (left + right) / 2 = 7

Tìm kiếm nhị phân

Ví dụ

Hiện tại: left = 7, right = 8.

0 1 2 3 4 5 6 7 8 11 14 25 30 40 41 52 57 70 Arr[mid] = 57 = V Result = 7

Bây giờ phần tử cần tìm đã được tìm thấy, thuật toán trả về chỉ số tương ứng (7).

Binary search – Cài đặt


# Tìm kiếm nhị phân cho một vị trí xuất hiện
# Độ phức tạp O(log n)
def binary_search(arr, value):
    left, right = 0, len(arr) - 1
    while left <= right:  # đảm bảo khoảng tìm kiếm hợp lệ
        mid = (left + right) // 2
        if arr[mid] == value:
            return mid
        elif arr[mid] < value:
            left = mid + 1
        else:
            right = mid - 1
    return -1
          

So sánh hai kiểu tìm kiếm

Linear Search Binary Search
Dữ liệu phải sắp xếp? ❌ Không cần ✅ Bắt buộc
Ý tưởng Duyệt từng phần tử từ đầu đến cuối. Liên tục chia đôi khoảng tìm kiếm.
Độ phức tạp O(n) O(log n)
Ưu điểm Đơn giản, dễ cài đặt. Rất nhanh với dữ liệu lớn đã sắp xếp.
Nhược điểm Chậm với danh sách dài. Phải trả giá cho bước sorting ban đầu.

3.
Thuật toán sắp xếp

Thuật toán Sắp xếp

  • Giúp sắp xếp mảng / danh sách theo một thứ tự xác định.
  • Máy tính dùng sắp xếp liên tục khi xử lý dữ liệu.
  • Nhóm chính:
    • Comparison-based: Bubble, Selection, Insertion, Merge, Quick…
    • Non-comparison: Counting Sort, Radix Sort…
    • Thuật toán lai: IntroSort = Quick + Heap + Insert.

Bubble Sort – Ý tưởng

  • Liên tục so sánh và hoán đổi các cặp phần tử kề nhau nếu sai thứ tự.
  • Phần tử “lớn nhất” sẽ “nổi” dần về cuối mảng sau mỗi lượt duyệt.
  • Độ phức tạp tệ nhất: O(n²).

Đơn giản nhưng kém hiệu quả cho dữ liệu lớn.

Bubble Sort – Các bước

  1. Lặp qua toàn bộ danh sách nhiều lần.
  2. Mỗi lượt:
    • So sánh từng cặp phần tử kề nhau.
    • Nếu đi sai thứ tự thì hoán đổi vị trí.
  3. Lặp cho đến khi không còn hoán đổi nào nữa (danh sách đã được sắp xếp).

Bubble Sort – Cài đặt


# Bubble sort – Complexity O(n^2)
def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        swapped = False
        for j in range(0, n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                swapped = True
        if not swapped:
            break
          

Không có hoán đổi trong một lượt → mảng đã được sắp xếp.

Minh họa Bubble Sort

Xem minh họa tại:

Selection Sort – Ý tưởng

  • Mỗi bước chọn phần tử nhỏ nhất trong phần chưa sắp xếp.
  • Đưa nó ra đầu vùng chưa sắp xếp (hoán đổi với phần tử đầu vùng đó).
  • Lặp lại với phần còn lại của mảng.

Selection Sort – Các bước

  1. Tìm phần tử nhỏ nhất trong toàn mảng, hoán đổi với phần tử đầu tiên.
  2. Tìm phần tử nhỏ nhất trong phần còn lại, hoán đổi với phần tử thứ hai.
  3. Lặp lại cho đến khi toàn bộ mảng được sắp xếp.

Selection Sort – Cài đặt


# Selection sort – Complexity O(n^2)
def selection_sort(arr):
    n = len(arr)
    for i in range(n):
        min_idx = i
        for j in range(i + 1, n):
            if arr[j] < arr[min_idx]:
                min_idx = j  # Update  min_idx
        arr[i], arr[min_idx] = arr[min_idx], arr[i]  # Swap
          

Mỗi vòng lặp i đặt thêm một phần tử vào đúng vị trí cuối cùng của nó.

Minh họa Selection Sort

Xem minh họa tại:

Insertion Sort – Ý tưởng

  • Xem phần đầu mảng là đoạn đã sắp xếp.
  • Chèn từng phần tử tiếp theo vào vị trí đúng trong đoạn đã sắp xếp.

Insertion Sort – Các bước

  1. Xem phần tử đầu tiên là mảng con đã sắp xếp.
  2. So sánh phần tử thứ hai với phần tử thứ nhất, nếu cần thì hoán đổi.
  3. Di chuyển sang phần tử tiếp theo, chèn vào vị trí đúng trong mảng con đã sắp xếp.
  4. Lặp lại cho đến hết mảng.

Insertion Sort – Cài đặt


# Insertion sort – Complexity O(n^2)
def insertion_sort(arr):
    for i in range(1, len(arr)):
        key = arr[i]  # Current element to be sorted
        j = i - 1   
        while j >= 0 and arr[j] > key:
             arr[j + 1] = arr[j]  # Shift element > key to the right
             j -= 1
         arr[j + 1] = key  # Insert key at the correct position
          

Hiệu quả hơn Bubble/Selection khi mảng gần như đã sắp xếp.

Minh họa Insertion Sort

Xem minh họa tại:

Merge Sort – Ý tưởng

  • Dựa trên nguyên lý chia để trị (divide and conquer).
  • Ba bước chính:
    1. Divide: Chia dãy thành hai nửa cho đến khi không thể chia nhỏ hơn.
    2. Conquer: Sắp xếp từng nửa nhỏ bằng chính Merge Sort.
    3. Merge: Trộn hai nửa đã sắp xếp thành một dãy lớn đã sắp xếp.
  • Độ phức tạp: O(n log n) trong mọi trường hợp.

Merge Sort – Cài đặt


# Merge sort – Complexity O(n log n) 
def merge_sort(arr):
      if len(arr) <= 1:
         return arr    
      mid = len(arr) // 2
      left = arr[:mid]
      right = arr[mid:]
      sorted_left = merge_sort(left)
      sorted_right = merge_sort(right)
      return merge(sorted_left, sorted_right)
          

Merge Sort – Cài đặt


def merge(left, right):
     res = []
     i = j = 0 
     while i < len(left) and j < len(right):
         if left[i] < right[j]:
             res.append(left[i])
             i += 1
         else:
             res.append(right[j])
             j += 1     
     res.extend(left[i:])
     res.extend(right[j:])
     return res
          

Minh họa Merge Sort

Xem minh họa tại:

Quick Sort – Ý tưởng

  • Cũng dựa trên chia để trị.
  • Chọn một phần tử làm pivot.
  • Phân hoạch mảng:
    • Các phần tử <= pivot sang bên trái.
    • Các phần tử > pivot sang bên phải.
  • Gọi đệ quy Quick Sort trên hai nửa.
  • Dừng khi mỗi đoạn chỉ còn 0 hoặc 1 phần tử.

Quick Sort – Cài đặt


# Quick sort 
def partition(arr, low, high):
    pivot = arr[high]  # pivot
    i = low - 1
    for j in range(low, high):
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return i + 1
          

Quick Sort – Cài đặt


def quickSort(arr, low, high):
    if low < high:
        pi = partition(arr, low, high)
        quickSort(arr, low, pi - 1)
        quickSort(arr, pi + 1, high)
          

Minh họa Quick Sort

Xem minh họa tại:

So sánh thuật toán sắp xếp

Thuật toán Độ phức tạp T(n) Ổn định? Đặc điểm chính Khi nên dùng
Bubble Best: O(n)
Avg/Worst: O(n²)
Đơn giản, dễ hiểu; rất nhiều phép so sánh & hoán đổi. Chỉ dùng cho ví dụ học thuật, dữ liệu rất nhỏ.
Selection Best/Avg/Worst: O(n²) Ít hoán đổi hơn Bubble; luôn quét hết phần chưa sắp xếp. Khi chi phí hoán đổi cao nhưng n vẫn nhỏ.
Insertion Best: O(n)
Avg/Worst: O(n²)
Hiệu quả khi dữ liệu gần như đã sắp xếp. Danh sách nhỏ / gần sắp xếp; bước con trong thuật toán lai.
Merge Best/Avg/Worst: O(n log n) Chia để trị, cần bộ nhớ phụ cho bước trộn. Khi cần hiệu năng ổn định, dữ liệu rất lớn.
Quick Best/Avg: O(n log n)
Worst: O(n²)
⚠️ Tuỳ cài đặt Rất nhanh trong thực tế; phụ thuộc chọn pivot. Sắp xếp trong bộ nhớ, hiệu năng cao cho đa số trường hợp.

Tổng kết

  • Searching & Sorting là các “khối xây dựng” cốt lõi cho nhiều thuật toán và cấu trúc dữ liệu.
  • Tìm kiếm:
    • Linear search: đơn giản, không cần sắp xếp, O(n).
    • Binary search: cần dãy đã sắp xếp, rất nhanh, O(log n).
  • Sắp xếp:
    • Bubble / Selection / Insertion: dễ hiểu, phù hợp dạy & demo.
    • Merge / Quick: hiệu quả cho dữ liệu lớn (thường dùng trong thực tế).
  • Thông điệp cho tư duy tính toán:
    • Chọn thuật toán phù hợp với bối cảnh.
    • Cân bằng giữa đơn giản & hiệu năng.
    • Hiểu độ phức tạp thời gian giúp thiết kế giải pháp scalable.

Bài tập thực hành

  • Bài 1: Sorting Race
    Cài đặt nhiều thuật toán sắp xếp và đo thời gian chạy bằng module time trong Python.
  • Bài 2: Median Matters
    Nếu chọn trung vị của mảng làm pivot trong Quick Sort, cài đặt sẽ như thế nào? Ưu điểm so với các cách chọn pivot khác?
  • Bài 3: Unsorted vs Sorted Search
    Có thể dùng Binary Search trên mảng chưa sắp xếp không? So sánh hiệu năng với Linear Search.