- H ạn chế của cách cài đặt này: kích thước của stack bị giới hạn và kém linh động, do đĩ việc sử dụng bộ nhớ kém hiệu quả (thiếu hay lãng phí bộ nhớ).
III.4 Một số kiểu DSLK khác
III.4.1. DSLK đơn cĩ nút câm
Qua các thao tác cơ bản trên DSLK đơn (khơng cĩ nút câm trước đây), ta nhận thấy cĩ sựkhác biệt trong cách xứ lý giữa nút đầu (khơng cĩ nút đứng trước, ta thường qui ước PredPtr là NULL) với các nút khác (luơn cĩ nút đứng trước
PredPtr). Để đơn giản khi viết các thao tác trên (khỏi phải phân biệt hai tình huống xử lý đĩ) người ta tạo thêm một nút giả (hay nút câm, ta khơng quan tâm đến dữ liệu của nút này) đứng trước nút dữ liệu đầu tiên của DSLK đơn thơng thường và gọi nĩ là DSLK (đơn) cĩ nút câm.
DList.Head Nút câm Nút dữ liệu đầu DList.Tail
? x y … z •
Khi đĩ, các thao tác cơ bản trên DSLK cĩ nút câm, sẽđược viết lại, trong một số trường hợp (chẳng hạn chèn, xĩa) sẽđơn giản hơn .
Cấp phát vùng nhớ cho một nút (khơng quan tâm đến dữ liệu) NodePointer CreateNode ()
{ NodePointer new_ele;
if ((new_ele = new NodeType) ==NULL)
cout << “\nLỗi cấp phát vùng nhớ cho một nút mới !”; else new_ele ->Next = NULL;
return new_ele; } • Khởi tạo một DSLK cĩ nút câm rỗng LL CreateEmptyLL2 () { LL List; List.Head = CreateNode(); List.Tail = List.Head; return List; }
• Kiểm tra một DSLK với nút câm cĩ rỗng hay khơng
int EmptyLL2(LL List)
{
return(List.Head->Next == NULL);
}
• Duyệt qua một DSLK cĩ nút câm
int TraverseLL2(LL List)
{ NodePointer CurrPtr = List.Head->Next; if (EmptyLL2(List)) return 0;
else { while (CurrPtr) { XửLý (CurrPtr); CurrPtr = CurrPtr->Next; } return 1; } }
• Thêm một phần tử x vào sau một nút được trỏ bởi con trỏ PredPtr * Thêm một nút mới vào sau một nút được trỏ bởi con trỏ PredPtr
List.Head List.Tail
? … •
2 1
PredPtr x
new_ele
void InsertNodeAfterLL2(LL &List, NodePointer new_ele, NodePointer PredPtr)
{ new_ele->Next = PredPtr->next; PredPtr->Next = new_ele;
if (PredPtr == List.Tail) List.Tail = new_ele; return ;
}
* Thêm một phần tử x vào sau một nút được trỏ bởi con trỏ PredPtr int InsertElementAfterLL2(LL &List, ElementType x, NodePointer PredPtr)
{ NodePointer new_ele;
if ((new_ele = CreateNodeLL(x)) == NULL) return 0; InsertNodeAfterLL2(List, new_ele, PredPtr);
return 1;
}
Thêm một phần tử x vào đầu DSLK cĩ nút câm
int InsertElementHeadLL2(LL &List, ElementType x)
{ return InsertElementAfterLL2(List, x, List.Head);
Thêm một phần tử x vào cuối DSLK cĩ nút câm
int InsertElementTailLL2(LL &List, ElementType x)
{ return InsertElementAfterLL2(List, x, List.Tail);
}
• Tìm kiếm một phần tử trên DSLK đơn cĩ nút câm
Tìm một phần tử x trong DSLK List. Nếu tìm thấy thì, thơng qua đối cuối của hàm, trả vềđịa chỉ PredPtr của nút đứng trước nút tìm thấy đầu tiên. Đểtăng tốc độ tìm kiếm (bằng cách giảm số lần so sánh trong biểu thức điều kiện của vịng lặp), ta đặt thêm lính canh ở cuối List.
List.Head List.Tail new_ele (lính canh)
? • x •
PredPtr CurrP …
- Thuật tốn tìm kiếm tuyến tính (cĩ lính canh) trên dãy chưa được sắp:
Boolean SearchLinearLL2(List, x, &PredPtr)
. Chèn nút mới new_ele chứa x vào cuối List (đĩng vai trị lính canh) . PredPtr = List.Head;
CurrPtr = List.Head->Next; // PredPtr đứng kề trước CurrPtr . Trong khi (CurrPtr->Data ≠ x) thực hiện
{ PredPtr = CurrPtr; CurrPtr = CurrPtr->Next; }
. if (CurrPtr ≠ new_ele) Thấy = True; // Thơng báo thấy x;
else Thấy = False; // Thơng báo khơng thấy x; . Xĩa nút (new_ele) đứng sau nút được trỏ bởi List.Tail;
. Trả về trị Thấy;
- Cài đặt
int SearchLinearLL2(LL List, ElementType x, NodePointer &PredPtr)
{ NodePointer CurrPtr = List.Head->Next, OldTail = List.Tail, new_ele = InsertElementTailLL2(List, x);
PredPtr = List.Head; int Thấy;
while (SoSánh(CurrPtr->Data, x) != 0)
{ PredPtr = CurrPtr ; CurrPtr = CurrPtr->Next; }
else Thấy = 0; // thấy giả hay khơng thấy !
RemoveAfterLL2(List, OldTail, x); // xĩanút new_ele; return Thấy;
}
• Xĩa một nút sau một nút được trỏ bởi con trỏ PredPtr
int RemoveAfterLL2(LL &List, NodePointer PredPtr, ElementType &x)
{ NodePointer Temp; if (EmptyLL2(List))
{ cout << “\nDS rỗng !”; return 0;
}
Temp = PredPtr->Next;
if (Temp == NULL) return 0; // khơng xĩa được nút sau nút cuối ?! else PredPtr->Next = Temp->Next;
if (Temp == List.Tail) List.Tail = PredPtr; //nếu xĩa đuơi, cần cập nhật lại đuơi
Gán(x, Temp->Data); delete Temp;
return 1; // xĩa thành cơng
}
Việc viết lại các thao tác cơ bản cịn lại trên DSLK đơn cĩ nút câm được xem như bài tập. Qua đĩ, ta thấy rõ mối liên quan mật thiết giữa cấu trúc dữ liệu và thuật tốn, được thể hiện qua “cơng thức” của Niklaus Wirth: