1. 二叉树基础概念与节点定义
二叉树图示
text
A
/ \
B C
/ \ \
D E F
节点定义代码
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 二叉树节点定义
typedef struct TreeNode {
int data; // 节点数据
struct TreeNode *left; // 左子树
struct TreeNode *right; // 右子树
} TreeNode;
// 队列节点定义(用于层序遍历)
typedef struct QueueNode {
TreeNode *treeNode; // 存储的树节点
struct QueueNode *next; // 下一个队列节点
} QueueNode;
// 队列定义
typedef struct {
QueueNode *front; // 队头
QueueNode *rear; // 队尾
} Queue;
// 栈节点定义(用于非递归遍历)
typedef struct StackNode {
TreeNode *treeNode; // 存储的树节点
struct StackNode *next; // 下一个栈节点
} StackNode;
// 栈定义
typedef struct {
StackNode *top; // 栈顶
} Stack;
2. 完全二叉树的链表构建
完全二叉树图示
text
1
/ \
2 3
/ \ / \
4 5 6 7
构建方法
完全二叉树可以使用层序构建法,按照从上到下、从左到右的顺序构建节点。
构建代码
c
// 创建新节点
TreeNode* createNode(int data) {
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
// 队列操作函数
Queue* createQueue() {
Queue* queue = (Queue*)malloc(sizeof(Queue));
queue->front = queue->rear = NULL;
return queue;
}
void enqueue(Queue* queue, TreeNode* treeNode) {
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
newNode->treeNode = treeNode;
newNode->next = NULL;
if (queue->rear == NULL) {
queue->front = queue->rear = newNode;
} else {
queue->rear->next = newNode;
queue->rear = newNode;
}
}
TreeNode* dequeue(Queue* queue) {
if (queue->front == NULL) return NULL;
QueueNode* temp = queue->front;
TreeNode* treeNode = temp->treeNode;
queue->front = queue->front->next;
if (queue->front == NULL) {
queue->rear = NULL;
}
free(temp);
return treeNode;
}
bool isQueueEmpty(Queue* queue) {
return queue->front == NULL;
}
// 构建完全二叉树
TreeNode* buildCompleteBinaryTree(int nodes[], int n) {
if (n == 0) return NULL;
// 创建根节点
TreeNode* root = createNode(nodes[0]);
// 使用队列辅助构建
Queue* queue = createQueue();
enqueue(queue, root);
int i = 1;
while (i < n) {
TreeNode* parent = dequeue(queue);
// 创建左孩子
if (i < n) {
parent->left = createNode(nodes[i]);
enqueue(queue, parent->left);
i++;
}
// 创建右孩子
if (i < n) {
parent->right = createNode(nodes[i]);
enqueue(queue, parent->right);
i++;
}
}
free(queue);
return root;
}
// 测试完全二叉树构建
void testCompleteBinaryTree() {
int nodes[] = {1, 2, 3, 4, 5, 6, 7};
int n = sizeof(nodes) / sizeof(nodes[0]);
printf("构建完全二叉树,节点序列: ");
for (int i = 0; i < n; i++) {
printf("%d ", nodes[i]);
}
printf("\n");
TreeNode* root = buildCompleteBinaryTree(nodes, n);
printf("完全二叉树构建完成!\n");
// 清理内存(实际使用时需要添加释放函数)
// freeTree(root);
}
3. 二叉树的递归遍历
遍历图示
text
A
/ \
B C
前序: A B C (根左右)
中序: B A C (左根右)
后序: B C A (左右根)
递归遍历代码
c
// 前序遍历(根-左-右)
void preorderTraversal(TreeNode* root) {
if (root == NULL) return;
printf("%d ", root->data); // 访问根节点
preorderTraversal(root->left); // 遍历左子树
preorderTraversal(root->right); // 遍历右子树
}
// 中序遍历(左-根-右)
void inorderTraversal(TreeNode* root) {
if (root == NULL) return;
inorderTraversal(root->left); // 遍历左子树
printf("%d ", root->data); // 访问根节点
inorderTraversal(root->right); // 遍历右子树
}
// 后序遍历(左-右-根)
void postorderTraversal(TreeNode* root) {
if (root == NULL) return;
postorderTraversal(root->left); // 遍历左子树
postorderTraversal(root->right); // 遍历右子树
printf("%d ", root->data); // 访问根节点
}
// 测试递归遍历
void testRecursiveTraversal() {
printf("\n=== 递归遍历测试 ===\n");
// 构建一个简单的二叉树
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);
root->right->right = createNode(7);
printf("前序遍历: ");
preorderTraversal(root);
printf("\n");
printf("中序遍历: ");
inorderTraversal(root);
printf("\n");
printf("后序遍历: ");
postorderTraversal(root);
printf("\n");
}
4. 二叉树的层序遍历
层序遍历图示
text
1 ← 第1层
/ \
2 3 ← 第2层
/ \ / \
4 5 6 7 ← 第3层
层序遍历结果: 1 2 3 4 5 6 7
层序遍历代码
c
// 层序遍历(广度优先遍历)
void levelOrderTraversal(TreeNode* root) {
if (root == NULL) return;
Queue* queue = createQueue();
enqueue(queue, root);
printf("层序遍历: ");
while (!isQueueEmpty(queue)) {
TreeNode* current = dequeue(queue);
printf("%d ", current->data);
// 将左右孩子入队
if (current->left != NULL) {
enqueue(queue, current->left);
}
if (current->right != NULL) {
enqueue(queue, current->right);
}
}
printf("\n");
free(queue);
}
// 测试层序遍历
void testLevelOrderTraversal() {
printf("\n=== 层序遍历测试 ===\n");
// 构建一个二叉树
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);
root->right->right = createNode(7);
levelOrderTraversal(root);
}
5. 二叉树的非递归遍历(顺序栈)
栈操作函数
c
// 栈操作函数
Stack* createStack() {
Stack* stack = (Stack*)malloc(sizeof(Stack));
stack->top = NULL;
return stack;
}
void push(Stack* stack, TreeNode* treeNode) {
StackNode* newNode = (StackNode*)malloc(sizeof(StackNode));
newNode->treeNode = treeNode;
newNode->next = stack->top;
stack->top = newNode;
}
TreeNode* pop(Stack* stack) {
if (stack->top == NULL) return NULL;
StackNode* temp = stack->top;
TreeNode* treeNode = temp->treeNode;
stack->top = stack->top->next;
free(temp);
return treeNode;
}
TreeNode* peek(Stack* stack) {
if (stack->top == NULL) return NULL;
return stack->top->treeNode;
}
bool isStackEmpty(Stack* stack) {
return stack->top == NULL;
}
// 非递归前序遍历
void preorderIterative(TreeNode* root) {
if (root == NULL) return;
Stack* stack = createStack();
push(stack, root);
printf("非递归前序遍历: ");
while (!isStackEmpty(stack)) {
TreeNode* current = pop(stack);
printf("%d ", current->data);
// 注意:右孩子先入栈,左孩子后入栈
// 这样左孩子会先出栈,符合根-左-右的顺序
if (current->right != NULL) {
push(stack, current->right);
}
if (current->left != NULL) {
push(stack, current->left);
}
}
printf("\n");
free(stack);
}
// 非递归中序遍历
void inorderIterative(TreeNode* root) {
if (root == NULL) return;
Stack* stack = createStack();
TreeNode* current = root;
printf("非递归中序遍历: ");
while (current != NULL || !isStackEmpty(stack)) {
// 将左子树所有节点入栈
while (current != NULL) {
push(stack, current);
current = current->left;
}
// 弹出栈顶节点并访问
current = pop(stack);
printf("%d ", current->data);
// 处理右子树
current = current->right;
}
printf("\n");
free(stack);
}
// 非递归后序遍历(使用两个栈)
void postorderIterative(TreeNode* root) {
if (root == NULL) return;
Stack* stack1 = createStack();
Stack* stack2 = createStack();
push(stack1, root);
printf("非递归后序遍历: ");
while (!isStackEmpty(stack1)) {
TreeNode* current = pop(stack1);
push(stack2, current);
if (current->left != NULL) {
push(stack1, current->left);
}
if (current->right != NULL) {
push(stack1, current->right);
}
}
// stack2中存储的是后序遍历的逆序
while (!isStackEmpty(stack2)) {
TreeNode* current = pop(stack2);
printf("%d ", current->data);
}
printf("\n");
free(stack1);
free(stack2);
}
// 测试非递归遍历
void testIterativeTraversal() {
printf("\n=== 非递归遍历测试 ===\n");
// 构建一个二叉树
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);
root->right->right = createNode(7);
preorderIterative(root);
inorderIterative(root);
postorderIterative(root);
}
6. 树的高度/层数统计
高度计算图示
text
A ← 高度3
/ \
B C ← 高度2
\ \
D E ← 高度1
空树高度为0,只有根节点高度为1
高度统计代码
c
// 方法1:递归计算树的高度
int treeHeightRecursive(TreeNode* root) {
if (root == NULL) return 0;
int leftHeight = treeHeightRecursive(root->left);
int rightHeight = treeHeightRecursive(root->right);
return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}
// 方法2:层序遍历计算树的高度
int treeHeightLevelOrder(TreeNode* root) {
if (root == NULL) return 0;
Queue* queue = createQueue();
enqueue(queue, root);
int height = 0;
while (!isQueueEmpty(queue)) {
height++;
int levelSize = 0;
// 计算当前层的节点数
QueueNode* temp = queue->front;
while (temp != NULL) {
levelSize++;
temp = temp->next;
}
// 处理当前层的所有节点
for (int i = 0; i < levelSize; i++) {
TreeNode* current = dequeue(queue);
if (current->left != NULL) {
enqueue(queue, current->left);
}
if (current->right != NULL) {
enqueue(queue, current->right);
}
}
}
free(queue);
return height;
}
// 统计每层节点数
void countNodesByLevel(TreeNode* root) {
if (root == NULL) return;
Queue* queue = createQueue();
enqueue(queue, root);
int level = 1;
printf("\n=== 每层节点数统计 ===\n");
while (!isQueueEmpty(queue)) {
int levelSize = 0;
// 计算当前层的节点数
QueueNode* temp = queue->front;
while (temp != NULL) {
levelSize++;
temp = temp->next;
}
printf("第%d层: %d个节点\n", level, levelSize);
// 处理当前层的所有节点
for (int i = 0; i < levelSize; i++) {
TreeNode* current = dequeue(queue);
if (current->left != NULL) {
enqueue(queue, current->left);
}
if (current->right != NULL) {
enqueue(queue, current->right);
}
}
level++;
}
free(queue);
}
// 测试高度统计
void testTreeHeight() {
printf("\n=== 树高度统计测试 ===\n");
// 构建一个二叉树
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);
root->right->right = createNode(7);
root->left->left->left = createNode(8); // 添加第4层
printf("递归计算树高度: %d\n", treeHeightRecursive(root));
printf("层序计算树高度: %d\n", treeHeightLevelOrder(root));
countNodesByLevel(root);
}
7. 非完全二叉树的构建
非完全二叉树图示
text
1
/ \
2 3
/ / \
4 5 6
\ /
7 8
非完全二叉树:节点不一定连续集中在最左边
非完全二叉树构建代码
c
// 交互式构建非完全二叉树
TreeNode* buildNonCompleteBinaryTree() {
int data;
char choice;
printf("输入节点数据(-1表示空节点): ");
scanf("%d", &data);
if (data == -1) {
return NULL;
}
TreeNode* newNode = createNode(data);
printf("为节点%d创建左子树吗? (y/n): ", data);
scanf(" %c", &choice);
if (choice == 'y' || choice == 'Y') {
newNode->left = buildNonCompleteBinaryTree();
}
printf("为节点%d创建右子树吗? (y/n): ", data);
scanf(" %c", &choice);
if (choice == 'y' || choice == 'Y') {
newNode->right = buildNonCompleteBinaryTree();
}
return newNode;
}
// 根据数组构建非完全二叉树(-1表示空节点)
TreeNode* buildNonCompleteBinaryTreeFromArray(int nodes[], int index, int n) {
if (index >= n || nodes[index] == -1) {
return NULL;
}
TreeNode* root = createNode(nodes[index]);
root->left = buildNonCompleteBinaryTreeFromArray(nodes, 2*index + 1, n);
root->right = buildNonCompleteBinaryTreeFromArray(nodes, 2*index + 2, n);
return root;
}
// 测试非完全二叉树构建
void testNonCompleteBinaryTree() {
printf("\n=== 非完全二叉树测试 ===\n");
// 方法1:交互式构建
// TreeNode* root = buildNonCompleteBinaryTree();
// 方法2:通过数组构建(-1表示空节点)
int nodes[] = {1, 2, 3, 4, -1, 5, 6, -1, 7, -1, -1, 8};
int n = sizeof(nodes) / sizeof(nodes[0]);
printf("构建非完全二叉树,节点序列: ");
for (int i = 0; i < n; i++) {
printf("%d ", nodes[i]);
}
printf("\n");
TreeNode* root = buildNonCompleteBinaryTreeFromArray(nodes, 0, n);
printf("前序遍历: ");
preorderTraversal(root);
printf("\n");
printf("层序遍历: ");
levelOrderTraversal(root);
printf("树高度: %d\n", treeHeightRecursive(root));
}
8. 哈希表基础概念
哈希表示意图
text
哈希表结构:
索引 0: [键1→值1] → [键2→值2] → ...
索引 1: [键3→值3] → ...
...
索引 n-1: [键k→值k] → ...
哈希函数: key → hash(key) → 索引
哈希表定义
c
// 哈希表节点定义(链表法解决冲突)
typedef struct HashNode {
int key; // 键
int value; // 值
struct HashNode* next; // 下一个节点
} HashNode;
// 哈希表定义
typedef struct {
int size; // 哈希表大小
HashNode** table; // 哈希表数组
} HashTable;
9. 哈希表应用示例
应用场景:统计100以内随机数出现次数
text
输入: [5, 23, 5, 78, 23, 5, 91, 23]
输出:
5出现3次
23出现3次
78出现1次
91出现1次
哈希表实现代码
c
// 创建哈希表
HashTable* createHashTable(int size) {
HashTable* hashTable = (HashTable*)malloc(sizeof(HashTable));
hashTable->size = size;
hashTable->table = (HashNode**)malloc(size * sizeof(HashNode*));
// 初始化所有链表为空
for (int i = 0; i < size; i++) {
hashTable->table[i] = NULL;
}
return hashTable;
}
// 简单哈希函数:取余法
int hashFunction(int key, int tableSize) {
return key % tableSize;
}
// 插入键值对
void insert(HashTable* hashTable, int key, int value) {
int index = hashFunction(key, hashTable->size);
// 创建新节点
HashNode* newNode = (HashNode*)malloc(sizeof(HashNode));
newNode->key = key;
newNode->value = value;
newNode->next = NULL;
// 如果该位置为空,直接插入
if (hashTable->table[index] == NULL) {
hashTable->table[index] = newNode;
} else {
// 否则插入到链表头部
newNode->next = hashTable->table[index];
hashTable->table[index] = newNode;
}
}
// 查找键对应的值
int search(HashTable* hashTable, int key) {
int index = hashFunction(key, hashTable->size);
HashNode* current = hashTable->table[index];
while (current != NULL) {
if (current->key == key) {
return current->value;
}
current = current->next;
}
return -1; // 未找到
}
// 更新键的值(如果存在则更新,不存在则插入)
void update(HashTable* hashTable, int key, int value) {
int index = hashFunction(key, hashTable->size);
HashNode* current = hashTable->table[index];
// 查找是否已存在该键
while (current != NULL) {
if (current->key == key) {
current->value = value;
return;
}
current = current->next;
}
// 不存在则插入
insert(hashTable, key, value);
}
// 统计100以内随机数出现次数
void countRandomNumbers() {
printf("\n=== 哈希表应用:统计100以内随机数 ===\n");
// 创建哈希表,大小为10(用于100以内的数字)
HashTable* hashTable = createHashTable(10);
// 生成随机数数组
int randomNumbers[] = {5, 23, 5, 78, 23, 5, 91, 23, 12, 45, 78, 5, 12};
int count = sizeof(randomNumbers) / sizeof(randomNumbers[0]);
printf("随机数序列: ");
for (int i = 0; i < count; i++) {
printf("%d ", randomNumbers[i]);
}
printf("\n\n");
// 统计每个数字出现次数
for (int i = 0; i < count; i++) {
int key = randomNumbers[i];
int currentValue = search(hashTable, key);
if (currentValue == -1) {
// 第一次出现
insert(hashTable, key, 1);
} else {
// 已存在,次数加1
update(hashTable, key, currentValue + 1);
}
}
// 显示统计结果
printf("统计结果:\n");
printf("数字\t出现次数\n");
printf("----\t--------\n");
// 遍历所有可能的键(0-99)
for (int key = 0; key < 100; key++) {
int value = search(hashTable, key);
if (value != -1) {
printf("%d\t%d次\n", key, value);
}
}
// 释放哈希表内存
for (int i = 0; i < hashTable->size; i++) {
HashNode* current = hashTable->table[i];
while (current != NULL) {
HashNode* temp = current;
current = current->next;
free(temp);
}
}
free(hashTable->table);
free(hashTable);
}
// 测试哈希表
void testHashTable() {
printf("\n=== 哈希表基本操作测试 ===\n");
HashTable* hashTable = createHashTable(7);
// 插入测试
insert(hashTable, 10, 100);
insert(hashTable, 20, 200);
insert(hashTable, 17, 170); // 17 % 7 = 3,与10冲突
// 查找测试
printf("查找键10: %d\n", search(hashTable, 10));
printf("查找键20: %d\n", search(hashTable, 20));
printf("查找键17: %d\n", search(hashTable, 17));
printf("查找键30: %d\n", search(hashTable, 30)); // 应返回-1
// 更新测试
update(hashTable, 10, 150);
printf("更新后查找键10: %d\n", search(hashTable, 10));
// 清理内存
for (int i = 0; i < hashTable->size; i++) {
HashNode* current = hashTable->table[i];
while (current != NULL) {
HashNode* temp = current;
current = current->next;
free(temp);
}
}
free(hashTable->table);
free(hashTable);
}
10. 补充内容
二叉树的其他重要操作
c
// 1. 查找节点
TreeNode* findNode(TreeNode* root, int target) {
if (root == NULL) return NULL;
if (root->data == target) return root;
TreeNode* leftResult = findNode(root->left, target);
if (leftResult != NULL) return leftResult;
return findNode(root->right, target);
}
// 2. 计算节点总数
int countNodes(TreeNode* root) {
if (root == NULL) return 0;
return 1 + countNodes(root->left) + countNodes(root->right);
}
// 3. 计算叶子节点数
int countLeafNodes(TreeNode* root) {
if (root == NULL) return 0;
if (root->left == NULL && root->right == NULL) return 1;
return countLeafNodes(root->left) + countLeafNodes(root->right);
}
// 4. 释放二叉树内存
void freeTree(TreeNode* root) {
if (root == NULL) return;
freeTree(root->left);
freeTree(root->right);
free(root);
}
// 5. 判断是否为完全二叉树
bool isCompleteBinaryTree(TreeNode* root) {
if (root == NULL) return true;
Queue* queue = createQueue();
enqueue(queue, root);
bool end = false; // 标记是否遇到了空节点
while (!isQueueEmpty(queue)) {
TreeNode* current = dequeue(queue);
if (current == NULL) {
end = true;
} else {
// 如果已经遇到过空节点,又遇到了非空节点,则不是完全二叉树
if (end) {
free(queue);
return false;
}
// 入队左右孩子(即使是NULL也入队)
enqueue(queue, current->left);
enqueue(queue, current->right);
}
}
free(queue);
return true;
}
哈希表的其他重要概念
-
哈希冲突解决方法:
- 链地址法(如上例)
-
常用哈希函数:
-
除留余数法:
h(key) = key % p -
直接定址法:
h(key) = a * key + b -
数字分析法:取关键字的某几位
-
平方取中法:取关键字平方的中间几位
-
综合测试函数
c
// 综合测试所有功能
void comprehensiveTest() {
printf("====== 数据结构与算法综合测试 ======\n\n");
// 1. 测试完全二叉树
testCompleteBinaryTree();
// 2. 测试递归遍历
testRecursiveTraversal();
// 3. 测试层序遍历
testLevelOrderTraversal();
// 4. 测试非递归遍历
testIterativeTraversal();
// 5. 测试树高度统计
testTreeHeight();
// 6. 测试非完全二叉树
testNonCompleteBinaryTree();
// 7. 测试哈希表基本操作
testHashTable();
// 8. 测试哈希表应用
countRandomNumbers();
printf("\n====== 测试完成 ======\n");
}
// 主函数
int main() {
comprehensiveTest();
return 0;
}
总结
今天学习的内容涵盖了二叉树和哈希表两大核心数据结构:
二叉树部分
-
构建方法:学会了完全二叉树和非完全二叉树的链表构建
-
遍历算法:
-
递归遍历(前序、中序、后序)
-
非递归遍历(使用栈)
-
层序遍历(使用队列)
-
-
统计功能:树的高度、每层节点数、节点总数等
哈希表部分
-
基本概念:哈希冲突
-
实现方法:使用数组+链表解决冲突
-
实际应用:统计随机数出现频率
学习建议
-
理解递归思想:二叉树很多操作都基于递归,理解递归调用栈很重要
-
掌握遍历顺序:三种遍历方式的区别和应用场景
-
动手实现:自己实现一遍代码,加深理解
-
分析时间复杂度:
-
二叉树遍历:O(n)
-
哈希表操作:平均O(1),最坏O(n)
-