Danh sách cài đặt bởi con trỏ

Một phần của tài liệu Giáo Trình Cấu Trúc Dữ Liệu Và Thuật Toán (Trang 29 - 35)

Danh sách được cài đặt bởi con trỏ ta còn gọi là CTDL danh sách liên kết hay CTDL danh sách móc nối, gọi tắt là danh sách liên kết/danh sách móc nối. Chúng thuộc loại cấu trúc dữ liệu động. Ta đã biết với CTDL danh sách kế tiếp cài đặt bởi mảng, các ô nhớ chứa các phần tử trong danh sách là kế tiếp nhau, nằm trong một vùng nhớ liên tục, các ô nhớ này được cấp phát ngay khi dịch chương trình cho đến khi chương trình kết thúc thực hiện thì nó được giải phóng, các ô nhớ này ta còn gọi là các ô nhớ tĩnh. Có thể lập công thức tính địa chỉ và truy cập trực tiếp đến chúng nếu ta biết trước địa chỉ của một ô nhớ nào đó trong mảng. Nhược điểm chính của cách biểu diễn này là số lượng các phần tử trong danh sách bị hạn chế vì phụ thuộc vào vùng nhớ trống liên tục trong bộ nhớ. Xét một dạng biểu diễn danh sách khác, biểu diễn móc nối, các ô nhớ chứa các dữ liệu của danh sách không nhất thiết phải nằm ở những vị trí kế tiếp nhau, chúng có thể nằm rải rác khắp nơi trong bộ nhớ và chúng gắn kết với nhau thông qua cơ chế móc nối – còn gọi là cơ chế lưu địa chỉ. Danh sách được lưu trữ theo kiểu còn gọi là danh sách liên kết hay danh sách móc nối. Trong phần nay ta tập trung vào các

CTDL danh sách liên kết. Con trỏ được sử dụng để tạo liên kết giữa các ô nhớ chứa phần tử của danh sách. Các hình thức tổ chức liên kết các phần tử trong danh sách có thể là:

(1) Liên kết đơn, tương ứng ta có cấu trúc dữ liệu danh sách liên kết đơn – gọi tắt là danh sách liên kết đơn

(2) Liên kết vòng: Tương ứng ta có cấu trúc dữ liệu danh sách liên kết vòng – gọi tắt là danh sách liên kết vòng

(3) Liên kết đôi: Tương ứng ta có cấu trúc dữ liệu danh sách liên kết đôi – gọi tắt là danh sách liên kết đôi/kép

(4)Đa liên kết: Tương ứng ta có danh sách đa liên kết/ đa móc nối

a) Danh sách liên kết đơn (Single Link List)

hai ngăn, một ngăn chứa dữ liệu của phần tử đó, một ngăn là con trỏ chứa địa chỉ của ô nhớ đứng kế sau phần tử này trong danh sách, ta có thể hình dung cơ chế này qua ví dụ sau:

Giả sử 1 nhóm có 4 bạn: Đông, Tây, Nam, Bắc có địa chỉ nhà ở lần lượt là d,t,n,b. Giả sử: Đông có địa chỉ của Nam, Tây không có địa chỉ của bạn nào, Bắc giữ địa chỉ của Đông, Nam có địa chỉ của Tây, điều này được mô tả qua Hình 2.1 như sau

Hình 2.1 – Danh sách liên kết đơn chứa 4 phần tử

Như vậy, nếu ta xét thứ tự các phần tử bằng cơ chế lưu địa chỉ này thì ta có một danh sách: Bắc, Đông, Nam, Tây. Hơn nữa để có thể tru y cập đế n các phần tử trong danh sách này thì chỉ cần giữ địa chỉ của Bắc (địa chỉ của ô nhớ chứa phần tử đầu tiên trong danh sách).

Mô tả dạng biểu diễn danh sách trên máy tính

Trong cài đặt, mỗi phần tử trong danh sách được cài đặt như một nút có hai trường: Trường info chứa giá trị của các phần tử trong danh sách;

Trường link là một con trỏ giữ địa chỉ của ô kế tiếp nó trong danh sách: Mỗi nút có dạng như sau:

Hình ảnh danh sách có dạng như sau:

Nút cuối cùng trong danh sách không có nút đứng sau, nên Trường link của phần tử cuối trong danh sách, trỏ đến một giá trị đặc biệt là Nil (trỏ tới đất – không trỏ tới

INFO LINK

Là con trỏ, trỏ đến nút đứng kế tiếp nó trong danh sách chứa giá trị phần tử của nút, giả sử có kiểu dữ liệu là Item

a1 a2 an+1 nil L a3 an Bắc d Đông n Nam t Tây nil

đâu). Để truy nhập vào d/s ta phải truy nhập tuần tự đến vị trí mong muốn, xuất phát từ phần tử đầu tiên, do đó để quản lý danh sách ta chỉ cần quản lý địa chỉ ô nhớ chứa phần tử đầu tiên của danh sách, tức là cần một con trỏ trỏ đến phần tử đầu tiên này - giả sử con trỏ L. L còn gọi là con trỏ quản lý danh sách. Danh sách L rỗng khi: L=nil

