Đi từ việc lớn đến việc làm được
Đầu vào là dãy số `a`; đầu ra là chính dãy ấy được xếp theo thứ tự **không giảm**. Ý tưởng ban đầu: coi phần bên trái đã sắp, lần lượt chèn phần tử tiếp theo vào đúng chỗ. Câu “chèn vào đúng chỗ” còn quá rộng để máy thực hiện, nên cần làm mịn thành ba việc: lưu `a[i]` vào `value`; dịch các phần tử ở bên trái **lớn hơn** `value` sang phải; đặt `value` vào ô vừa để trống. Sau mỗi lượt, phần từ đầu đến chỉ số `i` đã được sắp. Đây là cách thiết kế từ yêu cầu chung đến các lệnh cụ thể.
Một lượt chèn có thể kiểm bằng tay
Với `a = [2, 5, 4, 7]`, phần `[2, 5]` đã sắp. Lấy `value = 4`, dịch 5 sang phải để được chỗ trống tại chỉ số 1, rồi đặt 4 vào đó: `[2, 4, 5, 7]`. Không cần dịch 2 vì `2 ≤ 4`.
Trong mã dưới đây, `j` đi từ ô ngay trước `i` về bên trái:
```python
def sap_chen(a):
for i in range(1, len(a)):
value = a[i]
j = i - 1
while j >= 0 and a[j] > value:
a[j + 1] = a[j]
j -= 1
a[j + 1] = value
return a
```
Với dãy rỗng hoặc chỉ có một phần tử, vòng `for` không chạy và dãy giữ nguyên.
Bài luyện
Trong cách làm mịn dần sắp xếp chèn, yêu cầu “chèn a[i] vào đúng chỗ” được chi tiết thành những việc nào?
Không cần xoá giá trị khỏi dãy.
Ba việc này cụ thể hóa thao tác chèn.
Một phép đổi chưa chắc đưa giá trị đúng chỗ.
Đếm độ dài không sắp xếp.
Lời giải
Trước khi dịch phải giữ a[i] trong value; sau khi dịch các giá trị lớn hơn, đặt value vào vị trí j+1.
Bài luyện
Khi chèn 4 vào phần đã sắp `[2, 5]`, phần đầu sau khi chèn là gì?
2 nhỏ hơn 4 nên ở trước.
4 chưa được chèn đúng vị trí.
Dịch 5 rồi đặt 4 sau 2.
5 lớn hơn nên phải đứng sau 4.
Lời giải
5 > 4 được dịch một ô; 2 ≤ 4 nên dừng, cho [2, 4, 5].
Bài luyện
Vì sao phải lưu `a[i]` vào `value` trước khi dịch các phần tử?
Phép dịch có thể ghi đè ô tại i.
Một phép gán chưa sắp xếp được dãy.
Không có việc đổi kiểu.
Vòng lặp vẫn cần dịch các phần tử.
Lời giải
Khi a[j+1] = a[j], ô cũ của a[i] có thể bị ghi đè; value giữ giá trị để đặt lại sau khi dịch.
Đang học