今天我想分享一个有趣的树结构遍历实现方案——使用链表实现的层序遍历,完全不依赖数组和递归。这种实现方式在内存受限或需要避免递归栈溢出的场景下特别有用。
树结构是计算机科学中最基础的数据结构之一,广泛应用于文件系统、DOM树、游戏场景管理等场景。传统的层序遍历通常使用队列(基于数组)或递归实现,但前者可能受限于数组大小,后者则有栈溢出的风险。本文展示的方案通过链表模拟队列,既避免了数组大小限制,又规避了递归的潜在问题。
我们采用"第一个孩子+下一个兄弟"的表示法(First Child Next Sibling Representation),这种结构特别适合表示多叉树:
c复制struct TreeNode {
int data;
struct TreeNode *first_child;
struct TreeNode *next_sibling;
};
这种表示法的优势在于:
为了实现不使用数组的队列,我们定义了链表节点:
c复制struct ListNode {
struct TreeNode* tree_node;
struct ListNode* next;
};
每个链表节点包含一个指向树节点的指针和指向下一个链表节点的指针,这样就形成了一个简单的单向链表,可以模拟队列的先进先出特性。
链表队列需要支持两个基本操作:入队和出队。
入队操作(在尾部添加节点):
c复制struct ListNode* new_node = create_list_node(child);
if (head == NULL) {
head = tail = new_node;
} else {
tail->next = new_node;
tail = new_node;
}
出队操作(从头部移除节点):
c复制struct TreeNode* current = head->tree_node;
struct ListNode* temp = head;
head = head->next;
free(temp);
这种实现方式的时间复杂度:
完整的层序遍历算法流程如下:
具体实现:
c复制void traverse_all_no_stack(struct TreeNode* root) {
if (root == NULL) return;
struct ListNode* head = create_list_node(root);
struct ListNode* tail = head;
while (head != NULL) {
struct TreeNode* current = head->tree_node;
printf("%d ", current->data);
struct ListNode* temp = head;
head = head->next;
free(temp);
struct TreeNode* child = current->first_child;
while (child != NULL) {
struct ListNode* new_node = create_list_node(child);
if (head == NULL) {
head = tail = new_node;
} else {
tail->next = new_node;
tail = new_node;
}
child = child->next_sibling;
}
}
}
该算法的时间复杂度为O(n),其中n是树中节点的数量。这是因为:
空间复杂度也是O(n),在最坏情况下(当树退化为链表时),队列中最多会存储n个节点。
我们构建了一个测试用的多叉树:
code复制 1
/ | \
2 3 4
/| | |\
5 6 7 8 9
构建代码:
c复制struct TreeNode* root = create_node(1);
struct TreeNode* node2 = create_node(2);
struct TreeNode* node3 = create_node(3);
struct TreeNode* node4 = create_node(4);
struct TreeNode* node5 = create_node(5);
struct TreeNode* node6 = create_node(6);
struct TreeNode* node7 = create_node(7);
struct TreeNode* node8 = create_node(8);
struct TreeNode* node9 = create_node(9);
root->first_child = node2;
node2->next_sibling = node3;
node3->next_sibling = node4;
node2->first_child = node5;
node5->next_sibling = node6;
node3->first_child = node7;
node4->first_child = node8;
node8->next_sibling = node9;
执行层序遍历后,输出应为:
code复制1 2 3 4 5 6 7 8 9
这验证了我们的算法能够正确地进行广度优先的层序遍历。
在链表队列的实现中,容易忘记释放出队节点的内存。确保每次出队时都释放对应的链表节点:
c复制struct ListNode* temp = head;
head = head->next;
free(temp); // 必须释放内存
在访问树节点前,必须检查指针是否为NULL:
c复制if (root == NULL) return; // 空树直接返回
在处理树结构和链表结构时,多级指针操作容易出错。建议:
这种链表实现的层序遍历可以应用于:
在需要控制内存使用或避免递归的场景下,这种实现方式特别有价值。例如在嵌入式系统中,内存资源有限,递归深度不可控,这种非递归的链表实现就显示出其优势。