层序遍历(Level Order Traversal)是二叉树遍历中最直观的一种方式,它按照树的层级结构从上到下、从左到右依次访问每个节点。这种遍历方式在实际开发中有着广泛的应用场景,比如社交网络的好友推荐、文件系统的目录遍历等。
深度优先遍历(DFS)和广度优先遍历(BFS)是树遍历的两大基本策略:
实际选择时:当需要处理"最近关系"(如最短路径)用BFS;当需要完全探索分支(如查找所有可能解)用DFS。
层序遍历的标准实现需要借助队列数据结构:
这个过程的时空复杂度都是O(n),因为每个节点恰好入队出队一次。
层序遍历依赖队列的FIFO特性,我们先实现一个通用队列:
c复制// queue.h
#pragma once
#include <stdbool.h>
typedef struct TreeNode* QDataType; // 队列存储树节点指针
typedef struct QueueNode {
QDataType data;
struct QueueNode* next;
} QueueNode;
typedef struct Queue {
QueueNode* front;
QueueNode* rear;
int size;
} Queue;
void QueueInit(Queue* q);
void QueueDestroy(Queue* q);
void QueuePush(Queue* q, QDataType data);
void QueuePop(Queue* q);
QDataType QueueFront(Queue* q);
bool QueueEmpty(Queue* q);
int QueueSize(Queue* q);
对应的实现文件:
c复制// queue.c
#include "queue.h"
#include <stdlib.h>
#include <assert.h>
void QueueInit(Queue* q) {
assert(q);
q->front = q->rear = NULL;
q->size = 0;
}
void QueueDestroy(Queue* q) {
assert(q);
QueueNode* cur = q->front;
while (cur) {
QueueNode* next = cur->next;
free(cur);
cur = next;
}
q->front = q->rear = NULL;
q->size = 0;
}
void QueuePush(Queue* q, QDataType data) {
assert(q);
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
if (!newNode) {
perror("malloc failed");
exit(EXIT_FAILURE);
}
newNode->data = data;
newNode->next = NULL;
if (q->rear == NULL) {
q->front = q->rear = newNode;
} else {
q->rear->next = newNode;
q->rear = newNode;
}
q->size++;
}
void QueuePop(Queue* q) {
assert(q);
assert(!QueueEmpty(q));
QueueNode* next = q->front->next;
free(q->front);
q->front = next;
if (q->front == NULL) {
q->rear = NULL;
}
q->size--;
}
QDataType QueueFront(Queue* q) {
assert(q);
assert(!QueueEmpty(q));
return q->front->data;
}
bool QueueEmpty(Queue* q) {
assert(q);
return q->size == 0;
}
int QueueSize(Queue* q) {
assert(q);
return q->size;
}
c复制// tree.h
#pragma once
typedef int TreeDataType;
typedef struct TreeNode {
TreeDataType val;
struct TreeNode* left;
struct TreeNode* right;
} TreeNode;
TreeNode* CreateNode(TreeDataType x);
void LevelOrder(TreeNode* root);
bool IsCompleteTree(TreeNode* root);
c复制// tree.c
#include "tree.h"
#include "queue.h"
#include <stdio.h>
#include <stdlib.h>
TreeNode* CreateNode(TreeDataType x) {
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
if (!newNode) {
perror("malloc failed");
exit(EXIT_FAILURE);
}
newNode->val = x;
newNode->left = newNode->right = NULL;
return newNode;
}
void LevelOrder(TreeNode* root) {
if (!root) return;
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q)) {
TreeNode* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->val);
if (front->left) {
QueuePush(&q, front->left);
}
if (front->right) {
QueuePush(&q, front->right);
}
}
QueueDestroy(&q);
}
完全二叉树的定义是:除了最后一层,其他层节点都达到最大数,且最后一层节点都靠左排列。
c复制bool IsCompleteTree(TreeNode* root) {
if (!root) return true;
Queue q;
QueueInit(&q);
QueuePush(&q, root);
bool reachNull = false;
while (!QueueEmpty(&q)) {
TreeNode* front = QueueFront(&q);
QueuePop(&q);
if (!front) {
reachNull = true;
} else {
if (reachNull) {
QueueDestroy(&q);
return false;
}
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
}
QueueDestroy(&q);
return true;
}
有时我们需要知道每个节点属于哪一层,可以通过记录队列大小来实现:
c复制void LevelOrderWithLevel(TreeNode* root) {
if (!root) return;
Queue q;
QueueInit(&q);
QueuePush(&q, root);
int level = 1;
while (!QueueEmpty(&q)) {
int levelSize = QueueSize(&q);
printf("Level %d: ", level++);
for (int i = 0; i < levelSize; i++) {
TreeNode* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->val);
if (front->left) QueuePush(&q, front->left);
if (front->right) QueuePush(&q, front->right);
}
printf("\n");
}
QueueDestroy(&q);
}
即奇数层从左到右,偶数层从右到左打印:
c复制void ZigzagLevelOrder(TreeNode* root) {
if (!root) return;
Queue q;
QueueInit(&q);
QueuePush(&q, root);
bool leftToRight = true;
while (!QueueEmpty(&q)) {
int levelSize = QueueSize(&q);
int* levelNodes = (int*)malloc(levelSize * sizeof(int));
for (int i = 0; i < levelSize; i++) {
TreeNode* front = QueueFront(&q);
QueuePop(&q);
int index = leftToRight ? i : levelSize - 1 - i;
levelNodes[index] = front->val;
if (front->left) QueuePush(&q, front->left);
if (front->right) QueuePush(&q, front->right);
}
for (int i = 0; i < levelSize; i++) {
printf("%d ", levelNodes[i]);
}
printf("\n");
free(levelNodes);
leftToRight = !leftToRight;
}
QueueDestroy(&q);
}
c复制int MaxWidth(TreeNode* root) {
if (!root) return 0;
Queue q;
QueueInit(&q);
QueuePush(&q, root);
int maxWidth = 1;
while (!QueueEmpty(&q)) {
int levelSize = QueueSize(&q);
maxWidth = levelSize > maxWidth ? levelSize : maxWidth;
for (int i = 0; i < levelSize; i++) {
TreeNode* front = QueueFront(&q);
QueuePop(&q);
if (front->left) QueuePush(&q, front->left);
if (front->right) QueuePush(&q, front->right);
}
}
QueueDestroy(&q);
return maxWidth;
}
在使用队列实现层序遍历时,最容易出现的问题是内存泄漏。确保:
可以使用valgrind工具检查内存泄漏:
bash复制valgrind --leak-check=full ./your_program
为了方便测试,可以编写一个辅助函数构建二叉树:
c复制TreeNode* BuildTestTree() {
TreeNode* root = CreateNode(1);
root->left = CreateNode(2);
root->right = CreateNode(3);
root->left->left = CreateNode(4);
root->left->right = CreateNode(5);
root->right->left = CreateNode(6);
return root;
}
这些问题的解法都基于层序遍历,通过调整访问顺序和记录额外信息来实现。
虽然队列实现是标准解法,但也可以使用递归:
c复制void LevelOrderRecursive(TreeNode* root, int level) {
if (!root) return;
if (level == 1) {
printf("%d ", root->val);
} else {
LevelOrderRecursive(root->left, level-1);
LevelOrderRecursive(root->right, level-1);
}
}
void LevelOrderWrapper(TreeNode* root) {
int height = TreeHeight(root);
for (int i = 1; i <= height; i++) {
LevelOrderRecursive(root, i);
}
}
不过递归实现的时间复杂度是O(n²),不如队列实现的O(n)高效。
在某些场景下,使用两个队列可以避免频繁计算队列大小:
c复制void LevelOrderTwoQueues(TreeNode* root) {
if (!root) return;
Queue current, next;
QueueInit(¤t);
QueueInit(&next);
QueuePush(¤t, root);
while (!QueueEmpty(¤t)) {
TreeNode* front = QueueFront(¤t);
QueuePop(¤t);
printf("%d ", front->val);
if (front->left) QueuePush(&next, front->left);
if (front->right) QueuePush(&next, front->right);
if (QueueEmpty(¤t)) {
printf("\n");
Queue temp = current;
current = next;
next = temp;
}
}
QueueDestroy(¤t);
QueueDestroy(&next);
}
这些问题的解法核心都是层序遍历的变种,理解基础实现后可以灵活应用。