Bài 33.3: Danh sách liên kết đôi trong C++
Bài 33.3: Danh sách liên kết đôi trong C++
Chào mừng trở lại với series C++! Hôm nay, chúng ta sẽ đào sâu vào một cấu trúc dữ liệu vô cùng linh hoạt và mạnh mẽ: Danh sách liên kết đôi (Doubly Linked List). Nếu bạn đã quen thuộc với danh sách liên kết đơn (singly linked list), thì danh sách liên kết đôi sẽ là một bước tiến thú vị, mang lại những khả năng mới.
Vậy, danh sách liên kết đôi là gì và nó khác gì so với danh sách liên kết đơn?
Khác biệt cốt lõi: Hai con trỏ!
Điểm khác biệt quan trọng nhất nằm ở mỗi phần tử (hay node) của danh sách. Trong danh sách liên kết đơn, mỗi node chỉ chứa dữ liệu và một con trỏ next (trỏ tới node kế tiếp). Ngược lại, trong danh sách liên kết đôi, mỗi node chứa dữ liệu và hai con trỏ:
next: Con trỏ trỏ tới node kế tiếp trong danh sách.prev: Con trỏ trỏ tới node trước đó trong danh sách.
Chính sự tồn tại của con trỏ prev này mang lại những ưu điểm đáng kể, đặc biệt là khả năng duyệt danh sách theo cả hai chiều (từ đầu đến cuối và từ cuối về đầu) và thao tác xoá dễ dàng hơn trong nhiều trường hợp.
Hãy cùng xem cấu trúc của một node trong C++:
struct Node {
int g;
Node* n;
Node* p;
Node(int gt) : g(gt), n(nullptr), p(nullptr) {}
};
Giải thích code:
- Chúng ta định nghĩa một
structtên làNode. - Nó có một thành viên
datađể lưu trữ dữ liệu (ở đây dùngint, bạn có thể dùng kiểu dữ liệu khác tùy ý). nextlà một con trỏ kiểuNode*, trỏ đến node "đằng sau" nó.prevlà một con trỏ kiểuNode*, trỏ đến node "đằng trước" nó.- Constructor
Node(int val)là một cách tiện lợi để tạo một node mới, gán giá trịvalchodatavà ban đầu đặt cảnextlẫnprevlànullptr(chỉ báo rằng nó chưa liên kết với node nào khác).
Cấu trúc tổng thể của Danh sách liên kết đôi
Để quản lý toàn bộ danh sách, chúng ta thường sử dụng một lớp (class) hoặc struct chứa các con trỏ đặc biệt:
head: Con trỏ trỏ tới node đầu tiên của danh sách.tail: Con trỏ trỏ tới node cuối cùng của danh sách.
Sự có mặt của con trỏ tail cũng là một điểm khác biệt so với danh sách liên kết đơn thông thường (mặc dù bạn có thể thêm tail vào danh sách đơn nếu cần). Việc có tail giúp các thao tác thêm/xoá ở cuối danh sách trở nên hiệu quả hơn rất nhiều (O(1)) thay vì phải duyệt toàn bộ danh sách từ đầu như trong danh sách đơn.
Lớp quản lý danh sách liên kết đôi có thể trông như thế này:
#include <iostream>
struct Node {
int g;
Node* n;
Node* p;
Node(int gt) : g(gt), n(nullptr), p(nullptr) {}
};
class DoublyLinkedList {
private:
Node* h;
Node* t;
public:
DoublyLinkedList() : h(nullptr), t(nullptr) {}
~DoublyLinkedList();
void themDau(int g);
void themCuoi(int g);
void xoa(int k);
void hienThiXuoi() const;
void hienThiNguoc() const;
bool tim(int k) const;
};
DoublyLinkedList::~DoublyLinkedList() {
Node* hienTai = h;
while (hienTai != nullptr) {
Node* keTiep = hienTai->n;
delete hienTai;
hienTai = keTiep;
}
h = nullptr;
t = nullptr;
cout << "DSLK doi da duoc huy." << endl;
}
// ... Triển khai các phương thức khác sẽ ở dưới ...
Giải thích code:
- Lớp
DoublyLinkedListchứa hai con trỏheadvàtail, ban đầu được khởi tạo lànullptrtrong constructor, biểu thị một danh sách rỗng. - Destructor
~DoublyLinkedList()là phần cực kỳ quan trọng. Vì chúng ta cấp phát bộ nhớ cho các node bằngnew, chúng ta phải giải phóng chúng bằngdeletekhi không cần nữa để tránh rò rỉ bộ nhớ (memory leak). Destructor này duyệt qua toàn bộ danh sách và xoá từng node một cách an toàn.
Các thao tác cơ bản trên Danh sách liên kết đôi
Bây giờ, hãy đi sâu vào cách triển khai các thao tác phổ biến trên danh sách liên kết đôi.
1. Thêm node vào đầu danh sách (insertAtBeginning)
Thao tác này khá đơn giản, cần cập nhật con trỏ head và các liên kết của node mới cũng như node cũ ở đầu.
void DoublyLinkedList::themDau(int g) {
Node* n = new Node(g);
if (h == nullptr) {
h = n;
t = n;
} else {
n->n = h;
h->p = n;
h = n;
}
cout << "Da them " << g << " vao dau DSLK." << endl;
}
Giải thích code:
- Tạo một node mới với dữ liệu cần thêm. Con trỏ
nextvàprevcủa nó ban đầu lànullptr(từ constructorNode). - Kiểm tra danh sách rỗng: Nếu
headlànullptr, tức danh sách chưa có phần tử nào, node mới chính là node đầu tiên và cũng là node cuối cùng. Cập nhật cảheadvàtailtrỏ tới node mới. - Danh sách không rỗng:
- Con trỏ
nextcủanewNodesẽ trỏ tới node hiện đang làhead. - Con trỏ
prevcủa node hiện đang làheadsẽ trỏ ngược vềnewNode. - Cập nhật
headđể nó trỏ tớinewNode.
- Con trỏ
2. Thêm node vào cuối danh sách (insertAtEnd)
Thao tác này hiệu quả hơn nhiều so với danh sách đơn nhờ có con trỏ tail.
void DoublyLinkedList::themCuoi(int g) {
Node* n = new Node(g);
if (t == nullptr) {
h = n;
t = n;
} else {
n->p = t;
t->n = n;
t = n;
}
cout << "Da them " << g << " vao cuoi DSLK." << endl;
}
Giải thích code:
- Tạo một node mới.
- Kiểm tra danh sách rỗng: Giống như thêm vào đầu, node mới là head và tail.
- Danh sách không rỗng:
- Con trỏ
prevcủanewNodesẽ trỏ tới node hiện đang làtail. - Con trỏ
nextcủa node hiện đang làtailsẽ trỏ tớinewNode. - Cập nhật
tailđể nó trỏ tớinewNode.
- Con trỏ
3. Duyệt danh sách
Đây là lúc ưu điểm của danh sách đôi thể hiện rõ ràng: bạn có thể duyệt từ đầu hoặc từ cuối.
displayForwardvoid DoublyLinkedList::hienThiXuoi() const {
if (h == nullptr) {
cout << "DSLK rong." << endl;
return;
}
Node* hienTai = h;
while (hienTai != nullptr) {
cout << hienTai->g << " <-> ";
hienTai = hienTai->n;
}
cout << "nullptr" << endl;
}
Giải thích code:
- Kiểm tra danh sách rỗng.
- Bắt đầu từ
head. - Lặp chừng nào
currentcòn khácnullptr. - In dữ liệu của node
current. - Cập nhật
currentbằngcurrent->nextđể di chuyển tới node tiếp theo.
displayBackwardvoid DoublyLinkedList::hienThiNguoc() const {
if (t == nullptr) {
cout << "DSLK rong." << endl;
return;
}
Node* hienTai = t;
while (hienTai != nullptr) {
cout << hienTai->g << " <-> ";
hienTai = hienTai->p;
}
cout << "nullptr" << endl;
}
Giải thích code:
- Kiểm tra danh sách rỗng.
- Bắt đầu từ
tail. - Lặp chừng nào
currentcòn khácnullptr. - In dữ liệu của node
current. - Cập nhật
currentbằngcurrent->prevđể di chuyển tới node trước đó. Đây là điểm khác biệt so với duyệt xuôi.
4. Tìm kiếm giá trị (search)
Thao tác này tương tự như danh sách đơn, chỉ cần duyệt từ đầu đến cuối và kiểm tra dữ liệu.
bool DoublyLinkedList::tim(int k) const {
Node* hienTai = h;
while (hienTai != nullptr) {
if (hienTai->g == k) {
return true;
}
hienTai = hienTai->n;
}
return false;
}
Giải thích code:
- Bắt đầu từ
head. - Lặp qua từng node.
- Trong mỗi node, kiểm tra xem
current->datacó bằngkeycần tìm không. - Nếu bằng, trả về
truengay lập tức. - Nếu duyệt hết danh sách mà không tìm thấy, vòng lặp kết thúc và trả về
false.
5. Xoá node (deleteNode)
Xoá một node là nơi danh sách liên kết đôi thể hiện sự tiện lợi nếu bạn đã có con trỏ tới node cần xoá. Tuy nhiên, thường chúng ta cần xoá một node có giá trị cụ thể, nên trước tiên phải tìm node đó. Sau khi tìm thấy, việc cập nhật con trỏ next của node đứng trước và con trỏ prev của node đứng sau node bị xoá là cần thiết. Phải cẩn thận xử lý các trường hợp đặc biệt: xoá node đầu, xoá node cuối, xoá node duy nhất.
void DoublyLinkedList::xoa(int k) {
Node* hienTai = h;
while (hienTai != nullptr && hienTai->g != k) {
hienTai = hienTai->n;
}
if (hienTai == nullptr) {
cout << "Node " << k << " khong tim thay." << endl;
return;
}
cout << "Dang xoa node: " << k << endl;
if (hienTai->p != nullptr) {
hienTai->p->n = hienTai->n;
} else {
h = hienTai->n;
}
if (hienTai->n != nullptr) {
hienTai->n->p = hienTai->p;
} else {
t = hienTai->p;
}
if (h == nullptr) {
t = nullptr;
}
delete hienTai;
}
Giải thích code:
- Duyệt từ đầu danh sách để tìm node có
databằngkey. - Nếu vòng lặp kết thúc mà
currentlànullptr, nghĩa là không tìm thấy node, in thông báo và kết thúc hàm. - Nếu tìm thấy (
currentkhông phảinullptr):- Cập nhật con trỏ
prevcủa node kế tiếp: Nếu node bị xoá không phải là node cuối cùng (current->next != nullptr), thì con trỏprevcủa node đứng sau nó (current->next->prev) phải được cập nhật để trỏ tới node đứng trước node bị xoá (current->prev). Nếu node bị xoá là node cuối, cập nhậttailthành node đứng trước nó. - Cập nhật con trỏ
nextcủa node trước đó: Nếu node bị xoá không phải là node đầu tiên (current->prev != nullptr), thì con trỏnextcủa node đứng trước nó (current->prev->next) phải được cập nhật để trỏ tới node đứng sau node bị xoá (current->next). Nếu node bị xoá là node đầu, cập nhậtheadthành node đứng sau nó. - Trường hợp node duy nhất: Nếu sau khi xoá,
headtrở thànhnullptr, điều đó có nghĩa là danh sách đã rỗng, nêntailcũng phải được đặt lànullptr.
- Cập nhật con trỏ
- Cuối cùng, sử dụng
delete currentđể giải phóng bộ nhớ mà node đó đang chiếm giữ.
Ví dụ minh hoạ đầy đủ
Kết hợp tất cả lại, đây là một ví dụ đơn giản sử dụng các thao tác đã triển khai:
#include <iostream>
struct Node {
int g;
Node* n;
Node* p;
Node(int gt) : g(gt), n(nullptr), p(nullptr) {}
};
class DoublyLinkedList {
private:
Node* h;
Node* t;
public:
DoublyLinkedList() : h(nullptr), t(nullptr) {}
~DoublyLinkedList();
void themDau(int g);
void themCuoi(int g);
void xoa(int k);
void hienThiXuoi() const;
void hienThiNguoc() const;
bool tim(int k) const;
};
DoublyLinkedList::~DoublyLinkedList() {
Node* hienTai = h;
while (hienTai != nullptr) {
Node* keTiep = hienTai->n;
delete hienTai;
hienTai = keTiep;
}
h = nullptr;
t = nullptr;
cout << "DSLK doi da duoc huy." << endl;
}
void DoublyLinkedList::themDau(int g) {
Node* n = new Node(g);
if (h == nullptr) {
h = n;
t = n;
} else {
n->n = h;
h->p = n;
h = n;
}
cout << "Da them " << g << " vao dau DSLK." << endl;
}
void DoublyLinkedList::themCuoi(int g) {
Node* n = new Node(g);
if (t == nullptr) {
h = n;
t = n;
} else {
n->p = t;
t->n = n;
t = n;
}
cout << "Da them " << g << " vao cuoi DSLK." << endl;
}
void DoublyLinkedList::xoa(int k) {
Node* hienTai = h;
while (hienTai != nullptr && hienTai->g != k) {
hienTai = hienTai->n;
}
if (hienTai == nullptr) {
cout << "Node " << k << " khong tim thay." << endl;
return;
}
cout << "Dang xoa node: " << k << endl;
if (hienTai->p != nullptr) {
hienTai->p->n = hienTai->n;
} else {
h = hienTai->n;
}
if (hienTai->n != nullptr) {
hienTai->n->p = hienTai->p;
} else {
t = hienTai->p;
}
if (h == nullptr) {
t = nullptr;
}
delete hienTai;
}
void DoublyLinkedList::hienThiXuoi() const {
if (h == nullptr) {
cout << "DSLK rong.";
} else {
Node* hienTai = h;
while (hienTai != nullptr) {
cout << hienTai->g << " <-> ";
hienTai = hienTai->n;
}
cout << "nullptr";
}
cout << endl;
}
void DoublyLinkedList::hienThiNguoc() const {
if (t == nullptr) {
cout << "DSLK rong.";
} else {
Node* hienTai = t;
while (hienTai != nullptr) {
cout << hienTai->g << " <-> ";
hienTai = hienTai->p;
}
cout << "nullptr";
}
cout << endl;
}
bool DoublyLinkedList::tim(int k) const {
Node* hienTai = h;
while (hienTai != nullptr) {
if (hienTai->g == k) {
return true;
}
hienTai = hienTai->n;
}
return false;
}
int main() {
DoublyLinkedList ds;
ds.themCuoi(10);
ds.themDau(5);
ds.themCuoi(20);
ds.themDau(1);
ds.themCuoi(30);
cout << "\n--- DSLK sau khi them ---" << endl;
cout << "Duyet xuoi: ";
ds.hienThiXuoi();
cout << "Duyet nguoc: ";
ds.hienThiNguoc();
cout << "\n--- Tim kiem ---" << endl;
int gtTim = 10;
if (ds.tim(gtTim)) {
cout << gtTim << " co trong DSLK." << endl;
} else {
cout << gtTim << " khong co trong DSLK." << endl;
}
gtTim = 100;
if (ds.tim(gtTim)) {
cout << gtTim << " co trong DSLK." << endl;
} else {
cout << gtTim << " khong co trong DSLK." << endl;
}
cout << "\n--- Xoa phan tu ---" << endl;
ds.xoa(20);
ds.xoa(1);
ds.xoa(30);
ds.xoa(100);
cout << "\n--- DSLK sau khi xoa ---" << endl;
cout << "Duyet xuoi: ";
ds.hienThiXuoi();
cout << "Duyet nguoc: ";
ds.hienThiNguoc();
ds.xoa(5);
ds.xoa(10);
cout << "\n--- DSLK sau khi xoa het ---" << endl;
cout << "Duyet xuoi: ";
ds.hienThiXuoi();
cout << "Duyet nguoc: ";
ds.hienThiNguoc();
return 0;
}
Output:
Da them 10 vao cuoi DSLK.
Da them 5 vao dau DSLK.
Da them 20 vao cuoi DSLK.
Da them 1 vao dau DSLK.
Da them 30 vao cuoi DSLK.
--- DSLK sau khi them ---
Duyet xuoi: 1 <-> 5 <-> 10 <-> 20 <-> 30 <-> nullptr
Duyet nguoc: 30 <-> 20 <-> 10 <-> 5 <-> 1 <-> nullptr
--- Tim kiem ---
10 co trong DSLK.
100 khong co trong DSLK.
--- Xoa phan tu ---
Dang xoa node: 20
Dang xoa node: 1
Dang xoa node: 30
Node 100 khong tim thay.
--- DSLK sau khi xoa ---
Duyet xuoi: 5 <-> 10 <-> nullptr
Duyet nguoc: 10 <-> 5 <-> nullptr
Dang xoa node: 5
Dang xoa node: 10
--- DSLK sau khi xoa het ---
Duyet xuoi: DSLK rong.
Duyet nguoc: DSLK rong.
DSLK doi da duoc huy.
Giải thích code main:
- Tạo một đối tượng
listthuộc lớpDoublyLinkedList. - Sử dụng các phương thức
insertAtEndvàinsertAtBeginningđể thêm các số vào danh sách. - Gọi
displayForwardvàdisplayBackwardđể xem trạng thái danh sách từ hai chiều. - Sử dụng
searchđể kiểm tra sự tồn tại của các giá trị. - Gọi
deleteNodenhiều lần để minh hoạ việc xoá các node ở các vị trí khác nhau (giữa, đầu, cuối) và cả node không tồn tại. - Sau mỗi lần xoá, hiển thị lại danh sách để thấy sự thay đổi.
- Khi chương trình kết thúc (
return 0), đối tượnglistnằm trong hàmmainsẽ bị hủy, và destructor~DoublyLinkedList()sẽ tự động được gọi để giải phóng toàn bộ bộ nhớ đã cấp phát cho các node, tránh rò rỉ bộ nhớ.
Ưu điểm và Nhược điểm của Danh sách liên kết đôi
Ưu điểm:
- Duyệt hai chiều dễ dàng: Có thể đi từ head đến tail hoặc ngược lại một cách hiệu quả.
- Xoá node hiệu quả hơn: Nếu bạn đã có con trỏ tới node cần xoá, việc cập nhật các liên kết chỉ mất thời gian O(1) (hằng số), không cần tìm node trước đó như trong danh sách đơn.
- Thêm/Xoá ở cuối nhanh chóng: Nhờ có con trỏ
tail, các thao tác này cũng chỉ mất thời gian O(1).
Nhược điểm:
- Tốn bộ nhớ hơn: Mỗi node cần thêm một con trỏ (
prev), do đó tốn bộ nhớ gấp rưỡi hoặc gấp đôi so với danh sách liên kết đơn (tùy thuộc vào việc lưu trữ dữ liệu). - Thao tác thêm/xoá phức tạp hơn: Cần cập nhật hai con trỏ (
nextvàprev) cho mỗi node bị ảnh hưởng, thay vì chỉ một như trong danh sách đơn. Điều này dễ dẫn đến sai sót nếu không cẩn thận.
Khi nào sử dụng Danh sách liên kết đôi?
Bạn nên cân nhắc sử dụng danh sách liên kết đôi khi:
- Bạn cần thường xuyên duyệt danh sách theo cả hai chiều.
- Bạn cần thực hiện các thao tác thêm/xoá hiệu quả ở cả đầu và cuối danh sách.
- Bạn cần khả năng xoá một node nhanh chóng khi đã có sẵn con trỏ tới node đó.
Tuy nhiên, trong nhiều trường hợp thực tế, thư viện chuẩn C++ (list) đã cung cấp một triển khai danh sách liên kết đôi tối ưu và an toàn. Bạn nên ưu tiên sử dụng list trừ khi bạn có lý do cụ thể để tự triển khai (ví dụ: để học tập, tối ưu hóa rất đặc thù, hoặc làm việc trong môi trường không có STL).
Sử dụng list (Thay thế thực tế)
Để hoàn thiện bức tranh, hãy xem cách sử dụng list đơn giản:
#include <iostream>
#include <list>
#include <algorithm>
int main() {
list<int> ds;
ds.push_back(10);
ds.push_front(5);
ds.push_back(20);
cout << "DS list: ";
for (int gt : ds) {
cout << gt << " <-> ";
}
cout << "nullptr" << endl;
auto it = find(ds.begin(), ds.end(), 10);
if (it != ds.end()) {
ds.erase(it);
cout << "Da xoa 10." << endl;
}
cout << "DS sau khi xoa 10: ";
for (int gt : ds) {
cout << gt << " <-> ";
}
cout << "nullptr" << endl;
ds.pop_front();
cout << "Sau khi pop_front: ";
for (int gt : ds) {
cout << gt << " <-> ";
}
cout << "nullptr" << endl;
return 0;
}
Output:
DS list: 5 <-> 10 <-> 20 <-> nullptr
Da xoa 10.
DS sau khi xoa 10: 5 <-> 20 <-> nullptr
Sau khi pop_front: 20 <-> nullptr
Giải thích code list:
- Sử dụng
list<int>để tạo một danh sách các số nguyên. push_backvàpush_frontthêm phần tử vào cuối và đầu tương ứng.- Duyệt danh sách dễ dàng bằng range-based for loop.
- Để xoá theo giá trị, bạn cần dùng
findđể lấy iterator tới phần tử đó, sau đó dùng phương thứcerasecủa list với iterator này. pop_frontvàpop_backxoá phần tử ở đầu và cuối.- Ưu điểm lớn nhất là
listtự động quản lý bộ nhớ, bạn không cần lo lắng vềnewvàdelete.
Bài tập ví dụ: C++ Bài 21.A3: Vé vào hội chợ
Vé vào hội chợ
FullHouse Dev đang tổ chức một hội chợ ở FullHouseLand. Anh ấy muốn tham gia hội chợ cùng với N người bạn của mình. Anh ấy đã thu thập được K vé vào cửa. Liệu Anh ấy có thể vào hội chợ cùng với tất cả N người bạn của mình không?
Mỗi người cần một vé để vào hội chợ, và mỗi vé chỉ có thể được sử dụng bởi một người.
INPUT FORMAT
- Dòng đầu tiên chứa số nguyên T — số lượng bộ test.
- Mỗi bộ test gồm một dòng chứa hai số nguyên cách nhau bởi dấu cách N, K.
OUTPUT FORMAT
- Với mỗi bộ test, in ra trên một dòng mới YES nếu Anh ấy có thể vào hội chợ cùng với tất cả N người bạn của mình và NO nếu không thể.
CONSTRAINTS
- 1 ≤ T ≤ 100
- 1 ≤ N, K ≤ 100
Ví dụ
Input
4
5 8
6 3
2 2
1 2
Output
YES
NO
NO
YES
Giải thích:
Test 1: Anh ấy cần 5 vé cho bạn bè và một vé cho mình, và anh ấy đã thu thập được 8 vé. Vì vậy, anh ấy sẽ có thể vào hội chợ cùng với tất cả bạn bè.
Test 2: Anh ấy cần 6 vé cho bạn bè và một vé cho mình, trong khi anh ấy chỉ thu thập được 3 vé. Vì vậy, anh ấy sẽ không thể vào hội chợ cùng với tất cả bạn bè, chỉ có ba người có thể vào hội chợ.
Test 3: Anh ấy cần 2 vé cho bạn bè và một vé cho mình, trong khi anh ấy chỉ thu thập được 2 vé. Vì vậy, hoặc Anh ấy hoặc một trong những người bạn của anh ấy không thể vào hội chợ.
Test 4: Anh ấy cần tổng cộng 2 vé, một cho mình và một cho bạn. Anh ấy đã thu thập được 2 vé. Vì vậy, anh ấy sẽ có thể vào hội chợ cùng với người bạn của mình. Chào bạn, đây là hướng dẫn giải bài "Vé vào hội chợ" bằng C++ một cách ngắn gọn, tập trung vào logic và sử dụng các thư viện chuẩn (std).
Bài toán yêu cầu xác định xem với số vé K có đủ cho "Anh ấy" và toàn bộ N người bạn của mình cùng vào hội chợ hay không.
Phân tích bài toán:
- Số người cần vé: Anh ấy là 1 người, và có N người bạn. Tổng cộng có
1 + Nngười cần vé. - Điều kiện đủ vé: Để tất cả mọi người cùng vào được, số vé Anh ấy có (K) phải lớn hơn hoặc bằng tổng số người cần vé.
- Kết quả:
- Nếu K đủ (tức là
K >= 1 + N), in ra "YES". - Nếu K không đủ (tức là
K < 1 + N), in ra "NO".
- Nếu K đủ (tức là
Hướng dẫn giải chi tiết:
- Bạn cần đọc số lượng bộ test
T. - Sử dụng một vòng lặp để xử lý
Tbộ test. - Bên trong vòng lặp:
- Đọc hai số nguyên
NvàKcho bộ test hiện tại. - Tính tổng số người cần vé:
tong_nguoi = N + 1. - So sánh số vé Anh ấy có (
K) với tổng số người cần vé (tong_nguoi). - Nếu
K >= tong_nguoi, in ra "YES". - Ngược lại (nếu
K < tong_nguoi), in ra "NO". - Đảm bảo sau mỗi kết quả "YES" hoặc "NO", xuống dòng (
endl).
- Đọc hai số nguyên
Các bước thực hiện trong code C++:
- Bao gồm header cần thiết cho nhập xuất, ví dụ:
<iostream>. - Sử dụng
int main() { ... }. - Khai báo biến
Tvà đọc giá trị của nó. - Sử dụng vòng lặp
while (T--)hoặcfor (int i = 0; i < T; ++i)để lặpTlần. - Bên trong vòng lặp, khai báo biến
NvàK, đọc giá trị của chúng. - Sử dụng câu lệnh điều kiện
if-elseđể so sánhKvớiN + 1. - Sử dụng
coutđể in ra "YES" hoặc "NO" vàendlđể xuống dòng.
Lưu ý:
- Sử dụng các kiểu dữ liệu số nguyên phù hợp cho
NvàK(ví dụ:int). - Không cần sử dụng các cấu trúc dữ liệu phức tạp, chỉ cần đọc, tính toán đơn giản và so sánh.
- Ưu tiên sử dụng các hàm và đối tượng từ namespace
std(cin,cout,endl).
#include <iostream>
int main() {
int t;
cin >> t;
while (t--) {
int n, k;
cin >> n >> k;
if (k >= n + 1) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
}
return 0;
}
Output:
YES
NO
NO
YES
Comments