So sánh 2 danh sách liên kết (isEqual)
Đề bài
Viết hàm so sánh 2 danh sách liên kết xem có giống nhau hoàn toàn không, với tham số hàm truyền vào là con trỏ head đầu mỗi danh sách. Cụ thể, viết chương trình gồm:
struct node: biểu diễn một node trong danh sách liên kết.- Hàm
createLinkedList:- Nhận vào kích thước của danh sách liên kết ($\gt 0$).
- Tạo danh sách liên kết với các giá trị đọc từ đầu vào chuẩn (stdin), mỗi node mới được thêm vào CUỐI danh sách.
- Trả về con trỏ trỏ tới node đầu tiên của danh sách.
- Hàm
isEqualso sánh hai danh sách liên kết:- Nhận vào con trỏ
head1của danh sách thứ nhất và con trỏhead2của danh sách thứ hai. - Trả về
truekhi và chỉ khi 2 danh sách có cùng kích thước và giá trị các node giống hệt nhau (theo đúng thứ tự). Ngược lại trả vềfalse.
- Nhận vào con trỏ
- Hàm
mainđọc kích thước danh sách, gọi hàmcreateLinkedListđể khởi tạo danh sách, rồi gọi hàm để in kết quả.
Yêu cầu: hoàn thành hai hàm createLinkedList và isEqual.
Dữ liệu vào: các giá trị nhập từ đầu vào chuẩn (stdin), mỗi giá trị nằm trong khoảng $(0;\ 5000)$. Cụ thể (theo hàm main cho sẵn): dòng 1 là $n$, dòng 2 là $n$ giá trị của danh sách thứ nhất, dòng 3 là $m$, dòng 4 là $m$ giá trị của danh sách thứ hai.
Kết quả: in ra 1 nếu hai danh sách giống nhau hoàn toàn, ngược lại in ra 0 (vì cout in giá trị bool ra dạng số).
Write a program where:
struct node: represents a node in linkedlists- functions
createLinkedList:- Receive the size of a linked list ($\gt 0$)
- create a linked list with value from standard input (stdin) a new node is add to the END of the linked list
- Return a pointer which points to the first node of the linked list.
- Function
isEqualcompare two linked list:- Receive the pointer
head1of the 1st linked list, pointerhead2of the 2nd linked list - Return true if and only if 2 the linked lists have identical size, and node value. Otherwise, return false.
- Receive the pointer
- Function
mainreads the size of the linked list, calls functioncreateLinkedListto Initialize the linked list, then call function print to print all the node's values of the linked list.
Complete function createLinkedList, isEqual
Input:
The value input from standard input (stdin) with value in $(0;\ 5000)$
5
1 3 5 7 9
5
1 3 5 7 91Khung code cho sẵn của đề (chỉ được cài đặt bên trong 2 hàm createLinkedList và isEqual):
#include <iostream>
using namespace std;
struct node
{
int data;
node *next;
};
node *createLinkedList(int n)
{
// TO DO
}
bool isEqual(node *head1, node *head2)
{
// TO DO
}
int main()
{
int n = 0;
cin >> n;
node *head1 = createLinkedList(n);
int m = 0;
cin >> m;
node *head2 = createLinkedList(m);
cout << isEqual(head1, head2) << endl;
return 0;
}
createLinkedList nhưng trong main lại gọi createLinkList (thiếu chữ "ed"). Trong bài này mình thống nhất dùng một tên createLinkedList để code biên dịch được.Lời mở đầu
Đây là câu hỏi của một em học viên nhắn cho mình. Phần tạo danh sách (createLinkedList) thì hôm trước mình đã hướng dẫn rồi, còn cái hàm so sánh 2 danh sách liên kết thì em ấy chưa hiểu đề muốn gì 😅. Mình giải thích qua tin nhắn khá kỹ, có cả mấy kinh nghiệm về con trỏ head mà nhiều bạn mới học hay bị lú, nên viết lại thành bài để nhiều bạn cùng tham khảo luôn. Hy vọng giúp ích được cho các bạn 😇.
Bạn nào muốn thử nộp bài để hệ thống chấm xem có đúng hay không thì link bài đây nhé:
Bấm vào nút bên dưới để nộp bài và được hệ thống CLUE OJ chấm tự động.
Nộp bài trên CLUE OJĐề nói gì? Đơn giản thôi: trả về true khi 2 danh sách giống nhau hoàn toàn (cùng số node, và node thứ $i$ của danh sách này có data bằng node thứ $i$ của danh sách kia), còn có 1 cái gì đó khác nhau thì trả về false.
Em học viên còn thắc mắc: "lúc nhập vào thì chỉ có 1 list thôi, làm sao lúc so sánh lại tách ra được thành 2 list?". Các bạn nhìn lại hàm main cho sẵn sẽ thấy nó gọi createLinkedList hai lần: lần đầu đọc $n$ rồi $n$ số tạo ra danh sách head1, lần sau đọc $m$ rồi $m$ số tạo ra danh sách head2. Test ví dụ cũng nhập vào cả 2 list đó:
5và1 3 5 7 9→ danh sách 1:1 → 3 → 5 → 7 → 95và1 3 5 7 9→ danh sách 2:1 → 3 → 5 → 7 → 9
Hai danh sách cùng 5 node và từng cặp node đều bằng nhau nên kết quả là 1 (true). Còn nếu danh sách 2 là 1 3 5 7 (thiếu một node) hoặc 1 3 6 7 9 (khác ở node thứ 3) thì đều in 0.
Lộ trình lời giải, bấm vào từng phần để nhảy tới:
Cách 1Đếm số node trước, rồi so từng node – O(n + m)
Đây là hướng mà em học viên nghĩ ra đầu tiên: "đề yêu cầu size 2 cái linked list phải bằng nhau và các mắt xích trong đó phải giống nhau, nên đầu tiên em đếm số phần tử, xong em mới so sánh từng mắt xích". Code ban đầu của em ấy như sau:
node *current = head1;
int idx = 0;
while(current != nullptr)
{
current = current->next;
idx++;
}
int y = 0;
while(head1 == head2){ y++; head1 = head1->next; }
if (y == idx) return 1;
else return 0;
Ý tưởng thì không sai, nhưng code này "vẫn chưa đúng hết test case" vì mấy lỗi sau:
head1 == head2là so sánh địa chỉ của 2 con trỏ, không phải so sánhdata. Hai danh sách được tạo riêng bằngnewthì các node nằm ở vùng nhớ khác nhau, nên điều kiện này gần như luôn sai ⇒y = 0⇒ luôn trả về 0. Phải sohead1->datavớihead2->data.- Chỉ đếm số node của danh sách 1, không đếm danh sách 2. Nếu danh sách 2 dài hơn (vd
1 3 5 7 9 11) thì vẫn có thể bị kết luận là bằng nhau. - Vòng
whilethứ hai chỉ chohead1đi tiếp màhead2đứng yên, và cũng không kiểm tranullptr⇒ có thể truy cập vào con trỏ rỗng.
Sửa lại cho đúng ý tưởng "đếm trước, so sau" thì ta viết thêm hàm countNode đếm số node của cả hai danh sách. Nếu số node khác nhau thì trả về false luôn; nếu bằng nhau thì cho 2 con trỏ cùng đi, so data từng cặp node:
#include <iostream>
using namespace std;
struct node
{
int data;
node *next;
};
node *createLinkedList(int n)
{
node *head = nullptr, *tail = nullptr;
for(int i = 0; i < n; i++)
{
node *p = new node;
cin >> p->data;
p->next = nullptr;
if(head == nullptr)
head = tail = p;
else
{
tail->next = p;
tail = p;
}
}
return head;
}
int countNode(node *head)
{
int cnt = 0;
while(head != nullptr)
{
cnt++;
head = head->next;
}
return cnt;
}
bool isEqual(node *head1, node *head2)
{
if(countNode(head1) != countNode(head2)) // lượt duyệt thứ nhất: đếm
return false;
while(head1 != nullptr) // lượt duyệt thứ hai: so data
{
if(head1->data != head2->data)
return false;
head1 = head1->next;
head2 = head2->next;
}
return true;
}
int main()
{
int n = 0;
cin >> n;
node *head1 = createLinkedList(n);
int m = 0;
cin >> m;
node *head2 = createLinkedList(m);
cout << isEqual(head1, head2) << endl;
return 0;
}
import sys
class Node:
def __init__(self, data):
self.data = data
self.next = None
def create_linked_list(n, it):
head = tail = None
for _ in range(n):
p = Node(int(next(it)))
if head is None:
head = tail = p
else:
tail.next = p
tail = p
return head
def count_node(head):
cnt = 0
while head is not None:
cnt += 1
head = head.next
return cnt
def is_equal(head1, head2):
if count_node(head1) != count_node(head2): # lượt duyệt thứ nhất: đếm
return False
while head1 is not None: # lượt duyệt thứ hai: so data
if head1.data != head2.data:
return False
head1 = head1.next
head2 = head2.next
return True
it = iter(sys.stdin.read().split())
n = int(next(it))
head1 = create_linked_list(n, it)
m = int(next(it))
head2 = create_linked_list(m, it)
print(1 if is_equal(head1, head2) else 0)
Đánh giá độ phức tạp của cách làm này:
- Không gian: chỉ dùng vài biến đếm, con trỏ: $O(1)$ (không tính 2 danh sách).
- Thời gian: lượt đầu duyệt hết cả 2 danh sách để đếm: $O(n + m)$; lượt sau duyệt thêm tối đa $n$ node để so
data. Tổng $O(n + m)$.
Cách này chạy đúng, nhưng như mình nói với em ấy: nếu theo giải pháp đi đếm hết các phần tử của 2 list rồi so sánh với nhau, sau đó mới bắt đầu đi so chi tiết data của các phần tử thì cách này dở rồi 😃. Bởi vì bản chất khi đi đếm số lượng phần tử là mình đã duyệt qua từng node rồi, rồi lại phải duyệt lại lần nữa ⇒ duyệt 2 lần. Mình chỉ nên duyệt 1 lần thôi.
Cách 2Cho head1, head2 cùng duyệt song song – O(min(n, m))
Giải pháp tốt nhất nên là lấy chính 2 con trỏ head1, head2 cùng duyệt danh sách. Có thể bắt theo con trỏ head1 duyệt danh sách 1, cứ với mỗi node trong danh sách 1 thì đồng thời con trỏ head2 cũng duyệt node tương ứng bên nó:
- Nếu thấy con trỏ
head1có tồn tại mà con trỏhead2khi gọi sang node tương ứng bên nó bịnullptr⇒ danh sách 2 ngắn hơn ⇒return false. - Còn nếu
head2có tồn tại thì lúc này so sánh 2 cáidata: nếu khác nhau thìreturn false; còn nếu giống nhau thì tiếp tục cho lần lượt 2 con trỏ duyệt sang node tiếp theo và lặp lại quy trình trên. - Sau cùng, khi đã duyệt xong list 1 thì vòng lặp kết thúc (vì vòng lặp chạy duyệt theo list 1 mà). Lúc này kiểm tra con trỏ
head2của list 2: nếu nó cũng lànullptrtức là nó cũng đã kết thúc danh sách bên nó ⇒return truevì 2 danh sách cùng số lượng phần tử như nhau. Còn nếuhead2vẫn còn khácnullptrtức là list 2 chưa kết thúc ⇒return false, tức là 2 list không khớp số phần tử nhau.
data khác nhau, list 2 hết trước list 1, hoặc list 1 hết mà list 2 vẫn còn. Không cần biết trước $n$, $m$, và chỉ duyệt mỗi list 1 lần.Các bạn nhập 2 danh sách vào khung dưới đây rồi bấm Tiếp để xem 2 con trỏ đi song song như thế nào nhé:
Code của mình ở đây vẫn là "code notepad" gõ thẳng trong tin nhắn cho em học viên thôi 😄, em ấy nộp thử thì đúng hết luôn. Source code để các bạn tham khảo nếu làm theo hướng này:
#include <iostream>
using namespace std;
struct node
{
int data;
node *next;
};
node *createLinkedList(int n)
{
node *head = nullptr, *tail = nullptr;
for(int i = 0; i < n; i++)
{
node *p = new node;
cin >> p->data;
p->next = nullptr;
if(head == nullptr)
head = tail = p;
else
{
tail->next = p;
tail = p;
}
}
return head;
}
bool isEqual(node *head1, node *head2)
{
while(head1 != nullptr)
{
if(head2 != nullptr)
{
if(head1->data != head2->data)
return false;
}
else
return false;
head1 = head1->next;
head2 = head2->next;
}
if(head2 != nullptr)
return false;
return true;
}
int main()
{
int n = 0;
cin >> n;
node *head1 = createLinkedList(n);
int m = 0;
cin >> m;
node *head2 = createLinkedList(m);
cout << isEqual(head1, head2) << endl;
return 0;
}
import sys
class Node:
def __init__(self, data):
self.data = data
self.next = None
def create_linked_list(n, it):
head = tail = None
for _ in range(n):
p = Node(int(next(it)))
if head is None:
head = tail = p
else:
tail.next = p
tail = p
return head
def is_equal(head1, head2):
while head1 is not None:
if head2 is not None:
if head1.data != head2.data:
return False
else:
return False
head1 = head1.next
head2 = head2.next
if head2 is not None:
return False
return True
it = iter(sys.stdin.read().split())
n = int(next(it))
head1 = create_linked_list(n, it)
m = int(next(it))
head2 = create_linked_list(m, it)
print(1 if is_equal(head1, head2) else 0)
- Với những hàm mà đòi hỏi phải trả về head của danh sách (như
createLinkedList) thì nên dùng 1 con trỏ khác (ví dụ con trỏcurrent, haytailnhư trong code trên) để duyệt lần lượt các node. Chứ nếu dùng chínhheadmà duyệt thìheadđã bị thay đổi địa chỉ, như thế thì sau cùng không return được. - Còn với những hàm mà không trả về head, như trường hợp hàm
bool isEqualnày chỉ trả về true/false thôi, thì ta không cần tạo racurrentđể duyệt mà có thể lấy chínhhead1,head2duyệt luôn. - Cũng không sợ bị thay đổi
headở hàmmaintruyền vào, vì hàm này đang truyền theo kiểu tham trị: bản chất vô hàm là nó đã tạo ra 1 phiên bản con trỏhead_copy = head, và cáiheadcác bạn gọi trong hàm chính là con trỏhead_copyđó; cònheadở hàmmainvẫn giữ nguyên địa chỉ ban đầu. - Chừng nào tham số con trỏ truyền vào là tham chiếu, ví dụ
node *&head, thì nếu lấy con trỏheadnày duyệt thì con trỏheadngoài hàmmainsẽ bị thay đổi địa chỉ theo, vì lúc này nó không tạo ra phiên bản copy nữa mà đang làm việc trên chính vùng nhớ con trỏheadcủa hàmmaintruyền vào.
Khung chạy tay dưới đây chạy đúng từng dòng của code C++ ở trên. Phần đầu là 2 lần gọi createLinkedList để tạo danh sách, các bạn có thể bấm Tiếp nhanh qua phần đó để tới hàm isEqual (dòng 29 – 49):
Đánh giá độ phức tạp của cách làm này:
- Không gian: không tạo thêm biến nào ngoài chính 2 tham số con trỏ: $O(1)$ (không tính 2 danh sách).
- Thời gian: mỗi bước 2 con trỏ cùng tiến 1 node, dừng ngay khi gặp chỗ khác nhau hoặc list 1 hết, nên duyệt tối đa $\min(n, m) + 1$ bước. Khi 2 list giống nhau thì mỗi list chỉ duyệt đúng 1 lần.
Quan trọng không phải là chỉ xem code, mà là xem nó kèm theo những giải thích ở trên để hiểu cho rõ nghĩa. Các bạn nên lấy một tờ giấy vẽ 2 dãy ô vuông tượng trưng cho 2 danh sách rồi dò lại từng bước (hoặc bấm khung chạy tay) nha. Bạn nào còn đang lơ mơ phần con trỏ và struct thì nên ôn lại chắc phần đó trước rồi mới qua danh sách liên kết, sau đó quay lại tự giải lại bài này 😃.
cin), bản Python khoảng 2.5 giây.- Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
- Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
- Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
- Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
- Dễ nản giữa chừng khi không có ai theo sát.
- Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
- Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
- Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
- Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.
Nhận xét
Đăng nhận xét