chức theo kiểu kế tiếp hoặc móc nối) hoặc có thể có nhiêu hơn một kiểu (với danh sách được tổ chức theo kiểu móc nối). Điều đó có nghĩa là danh sách có thể lưu trữ
giá trị cho các loại đối tượng giống hoặc khác nhau.
- Mỗi phần tử của đanh sách được đặc trưng bởi các tham số là: giá trí, phần từ trước nó, phần tử sau nó (ngoại trừ hai phần tử đặc biệt là phần tứ đâu danh sách - là phần tử không có phần tử nào đứng trước và phần tử cuối danh sách - là phân tử không có phần tử nào đứng sau). Việc truy nhập đến một phần tử ¿ trong
danh sách được thực hiện một cách gián tiếp thông qua việc duyệt tất cả (ï-1) phần tử đứng trước nó (khác với mảng), bắt đầu từ phần tử đầu của đanh sách. Do đó,
thời gian truy nhập đến các phần tử trong danh sách là chậm hơn so với mảng và không đồng đêu giữa các phần tử trong danh sách.
2. Cấp phái bộ nhớ động
Khi làm việc với danh sách, thông thường ta cần quản lí bộ nhớ một cách khá mềm dẻo đáp ứng nhu cầu biến động không ngừng của dữ liệu rong lúc chạy
chương trình, Một cơ chế quản lí bộ nhớ linh hoạt như vậy được gọi là cấp phát bộ
nhớ động. Các hàm dùng để cấp phát bộ nhớ động được khai báo trong thư viện ailoc.h bao gồm:
void *ealloc(unsigned n, unsigned size), Dùng để cấp phát vùng nhớ cho n đối tượng có kích thước size bytes. Nếu thành công hàm trả về địa chỉ đầu vùng nhớ được cấp, ngược lại hàm trả về giá trị NULL.
void* malloc(unsigned n); Dùng để cấp phát một vùng nhớ œ byte. Nếu
thành công hàm trả về địa chỉ đầu vùng nhớ được cấp, ngược lại trả về giá trị
NULL.
void free(void *ptr), Dùng để giải phóng vùng nhớ đã cấp bằng cấp phát động do con trỏ ør trỏ đến.
void* realloc(void *ptr, unsigned size), Dùng để thay đổi kích thước vùng nhớ đã được cấp phát động do con trỏ pr trỏ đến với kích thước mới là size bytes.
Các dữ liệu trên vùng nhớ cũ sẽ được chuyển tới vùng nhớ mới. Khi thành công
hàm trả về địa chỉ của vùng nhớ mới, ngược lại hàm trả về giá trị VULL.
Chú ý:
Vì các hàm cấp phát bộ nhớ động đều làm việc với các con trỏ không kiểu,
cho nên khi cấp phát cho biến kiểu nào ta cần ép về kiểu của biến đó.
Ví dụ 3-27. Đoạn chương trình sau sẽ cấp phát ra một vùng nhớ cho 10 số
nguyên, sau đó gần các giá trị từ 2 đến 9 cho các số nguyên đó. int*Pi,b
Pi= (int") malloe(10*sizeof(nt), /* Pi trỏ đến đầu vùng nhớ được cấp ”/ if(!Pï) /" nếu hết bộ nhớ */
puts(*nKhong du bo nho ”),
exi(1); “ Làm cho chương trình kết thúc tức thì một cách bình
thường */
}
for(i=0; ì<10; ++i}
PÍ[†E Ì; /' gán các giá trị từ 0 đến 9 cho các số nguyên vừa cấp */
3. Danh sách tuyến tính (Linear lisD)
Một danh sách mà các phần tử của nó được lưu trữ kế tiếp nhau trong bộ nhớ thì gọi là danh sách tuyến tính (linear li30). Véctơ chính là trường hợp đặc biệt của danh sách tuyến tính tại một thời điểm xác định. -
Như vậy, danh sách tuyến tính có thể coi là một bộ có thứ tự và luôn biến động
các phần tử (a,, a,„..., a,) cùng kiểu nào đó. Tệp (i2) là một ví dụ điển hình về danh sách tuyến tính có kích thước lớn được lưu trữ ở bộ nhớ ngoài. Hình ảnh của danh sách tuyến tính trong bộ nhớ tại các thời điểm khác nhau có thể được mô tả như sau:
+ mi m | |] |5 T8. | đi ị^ T _- I= ]^ T5 |= TA L-
Hình 3.7. Hình ảnh của đanh sách tuyến tính trong bộ nhớ tại các thời điểm.
Do số lượng các phần tử của danh sách tuyến tính luôn biến động trong bộ
nhớ, cho nên ta phải có một cơ chế nào đó để đánh dấu phần tử đầu tiên, phần tử cuối cùng của danh sách, cũng như phải nhận biết được trường hợp đanh sách rỗng, nếu không ta sẽ không thể quản lí được danh sách. Có nhiều cách thức khác nhau
để giải quyết cho vấn đề này. Một trong những cách hay được sử dụng đó là dùng
hai biến trỏ để chứa địa chỉ của phần tử đâu và phần tử cuối của đanh sách (con trỏ
Dau và con trỏ Cươi trong hình về), mọi thao tác trên đanh sách đều được thực
hiện thông qua hai biến trỏ này. Thao tác duyệt toàn bộ đanh sách sẽ phải được tiến hành thông qua một con trỏ khác, con trỏ này sẽ đi chuyển từ đầu đến cuối đanh sách. Thao tác bổ sung một phân tử vào danh sách sẽ được tiến hành bằng cách dãn các phân từ để lấy chỗ chèn. Ngược lại, phép loại bộ một phần tử ra khỏi danh sách
sẽ được tiến hành bằng cách dồn các phản tử lại để lấp đầy chỗ trống... Khi biến
trô Dau có giá trị bằng biến trỏ Cươi thì danh sách đã cho là rỗng.
Danh sách tuyến tính thường được dùng để cài đặt cho hai kiểu cấu trúc dữ liệu đặc biệt đó là ngăn xếp (sizck) và hàng đợi (juewe), bai cấu trúc này sẽ được
xem xét kĩ hơn trong mục 3.3.5 và 3.3.6. 4. Danh sách móc nối (Linked lis) a) Khái niệm
Danh sách móc nối là một loại danh sách mà các phân tử của nó (còn gọi là
mực tin hay nút) được hạt trữ rải rác khắp nơi trong bộ nhớ, được nối kết với nhau
theo một thứ tự nhất định nhờ vào các vàng dữ liệu đặc biệt gọi là vững liên kết.
Hình 3.8. Hình ảnh cửa danh sách móc nối trong bộ nhớ,
Mỗi một phản tử của đanh sách móc nối sẽ là một biến kiếu Cấu đrúc tự trổ
hoặc kiểu Cấu trúc có chứa một thành phần dữ liệu là con trỏ trỏ tới một cấu trúc khác. Như vậy, mặc dù mỗi nút của danh sách được lưu trữ nằm rải rác ở bất kì đâu
trong bộ nhớ, nhưng ta vẫn có thể truy nhập được nó thông qua địa chỉ được lưu trữ
trong thành phần con trỏ của nút đứng ngay trước nó. Điều đó có nghĩa là, để truy
nhập đến phần tử thứ ¡ nào đó của danh sách móc nối ta cần phải biết địa chỉ của phần tử ¡-ƒ đứng ngay trước nó, để truy nhập được đến phần tử /-/ này ta cần phải biết địa chỉ của phần tử ¿-2 đứng ngay trước phần tử ¿-7... cứ như vậy, ta thấy rằng để truy nhập đến một phân tử ¿ bất kỉ nào đó của danh sách móc nối thì bao giờ ta
cũng phải biết địa chỉ của sứ: đâu tiên trong danh sách. Hay nói cách khác, ta chỉ
có thể truy nhập đến một phần tử nào đồ trong danh sách một cách gián tiếp thông qua các phần tử đứng trước theo một chiều nhát định bắt đâu từ nút đầu tiên. Do đó tốn thời gian truy nhập đến các phần tử trong danh sách, và thời gian
này là không đồng đếu giữa các nút khác nhau (càng xa nút dâu tiên thì càng tốn
thời gian hơn). Danh sách loại nầy còn được gọi với một tên khác là danh sách nói
đơn (Singly linked lisp),
Việc quản lí danh sách móc nối thực chất quy về việc quản 1í địa chỉ của nút đầu tiên thông qua con trỏ Đau, con trỏ này phải không được thay đổi trong quá trình hoạt động của danh sách vì nó là đầu mối duy nhất để có thể truy nhập danh sách. Khi con trô Đau bằng ULL ta có một danh sách rỗng (chưa có phần tử nào). Để đánh dấu sự kết thúc của danh sách thì thành phần con trỏ của nút cuối cùng phải bằng VULLL, (chưa trổ đến nút nào).
b) Các thao tác trên dạnh sách múc nốt
,__ Các thao tác chủ yếu trên cấu trúc đữ liệu kiểu danh sách nối đơn là các phép
bổ sung một phân tử vào danh sách, loại bỏ một phần tử ra khỏi danh sách, duyệt danh sách, ghép hai danh sách... Đề bổ sung một phần tử vào danh sách ta làm như sau: Giả sử rằng danh sách được quản lí bởi con trẻ đầu 1, nút Xcần bổ sung sẽ do con trỏ Ø trỏ tới, vị trí cần bổ sung trong danh sách #ƒ do con trỏ $ trỏ tới, khi đó ta cần xét các trường hợp sau đây:
* Trường hợp danh sách rỗng: