Cấu trúc Tự tham chiếu trong C

Xin chào các nhà lập trình tương lai! Hôm nay, chúng ta sẽ bắt đầu một chuyến hành trình đầy thú vị vào thế giới của các cấu trúc tự tham chiếu trong C. Đừng lo lắng nếu điều đó听起来 có vẻ đáng sợ - tôi承诺 chúng ta sẽ chia nhỏ thành những phần nhỏ dễ hiểu, ngay cả bà nội của tôi cũng có thể hiểu (và tin tôi đi, bà vẫn nghĩ rằng chuột chỉ là một con động vật nhỏ có lông).

C - Self-Referential Structures

Cấu trúc Tự tham chiếu là gì?

Hãy tưởng tượng bạn đang dự tiệc, và bạn muốn giới thiệu bạn của mình cho ai đó. Bạn có thể nói, "Đây là bạn của tôi, Alex, và Alex cũng là một nhà phát triển phần mềm." Trong lời giới thiệu này, bạn đang đề cập đến Alex hai lần - một lần bằng tên và một lần bằng nghề nghiệp. Đây là cách hoạt động tương tự như các cấu trúc tự tham chiếu trong C!

Một cấu trúc tự tham chiếu là một cấu trúc chứa một con trỏ đến một cấu trúc cùng loại trong định nghĩa của nó. Nó giống như một con búp bê Nga, nhưng thay vì các búp bê nhỏ hơn bên trong, nó là cùng một loại cấu trúc từ trên xuống.

Định nghĩa một Cấu trúc Tự tham chiếu

Hãy cùng xem cách chúng ta thực sự định nghĩa những cấu trúc thú vị này. Dưới đây là một ví dụ đơn giản:

struct Node {
int data;
struct Node* next;
};

Trong ví dụ này, chúng ta đã định nghĩa một cấu trúc叫做 Node. Nó chứa hai thành viên:

  1. Một số nguyên data để lưu trữ thông tin.
  2. Một con trỏ next chỉ đến một cấu trúc Node khác.

Đây là bản chất của cấu trúc tự tham chiếu - nó tham chiếu đến chính nó trong định nghĩa của mình.

Ví dụ về Cấu trúc Tự tham chiếu

Bây giờ, hãy xem một số ví dụ thực tế chúng ta có thể sử dụng cấu trúc tự tham chiếu.

1. Node của Danh sách Liên kết

Chúng ta đã thấy điều này trước đó, nhưng hãy phân tích sâu hơn:

struct ListNode {
int data;
struct ListNode* next;
};

Cấu trúc này hoàn hảo để tạo danh sách liên kết. Mỗi node chứa một số dữ liệu và một con trỏ đến node tiếp theo trong danh sách.

2. Node của Cây

Cây là một trường hợp sử dụng phổ biến khác cho cấu trúc tự tham chiếu:

struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
};

Ở đây, mỗi node trong cây có thể có tối đa hai con (trái và phải), cho phép chúng ta tạo cây nhị phân.

3. Node của Đồ thị

Đối với những ai muốn thử thách, đây là cách chúng ta có thể đại diện cho một node trong đồ thị:

struct GraphNode {
int data;
struct GraphNode** neighbors;
int neighborCount;
};

Trong trường hợp này, chúng ta có một mảng các con trỏ đến các cấu trúc GraphNode khác, đại diện cho các kết nối trong đồ thị của chúng ta.

Tạo Danh sách Liên kết với Cấu trúc Tự tham chiếu

Bây giờ chúng ta đã hiểu các nguyên tắc cơ bản, hãy tạo một danh sách liên kết đơn giản sử dụng cấu trúc tự tham chiếu. Đừng lo lắng nếu bạn không hiểu mọi thứ ngay lập tức - Roma không được xây dựng trong một ngày, và neither is coding expertise!

#include <stdio.h>
#include <stdlib.h>

struct Node {
int data;
struct Node* next;
};

// Hàm để tạo một node mới
struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}

// Hàm để in danh sách liên kết
void printList(struct Node* head) {
struct Node* current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}

int main() {
struct Node* head = createNode(1);
head->next = createNode(2);
head->next->next = createNode(3);

printf("Danh sách liên kết của chúng ta: ");
printList(head);

return 0;
}

Hãy phân tích điều này:

  1. Chúng ta định nghĩa cấu trúc Node như trước.
  2. Chúng ta tạo một hàm createNode để cấp phát bộ nhớ cho một node mới và khởi tạo dữ liệu.
  3. Chúng ta có một hàm printList để duyệt qua danh sách và in dữ liệu của mỗi node.
  4. Trong main, chúng ta tạo một danh sách liên kết với ba node và in nó.

Khi bạn chạy chương trình này, bạn nên thấy:

Danh sách liên kết của chúng ta: 1 -> 2 -> 3 -> NULL

Tạo Danh sách Liên kết Đôi với Cấu trúc Tự tham chiếu

Bây giờ, hãy nâng cấp và tạo một danh sách liên kết đôi. Đây là như danh sách liên kết bình thường, nhưng mỗi node cũng có một con trỏ đến node trước đó. Nó giống như một con đường hai chiều thay vì một con đường một chiều!

#include <stdio.h>
#include <stdlib.h>

struct DoubleNode {
int data;
struct DoubleNode* next;
struct DoubleNode* prev;
};

// Hàm để tạo một node mới
struct DoubleNode* createDoubleNode(int data) {
struct DoubleNode* newNode = (struct DoubleNode*)malloc(sizeof(struct DoubleNode));
newNode->data = data;
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}

// Hàm để in danh sách liên kết theo hướng trước
void printForward(struct DoubleNode* head) {
struct DoubleNode* current = head;
printf("Trước: ");
while (current != NULL) {
printf("%d <-> ", current->data);
current = current->next;
}
printf("NULL\n");
}

// Hàm để in danh sách liên kết theo hướng sau
void printBackward(struct DoubleNode* tail) {
struct DoubleNode* current = tail;
printf("Sau: ");
while (current != NULL) {
printf("%d <-> ", current->data);
current = current->prev;
}
printf("NULL\n");
}

int main() {
struct DoubleNode* head = createDoubleNode(1);
head->next = createDoubleNode(2);
head->next->prev = head;
head->next->next = createDoubleNode(3);
head->next->next->prev = head->next;

struct DoubleNode* tail = head->next->next;

printForward(head);
printBackward(tail);

return 0;
}

Trong ví dụ này:

  1. Cấu trúc DoubleNode của chúng ta但现在 có cả nextprev con trỏ.
  2. Chúng ta tạo các hàm để in danh sách theo hướng trước và sau.
  3. Trong main, chúng ta tạo một danh sách liên kết đôi và đặt cả nextprev con trỏ.

Khi chạy chương trình này, bạn nên thấy:

Trước: 1 <-> 2 <-> 3 <-> NULL
Sau: 3 <-> 2 <-> 1 <-> NULL

Tạo Cây với Cấu trúc Tự tham chiếu

Cuối cùng, hãy tạo một cây nhị phân đơn giản sử dụng cấu trúc tự tham chiếu. Hãy tưởng tượng này như một gia đình, nơi mỗi người có thể có tối đa hai đứa con.

#include <stdio.h>
#include <stdlib.h>

struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
};

// Hàm để tạo một node mới
struct TreeNode* createTreeNode(int data) {
struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

// Hàm để thực hiện duyệt in-order
void inOrderTraversal(struct TreeNode* root) {
if (root != NULL) {
inOrderTraversal(root->left);
printf("%d ", root->data);
inOrderTraversal(root->right);
}
}

int main() {
struct TreeNode* root = createTreeNode(1);
root->left = createTreeNode(2);
root->right = createTreeNode(3);
root->left->left = createTreeNode(4);
root->left->right = createTreeNode(5);

printf("Duyệt in-order của cây: ");
inOrderTraversal(root);
printf("\n");

return 0;
}

Trong ví dụ cuối cùng này:

  1. Cấu trúc TreeNode của chúng ta có leftright con trỏ cho các node con.
  2. Chúng ta tạo một hàm để thực hiện duyệt in-order, nó thăm subtree bên trái, sau đó gốc, và cuối cùng là subtree bên phải.
  3. Trong main, chúng ta tạo một cây nhị phân đơn giản và thực hiện duyệt in-order.

Khi chạy chương trình này, bạn nên thấy:

Duyệt in-order của cây: 4 2 5 1 3

Và thế là xong, các bạn! Chúng ta đã cùng nhau hành trình qua thế giới của các cấu trúc tự tham chiếu, từ danh sách liên kết đơn giản đến danh sách liên kết đôi và thậm chí là cây nhị phân. Nhớ rằng, thực hành là cách tốt nhất để trở nên thành thạo, vì vậy đừng ngần ngại thử nghiệm với các cấu trúc này và tạo ra các biến thể riêng của bạn. Ai biết được? Bạn có thể là người tiếp theo phát minh ra một cấu trúc dữ liệu đột phá!

Chúc các bạn may mắn và may mắn với các cấu trúc tự tham chiếu!

Credits: Image by storyset