Nói đến danh sách móc nối (Liên quan đến địa chỉ) ta phải nghĩ ngay đến biến trỏ, vậy biến trỏ là gì?

Dạng biểu diễn của danh sách:

typedef struct Node

{

int Data; Node* Next; }Node;

typedef Node* LinkedList;

* Cài đặt các phép toán cơ bản của danh sách liên kết đơn 1- Tạo danh sách rỗng

2- Kiểm tra một danh sách rỗng 3 -Chèn một phần tử vào danh sách:

Chèn một phần tử có giá trị x vào danh sách L tại vị trí p ta cần:

- Cấp phát một ô nhớ để lưu trữ phần tử mới này: Giả sử con trỏ Temp trỏ tới ô nhớ này

- Đổ dữ liệu cần chèn vào ô nhớ vừa cấp phát

- Nối kết lại các con trỏ để đưa ô nhớ mới này vào vị trí p. Sơ đồ nối kết được mô tả như trong Hình 2.2. Gồm:

o Di chuyển con trỏ M đến vị trí trước p

o Cho con trỏ Link của nút mới trỏ tới ô nhớ ứng với phần tử đứng sau nó (như thao tác 3)

o Xóa liên kết từ B đến C, và tạo liên kết từ phần tử thứ B đến phần tử mới ( như thao tác 4)

Hình 2.2: Thêm một phần tử vào danh sách tại vị trí p 4- Xóa phần tử ra khỏi danh sách L

Tương tự như khi thêm một phần tử vào danh sách liên kết, muốn xóa một phần tử khỏi danh sách ta cần: L A B C D E p = 3 x 3 4 Bỏ Temp M

(1) Xác định vị trí của phần tử muốn xóa trong danh sách L, giả sử ví trí thứ p, ta di chuyển con trỏ M tới vị trí trước p, con trỏ temp trỏ tới vị trí p

(2) Nối kết lại các con trỏ theo thao tác 1 như trong Hình 2.3. (3) Giải phóng vùng nhớ chứa phần tử thứ P/được trỏ bởi Temp

Hình 2.3: Xoá phần tử tại vị trí p

5 – Nhập dữ liệu cho danh sách

void Nhapds(LinkedList &L)//nhap ds ket noi vao dau ds

{

Node *N;

int x;

int n;//bien n dung de chua so phan tu cua ds

L=NULL;//khoi tao ds ban dau rong //Nhap so phan tu trong ds

printf("Nhap so phan tu cua ds:"); scanf("%d",&n);

for (int i=1;i<=n;i++) {

N=(Node*)calloc(1, sizeof(Node));// cap phat 1 o nho co kich thuoc Node

if (N!=NULL) {

printf("Nhap x="); scanf("%d",&x); N->Data=x;

//Ket noi N vao dau ds L

N->Next =L; L=N;

}

else printf("\n Cap phat o nho khong thanh cong"); }

}

6- In dữ liệu trong danh sách ra màn hình

//Chuong trinh inds ra man hinh void Inds(LinkedList L) { Node *P; P=L; while (P!=NULL) { printf("%5d",P->Data); P=P->Next ; } }

//Chuong trinh sap xep danh sach

b) Một số dạng danh sách liên kết khác L A B C D E p = 3 M 1 Temp

1- Danh sách nối vòng ( Circularly linked list)

Danh sách liên kết vòng là một cải tiến của d/s nối đơn. Mỗi phần tử trong danh sách được lưu trong một nút, trường Link của nút cuối cùng trong d/s chứa địa chỉ của nút đầu tiên của d/s.

Hình ảnh của nó như sau:

Ưu điểm của danh sách liên kết vòng giúp cho việc truy nhập vào các nút được linh hoạt hơn, vì nút nào trong danh sách cũng có thể coi là nút đầu tiên và con trỏ L trỏ tới nút nào cũng được, từ một nút trong danh sách ta có thể truy cập được đến các nút khác, tuy nhiên trong xử lý, nếu không cẩn thận sẽ dẫn đến 1 chu trình không kết thúc (Vì không biết được chỗ kết thúc d/s)

2- Danh sách nối kép (double link list)

Chúng ta nhận thấy rằng với danh sách móc nối đơn và nối vòng, chỉ có phép duyệt 1 chiều, từ phần tử trước có thể truy nhập đến phần tử đứng sau, nhưng từ phần tử đứng sau không truy cập trực tiếp đến phần tử đứng ngay trước nó được. Khắc phục hạn chế này ta có danh sách liên kết kép. Mỗi phần tử trong danh sách nối kép xem là một nút (bản ghi) gồm 3 trường:

