Thao tác với danh sách liên kết¶
Tóm lược nội dung
Bài này trình bày các thao tác thêm và xoá node trong danh sách liên kết.
Định nghĩa các lớp¶
Về mã lệnh trong bài này
Việc viết mã lệnh cho danh sách liên kết trong bài này đòi hỏi kiến thức về lập trình hướng đối tượng và kỹ thuật lập trình, vốn nằm ngoài chương trình phổ thông. Do đó, một số câu lệnh sẽ chỉ diễn giải sơ nét. Bạn có thể ghép các đoạn mã thành chương trình hoàn chỉnh mà không cần quá quan tâm chi tiết kỹ thuật.
1. Định nghĩa lớp node gồm hai thành phần data và next, đóng vai trò làm khuôn mẫu cho mỗi node.
2. Định nghĩa lớp LinkedList, đóng vai trò làm khuôn mẫu cho danh sách liên kết.
Trong danh sách liên kết, thuộc tính head dùng để nắm giữ node đầu tiên. head đóng vai trò là điểm khởi đầu của danh sách liên kết. Dựa vào head, ta có thể duyệt từng node trong danh sách liên kết.
Khởi tạo¶
1. Trong chương trình chính, ta khai báo biến char_list có kiểu LinkedList vừa định nghĩa.
Lúc này, danh sách liên kết char_list là rỗng, chưa có node nào.
2. Khởi tạo ba node, lần lượt đặt tên là first, second và third. Thành phần data của mỗi node chứa một ký tự.
Lúc này, cả ba node đều là đơn lẻ, chưa liên kết với nhau. Xem hình dưới đây.
Ba node chưa liên kết với nhau
3. Liên kết các node bằng cách:
3.1. Đặt first làm node đầu tiên của danh sách liên kết char_list.
3.2. Liên kết first với second.
3.3. Liên kết second với third.
Duyệt và in¶
Duyệt danh sách liên kết
Duyệt danh sách liên kết là tiến trình di chuyển qua từng node, xuất phát từ node đầu tiên (do biến head trỏ đến) và đi đến node cuối cùng (là node trỏ đến None).
Cụ thể, ta dùng một biến phụ, gọi là current, để lần lượt trỏ đến từng node như sau:
- Cho
currenttrỏ đến node đầu tiên (doheadtrỏ đến). - In ra dữ liệu của node mà
currentđang trỏ đến. - Cho
currenttrỏ đến node kế tiếp. - Lặp lại bước 2 và bước 3 cho đến khi
currenttrỏ đếnNone.
Dựa theo thuật toán trên, ta viết chương trình như sau:
1. Viết hàm print_linked_list() dùng để duyệt danh sách liên kết và in ra dữ liệu của từng node.
2. Trong chương trình chính, gọi hàm print_linked_list() ra thực hiện.
3. Chạy chương trình trên, kết quả như sau:
Thêm node mới¶
Khi thêm hoặc xoá một node trong danh sách liên kết, ta không hề di chuyển các node, mà chỉ đang tái thiết lập các liên kết giữa chúng.
Thêm và xoá node
Về bản chất, các thao tác thêm hoặc xoá node đều là thay đổi giá trị của thành phần next của các node.
Chèn vào đầu¶
Để chèn một node mới vào đầu danh sách, ta thực hiện các bước sau:
- Khởi tạo node mới, đặt tên là
new_node, chứa dữ liệu cần chènnew_data. - Liên kết
new_nodevới node đầu tiên của danh sách liên kết (doheadđang trỏ đến). - Cập nhật
headđể nó chuyển sang trỏ đếnnew_node.
Dựa theo thuật toán trên, ta viết chương trình như sau:
1. Viết hàm insert_at_head() dùng để chèn một node mới vào đầu danh sách liên kết.
2. Trong chương trình chính, gọi hàm insert_at_head() ra thực hiện.
3. Chạy chương trình trên, kết quả như sau:
Danh sách liên kết ban đầu: [o] -> [l] -> [d] -> None
Chèn [c] vào đầu danh sách liên kết: [c] -> [o] -> [l] -> [d] -> None
Thêm vào cuối¶
Để thêm một node mới vào cuối danh sách liên kết, ta thực hiện các bước sau:
- Khởi tạo node mới, đặt tên là
new_node, chứa dữ liệu cần chènnew_data. - Kiểm tra danh sách liên kết: nếu danh sách đang rỗng thì ta chỉ cần cho
headtrỏ vàonew_node. -
Ngược lại, danh sách không rỗng, thì tìm node cuối cùng bằng cách:
Dùng biến tạm
currentđể duyệt danh sách liên kết cho đến khi tìm được node cuối cùng. -
Liên kết node cuối cùng với
new_node.
Dựa theo thuật toán trên, ta viết chương trình như sau:
1. Viết hàm insert_at_end() dùng để chèn một node mới vào cuối danh sách liên kết.
2. Trong chương trình chính, gọi hàm insert_at_end() ra thực hiện.
3. Chạy chương trình trên, kết quả như sau:
Danh sách liên kết ban đầu: [o] -> [l] -> [d] -> None
Chèn [c] vào đầu danh sách liên kết: [c] -> [o] -> [l] -> [d] -> None
Thêm [y] vào cuối danh sách liên kết: [c] -> [o] -> [l] -> [d] -> [y] -> None
Chèn vào trước một node xác định¶
Giả sử node có dữ liệu k đã tồn tại trong danh sách liên kết và không phải là node đầu tiên.
Để chèn node mới vào trước node k, thử thách là phải tìm được node liền trước node k. Vì khi quá node k, ta không thể quay lại được.
Để giải quyết thử thách này, ta áp dụng kỹ thuật "hai người dắt tay nhau" như sau:
- Biến
currentđi trước để tìm nodek. - Biến
previousđi "nôi gót" theo sau.
Khi current tìm thấy node k, thì previous chính là node liền trước node k, đóng vai trò điểm tựa để ta chèn node mới vào.
Cụ thể, ta thực hiện các bước sau:
-
Khởi tạo node mới, đặt tên là
new_node, chứa dữ liệunew_data. -
Cho
currentxuất phát từ node đầu tiên, cònpreviouslàNone. -
Duyệt danh sách liên kết bằng
currentcho đến khi tìm thấy nodek:- Nếu tìm thấy
kthì ngắt vòng lặp. - Ngược lại, chưa tìm thấy, thì cho
previousthay thếcurrent, rồi chocurrentdi chuyển đến node kế tiếp.
- Nếu tìm thấy
-
Liên kết
new_nodevớicurrent. -
Liên kết
previousvớinew_node.
Dựa theo thuật toán trên, ta viết chương trình như sau:
2. Trong chương trình chính, ta gọi hàm insert_before() ra thực hiện.
3. Chạy chương trình trên, kết quả như sau:
Danh sách liên kết ban đầu: [o] -> [l] -> [d] -> None
Chèn [c] vào đầu danh sách liên kết: [c] -> [o] -> [l] -> [d] -> None
Thêm [y] vào cuối danh sách liên kết: [c] -> [o] -> [l] -> [d] -> [y] -> None
Chèn [y] vào trước [d]: [c] -> [o] -> [l] -> [y] -> [d] -> [y] -> None
Về việc chèn vào trước một node
Hàm insert_before() vừa viết chỉ áp dụng cho trường hợp lý tưởng, đó là node k đã tồn tại trong danh sách liên kết và không phải là node đầu tiên.
Khi viết đầy đủ, ta cần xét thêm các trường hợp:
- Danh sách liên kết rỗng.
- Node
klà node đầu tiên. - Node
kkhông tồn tại.
Xoá node¶
Xoá node đầu tiên¶
Để xoá node đầu tiên trong danh sách liên kết, ta thực hiện các bước sau:
- Kiểm tra danh sách liên kết: nếu danh sách rỗng thì không có gì để xoá, ta kết thúc hàm.
-
Ngược lại, danh sách không rỗng, ta cập nhật
headđể nó chuyển sang trỏ đến node kế tiếp củacurrent.Node đầu tiên không còn ai quản lý sẽ tự động bị hệ thống loại bỏ.
Dựa theo thuật toán trên, ta viết chương trình như sau:
1. Viết hàm remove_head() dùng để xoá node đầu tiên trong danh sách liên kết.
2. Trong chương trình chính, ta gọi hàm remove_head() ra thực hiện.
3. Chạy chương trình trên, kết quả như sau:
Danh sách liên kết ban đầu: [o] -> [l] -> [d] -> None
Chèn [c] vào đầu danh sách liên kết: [c] -> [o] -> [l] -> [d] -> None
Thêm [y] vào cuối danh sách liên kết: [c] -> [o] -> [l] -> [d] -> [y] -> None
Chèn [y] vào trước [d]: [c] -> [o] -> [l] -> [y] -> [d] -> [y] -> None
Xoá node đầu tiên: [o] -> [l] -> [y] -> [d] -> [y] -> None
Xoá node cuối cùng¶
Để xoá node cuối cùng trong danh sách liên kết, ta thực hiện các bước sau:
- Kiểm tra danh sách liên kết: nếu danh sách rỗng thì không có gì để xoá, ta kết thúc hàm.
- Nếu danh sách chỉ có một node thì ta cho
headtrỏ đếnNone. -
Ngược lại, danh sách có nhiều hơn một node, ta lại dùng kỹ thuật "hai người dắt tay nhau" để duyệt đến node cuối cùng:
- Biến
currentđi trước để tìm node cuối. - Biến
previousđi "nôi gót" theo sau.
Khi
currentđến node cuối cùng, thìpreviouschính là node liền trước node cuối. - Biến
-
Ngắt liên kết từ
previousđếncurrentbằng cách liên kếtpreviousvớiNone.
Dựa theo thuật toán trên, ta viết chương trình như sau:
1. Viết hàm remove_tail() dùng để xoá node cuối cùng trong danh sách liên kết.
2. Trong chương trình chính, ta gọi hàm remove_tail() ra thực hiện.
3. Chạy chương trình trên, kết quả như sau:
Danh sách liên kết ban đầu: [o] -> [l] -> [d] -> None
Chèn [c] vào đầu danh sách liên kết: [c] -> [o] -> [l] -> [d] -> None
Thêm [y] vào cuối danh sách liên kết: [c] -> [o] -> [l] -> [d] -> [y] -> None
Chèn [y] vào trước [d]: [c] -> [o] -> [l] -> [y] -> [d] -> [y] -> None
Xoá node đầu tiên: [o] -> [l] -> [y] -> [d] -> [y] -> None
Xoá node cuối cùng: [o] -> [l] -> [y] -> [d] -> None
Xoá một node xác định¶
Giả sử node có dữ liệu k đã tồn tại trong danh sách liên kết và không phải là node đầu tiên.
Để xoá node có dữ liệu k, ta lại áp dụng kỹ thuật "hai người dắt tay nhau" như sau:
-
Duyệt tìm
k: Chocurrentđi trước vàprevious"nối gót" theo sau. Vòng lặp dừng lại khicurrenttìm thấy nodek. -
Liên kết
previousvới node liền saucurrent.Thao tác này tạo ra một "chiếc cầu" đi xuyên qua nút.
current(là node chứa dữ liệuk) bị cô lập hoàn toàn. Python sẽ tự động thu hồi bộ nhớ của node này.
Dựa theo thuật toán trên, ta viết chương trình như sau:
1. Viết hàm remove() dùng để xoá node có dữ liệu k.
2. Trong chương trình chính, ta gọi hàm remove() ra thực hiện.
3. Chạy chương trình trên, kết quả như sau:
Danh sách liên kết ban đầu: [o] -> [l] -> [d] -> None
Chèn [c] vào đầu danh sách liên kết: [c] -> [o] -> [l] -> [d] -> None
Thêm [y] vào cuối danh sách liên kết: [c] -> [o] -> [l] -> [d] -> [y] -> None
Chèn [y] vào trước [d]: [c] -> [o] -> [l] -> [y] -> [d] -> [y] -> None
Xoá node đầu tiên: [o] -> [l] -> [y] -> [d] -> [y] -> None
Xoá node cuối cùng: [o] -> [l] -> [y] -> [d] -> None
Xoá [y] khỏi danh sách liên kết: [o] -> [l] -> [d] -> None
Về việc xoá node
Hàm remove() vừa viết cũng áp dụng cho trường hợp lý tưởng, đó là node k cần xoá đã tồn tại trong danh sách liên kết và không phải là node đầu tiên.
Khi viết đầy đủ, ta cần xét thêm các trường hợp:
- Danh sách liên kết rỗng.
- Node
kcần xoá là node đầu tiên. - Node
kcần xoá không tồn tại.
Mã nguồn¶
Code đầy đủ được đặt tại:
Sơ đồ tóm tắt¶
Some English words¶
| Vietnamese | Tiếng Anh |
|---|---|
| duyệt (danh sách liên kết) | traverse (a linked list) |