Professional Documents
Culture Documents
Đối với vài vấn đề chúng tôi đưa ra nhiều giải pháp, như là vòng lặp và đệ quy, dummy node và tham chiếu cục
bộ. Những vấn đề được trình bày theo thứ tự từ dễ tới khó: Count, GetNth, DeleteList, Pop, InsertNth, SortedInsert,
InsertSort, Append, FrontBackSplit, RemoveDuplicates, MoveNode, AlternatingSplit, ShuffleMerge, SortedMerge,
SortedIntersect, Reverse, and RecursiveReverse.
Nội dung:
1
More information: www.itspiritclub.net Translater: huahongquan2007
Tất cả các mã nguồn về danh sách liên kết trong tài liệu này dùng cấu trúc danh sách liên kết đơn thuần túy: chỉ
dùng một con trỏ head để trỏ tới node đầu tiên của danh sách. Mỗi node chứa một con trỏ .next để trỏ tới node tiếp theo.
Con trỏ .next cuối cùng là NULL. Danh sách rỗng thì biểu diễn bằng một con trỏ head NULL. Tất cả các node thì được
cấp phát ở trên heap.
struct node {
int data;
};
Ở vài chỗ, chúng ta thừa nhận sự có mặt của các hàm tiện ích sau:
Đoạn mã nguồn dưới đây mô tả cách sử dụng hàm tiện ích cơ bản.
void BasicsCaller() {
struct node* head;
int len;
head = BuildOneTwoThree(); // Start with {1, 2, 3}
Push(&head, 13); // Push 13 on the front, yielding {13, 1, 2, 3}
// (The '&' is because head is passed
// as a reference pointer.)
Push(&(head->next), 42); // Push 42 into the second position
// yielding {13, 42, 1, 2, 3}
// Demonstrates a use of '&' on
// the .next field of a node.
// (See technique #2 below.)
len = Length(head); // Computes that the length is 5.
}
2
More information: www.itspiritclub.net Translater: huahongquan2007
# Các phần còn lại của phần 1 giống hoàn toàn tài liệu trước
3
More information: www.itspiritclub.net Translater: huahongquan2007
Để các vấn đề này giúp ích được cho bạn, bạn cần phải cố gắng suy nghĩ về chúng. Dù bạn có giải được vấn đề
hay không, bạn sẽ vẫn có suy nghĩ về đúng vấn đề và giải pháp đưa ra sẽ làm rõ vấn đề hơn.
Những lập trình viên tốt có thể hình dung ra cấu trúc dữ liệu để thấy mã nguồn và bộ nhớ sẽ tương tác như thế
nào. Hãy dùng những vấn đề để phát triển kĩ năng tưởng tượng của bạn. Hãy vẽ những hình mô phỏng quá trình hoạt động
của mã nguồn. Dùng các hình vẽ để chuẩn bị cho việc hình dung ra giải pháp.
Viết một hàm Count() để đếm số lần xuất hiện của một biến int được đưa ra trong sách.
void CountTest() {
List myList = BuildOneTwoThree(); // build {1, 2, 3}
int count = Count(myList, 2); // returns 1 since there's 1 '2' in the list
}
/*
Given a list and an int, return the number of times that int occurs
in the list.
*/
int Count(struct node* head, int searchFor) {
// Your code
Viết một hàm GetNth() để nhận một danh sách liên kết và một chỉ số index nguyên và trả về giá
trị dữ liệu lưu trong node ở vị trí chỉ số. GetNth() dùng quy ước đánh số trong C : node đầu có index 0,
thứ hai là index 1….Ví dụ với danh sách {42, 13, 666} GetNth() với index 1 sẽ trả về 13. Index sẽ có
giá trị trong khoảng range [0..length-1]. Nếu nó không phải vậy, GetNth() sẽ thất bại.
void GetNthTest() {
struct node* myList = BuildOneTwoThree(); // build {1, 2, 3}
int lastNode = GetNth(myList, 2); // returns the value 3
}
Về cơ bản, GetNth() thì tương tự như array[i] . Tuy nhiên, GetNth() thì xử lý chậm hơn cú pháp [ ] của mảng.
Thuận lợi của danh sách liên kết là nó quản lý bộ nhớ linh động hơn – chúng ta có thể Push() bất cứ lúc nào để thêm một
phần tử khi cần.
4
More information: www.itspiritclub.net Translater: huahongquan2007
Viết một hàm DeleteList() để nhận một danh sách và giải phóng tất cả bộ nhớ của nó và đặt con trỏ head về
NULL.
void DeleteListTest() {
struct node* myList = BuildOneTwoThree(); // build {1, 2, 3}
DeleteList(&myList); // deletes the three nodes and sets myList to NULL
}
Hình vẽ sau đây sẽ mô phỏng trạng thái của bộ nhớ sau khi DeleteList() hoạt động trong ví dụ trên. Con trỏ được
ghi đè sẽ hiện lên trong màu xám và bộ nhớ được giải phóng có một dấu X trên nó. Về cơ bản DeleteList() cần gọi hàm
free() mỗi lần cho mỗi node và đặt con trỏ head về NULL.
DeleteList() cần sử dụng đối số tham chiếu giống như Push() để nó có thể thay đổi bộ nhớ của hàm gọi. Hàm cũng cần
phải cẩn thận không truy cập vào trường .next của một node sau khi node bị giải phóng.
// Your code
5
More information: www.itspiritclub.net Translater: huahongquan2007
Viết một hàm Pop() mà nó ngược lại với Push(). Pop() nhận một danh sách không rỗng và xóa node head và trả về
dữ liệu node head. Nếu bạn đã từng sử dụng Push() và Pop(), thì danh sách của bạn sẽ giống như một STACK. Tuy nhiên,
chúng ta cung cấp nhiều hàm hỗ trợ như GetNth() nên danh sách chúng ta sẽ giống một danh sách liên kết đơn hơn chỉ là
một stack. Pop() sẽ thất bại nếu như không có node để Pop. Dưới đây là vài ví dụ:
void PopTest() {
struct node* head = BuildOneTwoThree(); // build {1, 2, 3}
int a = Pop(&head); // deletes "1" node and returns 1
int b = Pop(&head); // deletes "2" node and returns 2
int c = Pop(&head); // deletes "3" node and returns 3
int len = Length(head); // the list is now empty, so len == 0
}
Pop()
/*
The opposite of Push(). Takes a non-empty list
and removes the front node, and returns the data
which was in that node.
*/
int Pop(struct node** headRef) {
// your code...
6
More information: www.itspiritclub.net Translater: huahongquan2007
Một vấn đề khó hơn là viết một hàm InsertNth() mà nó chèn thêm một node mới vào một vị trí thứ index trong
danh sách. Push() thì tương tự nhưng chỉ có thể chèn một node ở đầu trong danh sách (index 0). Index phải ở trong
khoảng [0 …length], và node mới sẽ được chèn ở vị trí index.
void InsertNthTest() {
struct node* head = NULL; // start with the empty list
InsertNth(&head, 0, 13); // build {13)
InsertNth(&head, 1, 42); // build {13, 42}
InsertNth(&head, 1, 5); // build {13, 5, 42}
DeleteList(&head); // clean up after ourselves
}
InsertNth() thì khá phức tạp – có lẽ bạn sẽ cần vẽ hình mô phỏng để suy nghĩ về hướng giải và kiểm tra độ chính
xác.
/*
A more general version of Push().
Given a list, an index 'n' in the range 0..length,
and a data element, add a new node to the list so
that it has the given index.
*/
void InsertNth(struct node** headRef, int index, int data) {
// your code...
Viết một hàm SortedInsert() mà nhận ra một danh sách đã được sắp tăng và một node đơn lẻ, sau đó chèn node
vào đúng vị trí được sắp xếp trong danh sách. Trong khi hàm Push() cấp phát một node mới và thêm vào danh sách,
SortedInsert() nhận một node có sẵn và chỉ thay đổi con trỏ để nó chèn vào trong danh sách. Có nhiều giải pháp cho vấn
đề này.
Viết một hàm InsertSort() mà nhận một danh sách, và sắp xếp các node của nó để chúng được sắp xếp theo thứ tự
tăng dần. Nó sẽ dùng SortedInsert().
7
More information: www.itspiritclub.net Translater: huahongquan2007
Viết một hàm Append() mà nhận hai danh sách , “a” và “b” , nối “b” vào cuối của “a” ,và khi đó đặt “b” thành
NULL. Dưới đây là hình vẽ mô phỏng Append(a, b) với trạng thái ban đầu là màu xám và trạng thái cuối cùng là màu
đen. Ở cuối của lần gọi, danh sách sẽ là {1, 2, 3, 4} và “b” thì rỗng
Rõ ràng là cả hai con trỏ đầu được truyền vào trong Append(a, b) cần phải là đối số tham chiếu vì chúng đều phải
bị thay đổi. Đối số thứ hai “b” thì luôn phải được gán về NULL. Vậy khi nào thì “a” thay đổi ? Trường hợp đó xảy ra khi
danh sách “a” lúc bắt đầu là một danh sách rỗng. Trong trường hợp đó, con trỏ head của “a” cần phải chuyển từ NULL
sang trỏ tới danh sách “b”.
// Append 'b' onto the end of 'a', and then set 'b' to NULL.
void Append(struct node** aRef, struct node** bRef) {
// Your code...
8
More information: www.itspiritclub.net Translater: huahongquan2007
Hàm FrontBackSplit() sẽ nhận một hàm danh sách và sau đó cắt danh sách thành hai danh sách nhỏ - một danh
sách ở trước và một danh sách ở sau. Nếu số lượng các phần tử thì lạ, phần tử thừa sẽ đi vào danh sách trước. Vậy
FrontBackSplit() đối với danh sách {2, 3, 5, 7, 11} sẽ chia thành hai danh sách nhỏ {2, 3, 5} và {7, 11}. Để làm đúng tất
cả các trường hợp thì khá là khó. Bạn cần phải kiểm tra giải pháp của bạn đối với các trường hợp (length = 2, length = 3,
length=4) để chắc chắn rằng danh sách được cắt đúng như điều kiện đề bài. Nếu nó hoạt động đúng cho length = 4, nó sẽ
hoạt động cho length = 1000. Bạn cũng cần phải xử lý trường hợp đặc biệt là length < 2.
Gợi ý: Chiến lược dễ nhất là tính toán chiều dài của danh sách, sau đó dùng một vòng lặp để nhảy qua các node để tìm
node cuối cùng của nửa danh sách trước và cắt danh sách đúng điểm đó. Có một kĩ thuật là sử dụng hai con trỏ để lướt
qua danh sách. Một con trỏ “chậm” tiến tới một node mỗi lần, trong khi con trỏ “nhanh” thì tới hai node mỗi lần. Khi mà
con trỏ nhanh đến cuối danh sách, con trỏ “chậm” sẽ ở giữa quãng đường. Đối với các chiến lược khác, điều cần quan tâm
là chia danh sách đúng vị trí.
/*
Split the nodes of the given list into front and back halves,
and return the two lists using the reference parameters.
If the length is odd, the extra node should go in the front list.
*/
void FrontBackSplit(struct node* source,
struct node** frontRef, struct node** backRef) {
// Your code...
Viết một hàm RemoveDuplicates() để nhận một danh sách, sắp xếp chúng theo thứ tự tăng dần và xóa những node
trùng nhau trong danh sách. Để cho lý tưởng, danh sách cần được nghiên cứu toàn bộ một lần.
/*
Remove duplicates from a sorted list.
*/
void RemoveDuplicates(struct node* head) {
// Your code...
Đây là một biến thể của Push(). Thay vì tạo ra một node và push nó vào trong danh sách co sẵn, MoveNode() sẽ
nhận hai danh sách, bỏ đi node trước (front node) của danh sách thứ haiv à push nó vào đầu danh sách thứ nhất. Hàm
MoveNode() trở thành một hàm tiện ích rất có lợi cho nhiều vấn đề sau. Cả hai Push() và MoveNode() được thiết kế
quanh đặc trưng là các hàm trong danh sách thường hoạt động chủ yếu ở đầu danh sách. Dưới đây là vài ví dụ:
9
More information: www.itspiritclub.net Translater: huahongquan2007
void MoveNodeTest() {
struct node* a = BuildOneTwoThree(); // the list {1, 2, 3}
struct node* b = BuildOneTwoThree();
MoveNode(&a, &b);
// a == {1, 1, 2, 3}
// b == {2, 3}
}
/*
Take the node from the front of the source, and move it to
the front of the dest.
It is an error to call this with the source list empty.
*/
void MoveNode(struct node** destRef, struct node** sourceRef) {
// Your code
Viết một hàm AlternatingSplit() mà nhận một danh sách và chia các node của nó để tạo thành hai danh sách nhỏ
hơn. Danh sách nhỏ phải được làm tử các phần tử luân phiên nhau trong danh sách gốc. Nếu danh sách là {a , b, a, b, a}
thì danh sách phụ là {a, a, a} và danh sách còn lại là { b, b}. Bạn sẽ cần dùng hàm MoveNode(). Các phần tử trong danh
sách mới có thể ở thứ tự bất kì.
/*
Given the source list, split its nodes into two shorter lists.
If we number the elements 0, 1, 2, ... then all the even elements
should go in the first list, and all the odd elements in the second.
The elements in the new lists may be in any order.
*/
void AlternatingSplit(struct node* source,
struct node** aRef, struct node** bRef) {
// Your code
Nhận hai danh sách, trộn các node với nhau để tạo thành một danh sách, nhận các node luân phiên nhau giữa hai
danh sách. Vậy ShuffleMerge() với {1, 2, 3} và {7 , 13, 1} sẽ tạo ra danh sách {1, 7, 2, 13, 3, 1}. Nếu một trong hai danh
sách hết node, hàm sẽ lấy hết tất cả các node còn lại của danh sách kia. Giải pháp thì phụ thuộc trên khả năng di chuyển
các node tới cuối của một danh sách như đã bàn ở phần 1. Bạn có thể sử dụng hàm MoveNode(). Nếu sử dụng hàm
FrontBackSplit(), bạn có thể giả lập quá trình xáo bài.
/*
Merge the nodes of the two lists into a single list taking a node
alternately from each list, and return the new list.
*/
struct node* ShuffleMerge(struct node* a, struct node* b) {
// Your code
10
More information: www.itspiritclub.net Translater: huahongquan2007
Viết một hàm SortedMerge() để nhận hai danh sách, mỗi cái thì được sắp xếp tăng dần, và trộn hai cái lại với
nhau thành một mảng tăng dần. Có nhiều trường sẽ xảy ra: có thể “a” hoặc “b” có thể trống, trong quá trình xử lý có thể
“a” hoặc “b” hết trước và cuối cùng thì có vấn đề từ việc bắt đầu từ một danh sách rỗng và xây dựng nó trong khi đi qua
“a” và “b”.
/*
Takes two lists sorted in increasing order, and
splices their nodes together to make one big
sorted list which is returned.
*/
struct node* SortedMerge(struct node* a, struct node* b) {
// your code...
(Vấn đề này yêu cầu đệ quy) Nhận một FrontBackSplit() và SortedMerge(), nó thì khá dễ để viết một hàm
MergeSort() đệ quy: cắt danh sách làm hai phần nhỏ hơn, sắp xếp theo đệ quy hai danh sách và cuối cùng nối các danh
sách được sắp xếp lại với nhau thành một danh sách đơn. Trớ trêu thay, vấn đề này thì dễ hơn là FrontBackSplit hay
SortedMerge.
Nhận hai danh sách đã sắp xếp tăng, tạo và trả về một danh sách mới đại diện cho chỗ giao nhau của hai danh
sách. Danh sách mới nên có bộ nhớ của chính nó – các danh sách gốc thì không nên bị thay đổi. Nói cách khác, nên sử
dụng Push() để xây dựng danh sách chứ không phải MoveNode().
/*
Compute a new sorted list that represents the intersection
of the two given sorted lists.
*/
struct node* SortedIntersect(struct node* a, struct node* b) {
// Your code
11
More information: www.itspiritclub.net Translater: huahongquan2007
Viết một hàm Reverse() lặp để đảo ngược một danh sách bằng cách sửa lại tất cả trường .next và con trỏ head.
Giải pháp lặp thì khá là phức tạp. Nó thì không quá khó để đặt ở cuối tài liệu này, nhưng nó cần thiết để xử lý vấn đề thứ
18.
void ReverseTest() {
struct node* head;
head = BuildOneTwoThree();
Reverse(&head);
// head now points to the list {3, 2, 1}
DeleteList(&head); // clean up after ourselves
}
/*
Reverse the given linked list by changing its .next pointers and
its head pointer. Takes a pointer (reference) to the head pointer.
*/
void Reverse(struct node** headRef) {
// your code...
12
More information: www.itspiritclub.net Translater: huahongquan2007
(Vấn đề này thì khó và chỉ nên làm nếu bạn đã quen với đệ quy) Có một giải pháp đệ quy hiệu quả và ngắn gọn cho vấn
đề này.
/*
Recursively reverses the given linked list by changing its .next
pointers and its head pointer in one pass of the list.
*/
void RecursiveReverse(struct node** headRef) {
// your code...
13
More information: www.itspiritclub.net Translater: huahongquan2007
Một vòng lặp thẳng xuống danh sách – giống như Length().
Kết hợp vòng lặp danh sách cơ bản với vấn đề đếm để tìm ra node đúng. Có những lỗi thì thông dụng trong
loại code này. Kiểm tra nó kĩ trong những trường hợp đơn giản. Nếu code hoạt động đúng cho n = 0, n = 1 và n =2 thì nó
sẽ đúng cho n = 1000
14
More information: www.itspiritclub.net Translater: huahongquan2007
Xóa toàn bộ danh sách và đặt con trỏ head về NULL. Có một sự phức tạp trong vòng lặp do chúng ta cần phải
tách con trỏ .next trước khi chúng ta xóa node, vì sau khi xóa, nó sẽ không khả dụng nữa.
Tách dữ liệu khỏi node đầu, xóa node, tiến con trỏ head để trỏ tới node tiếp theo. Dùng đối số tham chiếu vì nó
thay đổi con trỏ head.
Đoạn mã này xử lý việc chèn vào đầu như là một trường hợp đặc biệt. Mặc dù nó hoạt động bằng cách chạy
con trỏ current tới node trước node cần thêm vào. Dùng một vòng lặp để kiểm tra con trỏ chính xác.
15
More information: www.itspiritclub.net Translater: huahongquan2007
Chiến lược cơ bản là duyệt xuống danh sách để tìm nơi chèn node mới vào. Có thể là cuối của một danh sách
hoặc là một điểm trước node mà lớn hơn node mới. Có ba giải pháp cho ba cách khác nhau:
16
More information: www.itspiritclub.net Translater: huahongquan2007
Bắt đầu với một danh sách kết quả rỗng. Duyệt xuống danh sách nguồn và SortedInsert() mỗi nodes của nó
vào danh sách kết quả. Cẩn thận chú ý tới trường .next trong mỗi node trước khi di chuyển nó vào danh sách kết quả.
17
More information: www.itspiritclub.net Translater: huahongquan2007
Trường hợp mà danh sách “a” là rỗng là một trường hợp được xử lý đầu tiên – trong trường hợp đó con trỏ head
“a” cần phải thay đổi trực tiếp. Mặc khác, chúng ta duyệt xuống danh sách “a” cho tới khi chúng ta tìm thấy node cuối với
phép kiểm tra (current->next != NULL), và đính danh sách “b” ở đây. Cuối cùng, con trỏ head “b” thì được gán về NULL.
Đoạn code chứng minh việc dùng rộng rãi của đối số tham chiếu của con trỏ và những vấn đề thông thường mà cần thiết
để xác định vị trí của node cuối trong danh sách.
Hàm AppendTest() sau gọi hàm Append() để nối hai danh sách. Bộ nhớ sẽ như thế nào sau khi hàm Append()
thoát?
void AppendTest() {
struct node* a;
struct node* b;
// set a to {1, 2}
// set b to {3, 4}
Append(&a, &b);
}
Hãy chú ý cách mà đối số tham chiếu trong Append() trỏ về con trỏ head trong AppendTest()…
18
More information: www.itspiritclub.net Translater: huahongquan2007
19
More information: www.itspiritclub.net Translater: huahongquan2007
Do danh sách đã được sắp xếp, chúng ta có thể duyệt xuống danh sách và so sánh các node liền kề. Khi các node liền kề
giống nhau, xóa cái thứ hai.
Code của MoveNode() thi tương tự như code của Push(). Nó thì ngắn và chỉ thay đổi một cặp con trỏ - nhưng
nó khá phức tạp. Hãy thử vẽ hình vẽ mô phỏng.
Các tiếp cận đơn giản nhất là duyệ qua danh sách nguồn và dùng MoveNode() để kéo các node ra khỏi nguồn và
luân phiên đặt nó vào “a” và “b”. Phần kì lạ duy nhất là các node sẽ ở thứ tự ngược lại so với danh sách gốc.
20
More information: www.itspiritclub.net Translater: huahongquan2007
AlternatingSplit()
Đây là một cách tiếp cận thay thế mà nó xây dựng các hàm con trong thứ tự giống như danh sách gốc. Mã nguồn
sử dụng một node đầu dummy tạm cho danh sách “a” và “b”. Mỗi danh sách con có một con trỏ tail để trỏ tới node cuối
cùng của nó – bằng cách đó, node mới có thể được nối thêm vào đuôi của mỗi danh sách một cách dễ dàng. Node dummy
thì hữu dụng trong trường trường hợp này vì chúng ta tạm và được cấp phát ở trong stack. Một cách thay thế, kĩ thuật
tham chiếu cục bộ ( local references) có thể dùng để loại bỏ các node dummy( xem phần 1 để biết thêm chi tiết).
21
More information: www.itspiritclub.net Translater: huahongquan2007
Dưới đây là bốn giải pháp riêng biệt. Xem phần 1 để biết thêm về dummy node và local references.
22
More information: www.itspiritclub.net Translater: huahongquan2007
Dùng tham chiếu cục bộ (local references) để loại bỏ các dummy node
SuffleMerge() — Recursive
Giải pháp đệ quy thì súc tích hơn cả, nhưng nó có thể không phù hợp cho mã nguồn thương mại vì nó dùng không gian
space tương ứng với độ dài của danh sách.
23
More information: www.itspiritclub.net Translater: huahongquan2007
Kĩ thuật này dùng node dummy tạm như là node bắt đầu của danh sách kết quả. Con trỏ tail luôn luôn trỏ tới node cuối
cùng của danh sách kết quả nên nối node mới thì đơn giản. Dummy node thì hữu dụng vì nó là tạm thời, nó cấp phát ở
trong stack. Vòng lặp hoạt động, loại bỏ một node khỏi “a” hoặc “b” và thêm nó vào tail. Khi hoàn thành, kết quả trả về là
dummy.next.
Giải pháp này về cấu trúc thì rất tương tự như trên, nhưng nó tránh dùng node dummy. Thay vào đó, nó duy trì con trỏ
struct node **, lastPtrRef, nó luôn trỏ về con trỏ cuối của danh sách kết quả. Điều này giải quyết trường hợp tương tự như
node dummy – xử lý với danh sách kết quả khi nó rỗng.
24
More information: www.itspiritclub.net Translater: huahongquan2007
Merge() là một trong những vấn đề đệ quy đẹp khi đệ quy thì rõ ràng hơn là code lặp.
25
More information: www.itspiritclub.net Translater: huahongquan2007
Ý tưởng của MergeSort là : cắt thành các danh sách nhỏ, sắp xếp chúng bằng đệ quy và trộn hai danh sách đã được sắp
xếp với nhau để tạo thành câu trả lời.
Kĩ thuật là tiến tới trong cả hai danh sách và xây dựng danh sách kết quả. Khi vị trí current trong cả hai danh sách
thì giống nhau, thêm một node vào trong kết quả.Bằng cách khai thác việc cả hai danh sách được sắp xếp, chúng ta chỉ
cần đi qua danh sách một lần. Bằng cách xây dựng danh sách kết quả, có thể dùng cả hai node dummy và local reference
như sau:
// This solution uses the temporary dummy to build up the result list
struct node* SortedIntersect(struct node* a, struct node* b) {
struct node dummy;
struct node* tail = &dummy;
dummy.next = NULL;
// Once one or the other list runs out -- we're done
while (a!=NULL && b!=NULL) {
if (a->data == b->data) {
Push((&tail->next), a->data);
tail = tail->next;
a = a->next;
b = b->next;
}
else if (a->data < b->data) { // advance the smaller list
a = a->next;
}
else {
b = b->next;
}
}
return(dummy.next);
26
More information: www.itspiritclub.net Translater: huahongquan2007
27
More information: www.itspiritclub.net Translater: huahongquan2007
Giải pháp đầu tiên là dùng kĩ thuật “Push” với sự thay đổi con trỏ bằng tay trong vòng lặp. Giải pháp này khá là mánh nếu
chúng ta cần phải lưu giá trị của con trỏ “current ->next” ở đầu vòng lặp khi thân của vòng lặp sẽ ghi đè con trỏ đó.
/*
Iterative list reverse.
Iterate through the list left-right.
Move/insert each node to the front of the result list --
like a Push of the node.
*/
static void Reverse(struct node** headRef) {
struct node* result = NULL;
struct node* current = *headRef;
struct node* next;
while (current != NULL) {
next = current->next; // tricky: note the next node
current->next = result; // move the node onto the result
result = current;
current = next;
}
*headRef = result;
}
28
More information: www.itspiritclub.net Translater: huahongquan2007
// Reverses the given linked list by changing its .next pointers and
// its head pointer. Takes a pointer (reference) to the head pointer.
void Reverse(struct node** headRef) {
if (*headRef != NULL) { // special case: skip the empty list
/*
Plan for this loop: move three pointers: front, middle, back
down the list in order. Middle is the main pointer running
down the list. Front leads it and Back trails it.
For each step, reverse the middle pointer and then advance all
three to get the next node.
*/
struct node* middle = *headRef; // the main pointer
struct node* front = middle->next; // the two other pointers (NULL ok)
struct node* back = NULL;
while (1) {
middle->next = back; // fix the middle node
if (front == NULL) break; // test if done
back = middle; // advance the three pointers
middle = front;
front = front->next;
}
*headRef = middle; // fix the head pointer to point to the new front
}
}
Có lẽ phần khó nhất là nắm bắt được khái niệm của RecursiveReverse(&rest). Hãy vẽ hình mô phỏng để hiểu
cách hàm hoạt động.
Giải pháp kém hiệu quả là đảo những phầ tử n-1 cuối của danh sách, và sau đó duyệt xuống tất cả xuống tới tail
mới và đặt node head cũ đó. Giải pháp đó thì rất là chậm so với trên mà nó lấy node đầu ở đúng vị trí mà không cần vòng
lặp thêm.
29
More information: www.itspiritclub.net Translater: huahongquan2007
Nếu các bạn có ý kiến đóng góp hoặc muốn tham gia dịch tài liệu với chúng tôi, các
bạn hãy liên hệ nhóm itspirit qua ym: whereareyou_sweetie
Xin chân thành cám ơn!
30