- Info : chứa thông tin về đối tượng. - LPTR : con trỏ trỏ tới phần tử bên trái - RPTR : con trỏ trỏ tới phần tử bên phải

Quy cách một nút:

Hình ảnh danh sách móc nối kép có dạng:

Việc truy nhập các phần tử của d/s phải truy cập xuất phát từ một trong hai đầu của danh sách. Do đó, để quản lý danh sách, dùng 2 con trỏ L, R lần lượt trỏ tới nút trái nhất và phải nhất của danh sách, hai con trỏ này còn gọi là con trỏ cực trái, cực phải của danh sách. Khi đó d/s rỗng nếu: L=R= nill.

Biểu diễn danh sách liên kết vòng

Dành cho bạn đọc Một số phép toán cơ bản

1) Phép bổ sung một phần tử (vào trước phần tử trỏ bởi con trỏ M)

A B C D

L

LPTR INFO RPTR

A B C D

L R

Hình 2.4 – hình ảnh danh sách liên kết đơn vòng

Các bước để chèn thêm một phần tử mới vào danh sách liên kết kép được thực hiện tương tự như danh sách liên kết đơn, tuy nhiên khi gắn kết nút tương ứng với phần tử mới (nút được trỏ bởi con trỏ q) vào vị trí trước phần tử được trở bởi con trỏ M được thực hiện từ bước (1) đến (4) như mô tả như trong hình 2.6:

2) Phép loại bỏ 1 phần tử ra khỏi danh sách nối kép

Xét d/s nối kép, có 2 nút cực trái, cực phải là L, R. Loại bỏ nút trỏ bởi con trỏ M ra khỏi danh sách. Để loại bỏ phần tử được trỏ bởi con trỏ M ra khỏi danh sách liên kết kép L, R ta tiến hành theo các bước từ 1) đến 5) như sau:

1) Nếu danh sách rỗng, thông báo lỗi không có dữ liệu để loại bỏ 2) Việc xóa dữ liệu trong danh sách cần chia thành các trường hợp:

a. Nếu M =L: phần tử loại bỏ là phần tử đầu tiên b. Nếu M =R: phần tử loại bỏ là phần tử cuối

c. Nếu phần tử loại bỏ nằm ở vị trí p trong danh sách

3) Cuối cùng ta thực hiện thao tác giải phóng vùng nhớ chứa phần tử cần loại bỏ

Việc loại bỏ ứng với trường hợp (c) được mô tả như trong hình 2.7

2.1.3.3. So sánh hai phương pháp cài đặt danh sách bởi mảng và bởi con trỏ

Không thể kết luận phương pháp cài đặt nào hiệu quả hơn, mà nó phụ thuộc chủ yếu vào bài toán và các phép toán trên danh sách. Tuy nhiên ta có thể tổng kết một số ưu nhược điểm/ nhược điểm chung của từng phương pháp làm cơ sở để lựa chọn phương pháp biểu diễn thích hợp:

A B C D L M R E × × (1) (3) (2) (4) q

Hình 2.6 – Thêm một phần tử ở vị trí trước vị trí được trở bởi con trỏ M

A B C D

L M R

(1)

(2)

35

Cài đặt bằng mảng đòi hỏi phải xác định số phần tử của mảng, do đó nếu không thể ước lượng được số phần tử trong danh sách thì khó áp dụng cách cài đặt này một cách hiệu quả vì nếu khai báo thiếu chỗ thì mảng thường xuyên bị đầy, không thể làm việc được còn nếu khai báo quá thừa thì lãng phí bộ nhớ.

Cài đặt bằng con trỏ thích hợp cho sự biến động của danh sách, danh sách có thể rỗng hoặc lớn tuỳ ý chỉ phụ thuộc vào bộ nhớ tối đa của máy. Tuy nhiên ta phải tốn thêm vùng nhớ cho các con trỏ.

Cài đặt bằng mảng thì thời gian thêm hoặc xoá một phần tử tỉ lệ với số phần tử đi sau vị trí thêm/ xóa. Trong khi cài đặt bằng con trỏ các phép toán này mất chỉ một hằng thời gian.

Phép truy nhập vào một phần tử trong danh sách, chẳng hạn như PREVIOUS, chỉ tốn một hằng thời gian đối với cài đặt bằng mảng, trong khi đối với danh sách cài đặt bằng con trỏ ta phải tìm từ đầu danh sách cho đến vị trí trước vị trí của phần tử hiện hành. Nói chung danh sách liên kết thích hợp với danh sách có nhiều biến động, tức là ta thường xuyên thêm, xoá các phần tử.

2.2 Ngăn xếp (Stack)

Một phần của tài liệu Giáo Trình Cấu Trúc Dữ Liệu Và Thuật Toán (Trang 29 - 35)

Tải bản đầy đủ (PDF)

(76 trang)