Danh sách liên kết¶
Tóm lược nội dung
Bài này trình bày những khái niệm về danh sách liên kết.
Đặt vấn đề¶
Khi lưu trữ và xử lý tập hợp gồm nhiều phần tử, mảng là một lựa chọn phù hợp. Song mảng vẫn có những hạn chế như:
-
Kích thước của mảng là cố định. Nói cách khác, số lượng phần tử của mảng không thể tăng hoặc giảm một cách linh hoạt. Số lượng phần tử của mảng thường phải khai báo dư so với số lượng sử dụng thực tế.
-
Khi cần chèn hoặc xoá phần tử, mảng cần phải dịch chuyển các phần tử, dẫn đến hao phí về mặt thời gian và bộ nhớ. Thậm chí, có trường hợp phải khai báo mảng mới, rồi sao chép các phần tử từ mảng gốc sang mảng mới, hiệu quả thực thi lại càng kém hơn.
Do đó, ta cần một cấu trúc dữ liệu khác có thể khắc phục những hạn chế này.
Khái niệm¶
Danh sách liên kết là một cấu trúc dữ liệu gồm các phần tử kết nối với nhau. Mỗi phần tử được gọi là một node, thường được tổ chức thành hai thành phần:
- Phần thứ nhất chứa dữ liệu, tạm gọi là
data
. - Phần thứ hai chứa địa chỉ của một node khác, tạm gọi là
next
. (1)
- Khi
next
của node liền trước chứa địa chỉ của node liền sau, ta nói rằng, node trước là trỏ vào / trỏ tới / trỏ đến node liền sau.
Bên cạnh đó, danh sách liên kết còn có một biến được dùng để nắm giữ node đầu tiên, tạm gọi là head
. Biến head
đóng vai trò là điểm khởi đầu hoặc đầu vào của danh sách liên kết. Dựa vào head
, ta có thể duyệt qua các node trong danh sách liên kết.
Node cuối cùng có thành phần next
trỏ đến None
, là đối tượng mang ý nghĩa rỗng, không có giá trị. None
là dấu hiệu giúp nhận biết không còn node tiếp theo nữa.
Xe lửa có thể xem là hình ảnh minh họa cho danh sách liên kết: xe lửa gồm nhiều toa, toa liền trước móc nối với toa liền sau.
Danh sách liên kết được sử dụng hiệu quả trong những trường hợp như:
- Thao tác chèn thêm hoặc xoá bớt thường xuyên diễn ra.
- Quản lý bộ nhớ động, tức bộ nhớ thay đổi trong khi chạy chương trình, chứ không cố định từ đầu.
Có nhiều loại danh sách liên kết, ví dụ như: danh sách liên kết đơn, danh sách liên kết đôi, danh sách liên kết vòng, v.v...
Bài học này chỉ đề cập danh sách liên kết đơn, và từ đây gọi tắt là danh sách liên kết.
Một vài thao tác xử lý¶
Ngoài thành phần data
, mỗi node còn có thành phần next
trỏ đến node liền sau nó, giúp các node liên kết với nhau. Các thao tác thêm node và xoá node thực chất đều là thay đổi thành phần next
này, nghĩa là thay đổi liên kết giữa các node.
Về mã lệnh trong bài này
Việc viết mã lệnh để minh họa danh sách liên kết trong bài này đòi hỏi hiểu biết về lập trình hướng đối tượng và kỹ thuật lập trình, là kiến thức 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ã lệnh thành chương trình hoàn chỉnh để chạy mà không cần quá quan tâm chi tiết kỹ thuật.
Khởi tạo¶
Để khởi tạo một danh sách liên kết gồm ba node, ta thực hiện theo những bước sau. Giả sử, mỗi node chỉ chứa dữ liệu là một ký tự.
Bước 1:
Tạo kiểu dữ liệu node
gồm hai thành phần data
và next
.
Bước 2:
Tạo kiểu dữ liệu danh sách liên kết, đặt tên là linked_list
. Kiểu dữ liệu linked_list
này có thuộc tính head
dùng để nắm giữ node đầu tiên trong danh sách liên kết.
- Lệnh này chỉ là khai báo mang tính thủ tục, thuộc tính
head
tạm thời trỏ đếnNone
.
Bước 3:
Trong chương trình chính, ta khai báo biến danh sách liên kết, đặt tên là L
. Lúc này, danh sách liên kết L
là rỗng, chưa có node nào, do thuộc tính head
của L
đang trỏ đến None
như khai báo tại bước 2.
Bước 4:
Khởi tạo 3 node, lần lượt đặt tên là first
, second
và third
, chứa dữ liệu lần lượt là 'o'
, 'l'
và 'd'
. Lúc này, cả ba node đều đơn lẻ, rời rạc, chưa có kết nối với nhau.
Bước 5:
Liên kết các node với nhau bằng cách cho thuộc tính head
của L
trỏ đến một node nào đó, rồi cho node này trỏ đến một node khác, và cứ như thế đối với các node còn lại.
-
Bước 5.1: Cho
head
củaL
trỏ đếnfirst
. -
Bước 5.2: Cho
next
củafirst
trỏ đếnsecond
. -
Bước 5.3: Cho
next
củasecond
trỏ đếnthird
.
Duyệt¶
Duyệt danh sách liên kết là tiến trình di chuyển lần lượt qua các node, xuất phát từ node đầu tiên, do biến head
trỏ đến, cho đến node cuối cùng, là node có thành phần next
trỏ đến None
.
Cụ thể, ta sử dụng biến current
, xuất phát từ head
, rồi dùng vòng lặp cho current
lần lượt trỏ đến các node tiếp theo, cho đến khi gặp None
.
Hàm sau đây thực hiện duyệt danh sách liên kết và in ra màn hình dữ liệu của từng node.
Trong chương trình chính, ta gọi hàm vừa viết trên.
Output:
Chèn vào trước một node có dữ liệu key¶
Giả sử node có dữ liệu key
đã 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 key
, ta thực hiện các bước sau:
Bước 0:
Tạo node mới, đặt tên là new_node
, chứa dữ liệu new_data
.
Bước 1:
Duyệt danh sách liên kết bằng hai biến previous
và current
cho đến khi tìm thấy node key
:
- Bước 1.1: Cho
current
trỏ đến node đầu tiên. -
Bước 1.2: Dùng vòng lặp để tìm node có dữ liệu
key
:- Nếu tìm thấy
key
thì ngắt vòng lặp. Chuyển sang bước 2. - Ngược lại, chưa tìm thấy, thì cho
previous
thay thếcurrent
và chocurrent
di chuyển đến node tiếp theo.
- Nếu tìm thấy
Bước 1.2 mang ý nghĩa rằng, biến previous
luôn bước nối gót theo biến current
.
Bước 2:
Cho next
của node mới trỏ đến current
.
Bước 3:
Cho next
của previous
trỏ đến node mới.
Hàm chèn thêm node mới vào trước node có dữ liệu key
viết như sau:
Trong chương trình chính, ta gọi hàm vừa viết trên.
Output:
Lưu ý
Như đã nói trên, hàm này áp dụng cho trường hợp lý tưởng, đó là node key
có 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, chưa có node nào.
- Node
key
là node đầu tiên. - Node
key
không tồn tại.
Xoá node có dữ liệu key¶
Giả sử node có dữ liệu key
đã 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 key
, ta thực hiện các bước sau:
Bước 1:
Duyệt danh sách liên kết bằng hai biến previous
và current
cho đến khi tìm thấy node key
:
- Bước 1.1: Cho
current
trỏ đến node đầu tiên. -
Bước 1.2: Dùng vòng lặp để tìm node có dữ liệu
key
:- Nếu tìm thấy
key
thì ngắt vòng lặp. Chuyển sang bước 2. - Ngược lại, chưa tìm thấy, thì cho
previous
thay thếcurrent
và chocurrent
di chuyển đến node tiếp theo.
Tương tự thao tác chèn, tại bước 1.2 này, ta cũng cho
previous
di chuyển nối gótcurrent
. - Nếu tìm thấy
Bước 2:
Ngắt liên kết từ previous
đến current
, là node chứa key
cần xoá, bằng cách cho next
của previous
trỏ đến node liền sau current
.
Bước 3:
Thủ tiêu current
.
Hàm xoá một node có dữ liệu key
viết như sau:
Trong chương trình chính, ta gọi hàm vừa viết trên.
Output:
Lưu ý
Hàm này cũng áp dụng cho trường hợp lý tưởng, đó là node key
cần xoá có 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, chưa có node nào.
- Node
key
là node đầu tiên. - Node
key
không tồn tại.
Google Colab¶
Các đoạn mã trong bài này được đặt tại Google Colab để bạn có thể thử nghiệm theo cách của riêng mình.
Some English words¶
Vietnamese | Tiếng Anh |
---|---|
danh sách liên kết đôi | doubly linked list |
danh sách liên kết đơn | singly linked list |
danh sách liên kết vòng | circular linked list |