数据结构与算法:队列与树形结构详细总结

一、队列(Queue)详解

1. 队列基本概念

定义:

队列是一种**先进先出(FIFO)**的线性数据结构,就像现实生活中的排队一样。

图示:

text

复制代码
入队方向 → → → → → → → 
        ┌───┬───┬───┬───┬───┐
队列:  │ A │ B │ C │ D │ E │
        └───┴───┴───┴───┴───┘
出队方向 ← ← ← ← ← ← ← 
   ↑               ↑
 front           rear
 队头            队尾

2. 队列的组成要素

  • 队头(front):允许删除的一端

  • 队尾(rear):允许插入的一端

  • 出队(dequeue):从队头删除元素

  • 入队(enqueue):在队尾插入元素

3. 顺序循环队列

3.1 为什么需要循环队列?

普通顺序队列的问题

text

复制代码
初始状态:front=0, rear=0
入队A,B,C:front=0, rear=3
出队A,B:front=2, rear=3
    ┌───┬───┬───┬───┐
    │   │   │ C │   │
    └───┴───┴───┴───┘
          ↑       ↑
        front   rear
虽然前面有空间,但rear已到末尾,无法再入队→"假溢出"
3.2 循环队列的解决方案

通过取模运算实现循环利用空间:

c

复制代码
// 关键运算:
rear = (rear + 1) % MAX_SIZE;
front = (front + 1) % MAX_SIZE;
3.3 循环队列的三种状态判断方法

方法一:牺牲一个存储单元(最常用)

c

复制代码
typedef struct {
    int data[MAX_SIZE];
    int front;  // 指向队头元素
    int rear;   // 指向队尾元素的下一个位置
} CircularQueue;

// 队空条件:front == rear
// 队满条件:(rear + 1) % MAX_SIZE == front

方法二:增加一个计数器

c

复制代码
typedef struct {
    int data[MAX_SIZE];
    int front;
    int rear;
    int count;  // 记录元素个数
} CircularQueue;

// 队空条件:count == 0
// 队满条件:count == MAX_SIZE

方法三:增加标志位

c

复制代码
typedef struct {
    int data[MAX_SIZE];
    int front;
    int rear;
    int flag;  // 0:空, 1:非空, 2:满
} CircularQueue;
3.4 完整实现代码(牺牲一个存储单元法)

c

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAX_SIZE 5

typedef struct {
    int data[MAX_SIZE];
    int front;  // 队头索引
    int rear;   // 队尾索引(指向下一个入队位置)
} CircularQueue;

// 1. 初始化队列
void InitCircularQueue(CircularQueue *q) {
    q->front = 0;
    q->rear = 0;
    printf("初始化队列,front=%d, rear=%d\n", q->front, q->rear);
}

// 2. 判断队列是否为空
bool IsEmpty(CircularQueue *q) {
    return q->front == q->rear;
}

// 3. 判断队列是否已满
bool IsFull(CircularQueue *q) {
    return (q->rear + 1) % MAX_SIZE == q->front;
}

// 4. 入队操作
bool Enqueue(CircularQueue *q, int value) {
    if (IsFull(q)) {
        printf("队列已满,无法入队 %d\n", value);
        return false;
    }
    
    q->data[q->rear] = value;
    q->rear = (q->rear + 1) % MAX_SIZE;
    
    printf("入队: %d, front=%d, rear=%d\n", value, q->front, q->rear);
    return true;
}

// 5. 出队操作
bool Dequeue(CircularQueue *q, int *value) {
    if (IsEmpty(q)) {
        printf("队列为空,无法出队\n");
        return false;
    }
    
    *value = q->data[q->front];
    q->front = (q->front + 1) % MAX_SIZE;
    
    printf("出队: %d, front=%d, rear=%d\n", *value, q->front, q->rear);
    return true;
}

// 6. 获取队头元素(不出队)
bool GetFront(CircularQueue *q, int *value) {
    if (IsEmpty(q)) {
        printf("队列为空,无队头元素\n");
        return false;
    }
    
    *value = q->data[q->front];
    return true;
}

// 7. 获取队列元素个数
int GetSize(CircularQueue *q) {
    return (q->rear - q->front + MAX_SIZE) % MAX_SIZE;
}

// 8. 打印队列状态
void PrintQueue(CircularQueue *q) {
    printf("队列状态: [");
    
    if (IsEmpty(q)) {
        printf("空");
    } else {
        int i = q->front;
        while (i != q->rear) {
            printf("%d", q->data[i]);
            i = (i + 1) % MAX_SIZE;
            if (i != q->rear) printf(", ");
        }
    }
    
    printf("], front=%d, rear=%d, size=%d\n", 
           q->front, q->rear, GetSize(q));
}

// 9. 测试函数
void TestCircularQueue() {
    printf("===== 测试循环队列 =====\n");
    CircularQueue q;
    InitCircularQueue(&q);
    
    printf("\n1. 入队测试:\n");
    for (int i = 1; i <= 6; i++) {
        Enqueue(&q, i * 10);
        PrintQueue(&q);
    }
    
    printf("\n2. 出队测试:\n");
    int value;
    for (int i = 0; i < 3; i++) {
        Dequeue(&q, &value);
        PrintQueue(&q);
    }
    
    printf("\n3. 再次入队测试(验证循环):\n");
    for (int i = 7; i <= 9; i++) {
        Enqueue(&q, i * 10);
        PrintQueue(&q);
    }
    
    printf("\n4. 查看队头元素:\n");
    if (GetFront(&q, &value)) {
        printf("队头元素: %d\n", value);
    }
    
    printf("\n5. 获取队列大小:\n");
    printf("当前队列大小: %d\n", GetSize(&q));
}

int main() {
    TestCircularQueue();
    return 0;
}
3.5 循环队列状态图示

text

复制代码
初始状态:front=0, rear=0
┌───┬───┬───┬───┬───┐
│   │   │   │   │   │
└───┴───┴───┴───┴───┘
 ↑
front/rear

入队10,20后:front=0, rear=2
┌───┬───┬───┬───┬───┐
│10 │20 │   │   │   │
└───┴───┴───┴───┴───┘
 ↑       ↑
front   rear

出队10后:front=1, rear=2
┌───┬───┬───┬───┬───┐
│   │20 │   │   │   │
└───┴───┴───┴───┴───┘
     ↑   ↑
   front rear

入队30,40,50后:front=1, rear=0(循环)
┌───┬───┬───┬───┬───┐
│50 │20 │30 │40 │   │
└───┴───┴───┴───┴───┘
     ↑
    front=1, rear=0
     ↓
    循环利用空间

4. 链式队列

4.1 结构定义

c

复制代码
// 队列节点
typedef struct QueueNode {
    int data;
    struct QueueNode *next;
} QueueNode;

// 链式队列(带front和rear指针)
typedef struct {
    QueueNode *front;  // 队头指针
    QueueNode *rear;   // 队尾指针
    int size;          // 队列大小
} LinkedQueue;
4.2 完整实现代码

c

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

// 1. 节点定义
typedef struct QueueNode {
    int data;
    struct QueueNode *next;
} QueueNode;

// 2. 链式队列定义
typedef struct {
    QueueNode *front;
    QueueNode *rear;
    int size;
} LinkedQueue;

// 3. 初始化队列
void InitLinkedQueue(LinkedQueue *q) {
    q->front = NULL;
    q->rear = NULL;
    q->size = 0;
    printf("初始化链式队列\n");
}

// 4. 创建新节点
QueueNode* CreateQueueNode(int value) {
    QueueNode *node = (QueueNode*)malloc(sizeof(QueueNode));
    if (node == NULL) {
        printf("内存分配失败\n");
        return NULL;
    }
    node->data = value;
    node->next = NULL;
    return node;
}

// 5. 判断队列是否为空
bool IsLinkedQueueEmpty(LinkedQueue *q) {
    return q->front == NULL;
}

// 6. 入队操作
bool EnqueueLinked(LinkedQueue *q, int value) {
    QueueNode *newNode = CreateQueueNode(value);
    if (newNode == NULL) return false;
    
    if (IsLinkedQueueEmpty(q)) {
        // 队列为空时,front和rear都指向新节点
        q->front = newNode;
        q->rear = newNode;
    } else {
        // 队列不为空,将新节点加到队尾
        q->rear->next = newNode;
        q->rear = newNode;
    }
    
    q->size++;
    printf("入队: %d, 队列大小: %d\n", value, q->size);
    return true;
}

// 7. 出队操作
bool DequeueLinked(LinkedQueue *q, int *value) {
    if (IsLinkedQueueEmpty(q)) {
        printf("队列为空,无法出队\n");
        return false;
    }
    
    QueueNode *temp = q->front;
    *value = temp->data;
    
    q->front = q->front->next;
    
    // 如果出队后队列为空,需要重置rear
    if (q->front == NULL) {
        q->rear = NULL;
    }
    
    free(temp);
    q->size--;
    
    printf("出队: %d, 队列大小: %d\n", *value, q->size);
    return true;
}

// 8. 获取队头元素
bool GetFrontLinked(LinkedQueue *q, int *value) {
    if (IsLinkedQueueEmpty(q)) {
        printf("队列为空,无队头元素\n");
        return false;
    }
    
    *value = q->front->data;
    return true;
}

// 9. 打印队列
void PrintLinkedQueue(LinkedQueue *q) {
    if (IsLinkedQueueEmpty(q)) {
        printf("队列为空\n");
        return;
    }
    
    printf("队列元素(队头→队尾): ");
    QueueNode *current = q->front;
    while (current != NULL) {
        printf("%d", current->data);
        if (current->next != NULL) printf(" → ");
        current = current->next;
    }
    printf("\n队列大小: %d\n", q->size);
}

// 10. 销毁队列
void DestroyLinkedQueue(LinkedQueue *q) {
    int value;
    printf("销毁队列,依次出队: ");
    while (!IsLinkedQueueEmpty(q)) {
        DequeueLinked(q, &value);
        printf("%d ", value);
    }
    printf("\n队列已销毁\n");
}

// 11. 测试函数
void TestLinkedQueue() {
    printf("===== 测试链式队列 =====\n");
    LinkedQueue q;
    InitLinkedQueue(&q);
    
    printf("\n1. 入队测试:\n");
    for (int i = 1; i <= 5; i++) {
        EnqueueLinked(&q, i * 10);
    }
    PrintLinkedQueue(&q);
    
    printf("\n2. 出队测试:\n");
    int value;
    for (int i = 0; i < 3; i++) {
        DequeueLinked(&q, &value);
        PrintLinkedQueue(&q);
    }
    
    printf("\n3. 再次入队测试:\n");
    for (int i = 6; i <= 8; i++) {
        EnqueueLinked(&q, i * 10);
    }
    PrintLinkedQueue(&q);
    
    printf("\n4. 查看队头元素:\n");
    if (GetFrontLinked(&q, &value)) {
        printf("队头元素: %d\n", value);
    }
    
    printf("\n5. 销毁队列:\n");
    DestroyLinkedQueue(&q);
}
4.3 链式队列内存图示

text

复制代码
初始状态:
front → NULL
rear → NULL
size = 0

入队10后:
front → [10|NULL] ← rear
size = 1

入队20后:
front → [10|∙] → [20|NULL] ← rear
size = 2

入队30后:
front → [10|∙] → [20|∙] → [30|NULL] ← rear
size = 3

出队10后:
front → [20|∙] → [30|NULL] ← rear
size = 2

5. 顺序队列 vs 链式队列对比

特性 顺序循环队列 链式队列
存储结构 数组 链表
容量限制 固定大小 动态扩展
内存使用 连续内存 离散内存
空间效率 存储密度高 有指针开销
时间效率 O(1) 操作 O(1) 操作
适用场景 队列大小可预估 队列大小变化大
优点 实现简单,访问快 灵活,无大小限制
缺点 容量固定,可能溢出 内存碎片,指针开销

二、树形结构(Tree)详解

1. 树的基本概念

1.1 树的定义

树是一种非线性数据结构 ,描述数据间一对多的层次关系。

1.2 树的图示

text

复制代码
         A (根节点,第1层)
        / \
       B   C (分支节点,第2层)
      / \   \
     D   E   F (叶子节点,第3层)
    / \
   G   H (叶子节点,第4层)
1.3 树的相关术语
  • 节点(Node):树中的每个元素

  • 根节点(Root):没有父节点的节点(如A)

  • 叶子节点(Leaf):没有子节点的节点(如D,E,F,G,H)

  • 分支节点/内部节点:有子节点的节点(如A,B,C)

  • 父节点(Parent):节点的直接上级

  • 子节点(Child):节点的直接下级

  • 兄弟节点(Sibling):同一父节点的子节点

  • 祖先节点(Ancestor):从根到该节点路径上的所有节点

  • 后代节点(Descendant):以该节点为根的子树中的所有节点

  • 度(Degree):节点的子节点个数

    • 节点B的度 = 2(有D,E两个子节点)

    • 叶子节点的度 = 0

  • 树的度:树中所有节点的最大度数

  • 路径(Path):从节点A到节点B所经过的节点序列

  • 路径长度:路径上的边数

  • 层(Level):根节点为第1层,向下依次递增

  • 深度(Depth):节点到根节点的路径长度(根节点深度为0或1)

  • 高度(Height):节点到最远叶子节点的路径长度(叶子节点高度为0或1)

  • 树的高度:根节点的高度

  • 森林(Forest):多棵互不相交的树的集合

2. 二叉树(Binary Tree)

2.1 二叉树的定义

每个节点最多有两个子节点(左孩子和右孩子)的树结构。

2.2 二叉树的五种基本形态
  1. 空二叉树

  2. 只有一个根节点

  3. 根节点只有左子树

  4. 根节点只有右子树

  5. 根节点有左右子树

2.3 特殊二叉树
2.3.1 满二叉树(Full Binary Tree)
  • 所有叶子节点都在同一层

  • 每个分支节点都有两个子节点

  • 深度为k的满二叉树有2^k-1个节点

text

复制代码
         A
        / \
       B   C
      / \ / \
     D  E F  G
2.3.2 完全二叉树(Complete Binary Tree)
  • 除了最后一层,其他层都是满的

  • 最后一层的叶子节点都集中在左侧

  • 按层序编号,编号与满二叉树一一对应

text

复制代码
         A
        / \
       B   C
      / \ /
     D  E F
2.3.3 斜树(Skewed Tree)
  • 所有节点都只有左子节点或只有右子节点

左斜树: 右斜树:

text

复制代码
    A          A
   /            \
  B              B
 /                \
C                  C
2.4 二叉树的性质
  1. 在二叉树的第i层上最多有2^(i-1)个节点(i≥1)

  2. 深度为k的二叉树最多有2^k-1个节点(k≥1)

  3. 对于任意二叉树,叶子节点数n₀与度为2的节点数n₂满足:n₀ = n₂ + 1

  4. 具有n个节点的完全二叉树深度为⌊log₂n⌋ + 1

  5. 对完全二叉树从上到下、从左到右编号(1开始):

    • 节点i的父节点编号为⌊i/2⌋

    • 节点i的左孩子编号为2i(如果2i≤n)

    • 节点i的右孩子编号为2i+1(如果2i+1≤n)

3. 二叉树的遍历

3.1 遍历的重要性

遍历是二叉树各种操作的基础:查找、插入、删除、统计等。

3.2 深度优先遍历(DFS)
3.2.1 前序遍历(Preorder Traversal)

访问顺序:根节点 → 左子树 → 右子树

3.2.2 中序遍历(Inorder Traversal)

访问顺序:左子树 → 根节点 → 右子树

3.2.3 后序遍历(Postorder Traversal)

访问顺序:左子树 → 右子树 → 根节点

3.3 广度优先遍历(层序遍历)

访问顺序:从上到下、从左到右逐层访问

3.4 遍历图示与示例

示例二叉树

text

复制代码
        1
       / \
      2   3
     / \   \
    4   5   6
   / \
  7   8

遍历结果

  • 前序遍历:1 2 4 7 8 5 3 6

    • 访问顺序:根→左→右

    • 记忆:根左右

  • 中序遍历:7 4 8 2 5 1 3 6

    • 访问顺序:左→根→右

    • 记忆:左根右

  • 后序遍历:7 8 4 5 2 6 3 1

    • 访问顺序:左→右→根

    • 记忆:左右根

  • 层序遍历:1 2 3 4 5 6 7 8

    • 访问顺序:逐层从左到右

三、队列与树的综合应用

1. 使用队列实现栈的功能

c

复制代码
// 使用两个队列实现栈(后进先出)
typedef struct {
    LinkedQueue queue1;
    LinkedQueue queue2;
} StackUsingQueues;

// 入栈操作:总是进入非空的队列
void PushUsingQueues(StackUsingQueues *s, int value) {
    if (!IsLinkedQueueEmpty(&s->queue1)) {
        EnqueueLinked(&s->queue1, value);
    } else {
        EnqueueLinked(&s->queue2, value);
    }
}

// 出栈操作:将非空队列的前n-1个元素转移到另一个队列,然后出队最后一个元素
int PopUsingQueues(StackUsingQueues *s) {
    LinkedQueue *nonEmpty, *empty;
    int value;
    
    if (!IsLinkedQueueEmpty(&s->queue1)) {
        nonEmpty = &s->queue1;
        empty = &s->queue2;
    } else {
        nonEmpty = &s->queue2;
        empty = &s->queue1;
    }
    
    // 将前n-1个元素转移到空队列
    while (nonEmpty->size > 1) {
        DequeueLinked(nonEmpty, &value);
        EnqueueLinked(empty, value);
    }
    
    // 出队最后一个元素(栈顶)
    DequeueLinked(nonEmpty, &value);
    return value;
}

2. 使用栈实现队列的功能

c

复制代码
// 使用两个栈实现队列(先进先出)
typedef struct {
    Stack stack1;  // 用于入队
    Stack stack2;  // 用于出队
} QueueUsingStacks;

// 入队操作:直接压入stack1
void EnqueueUsingStacks(QueueUsingStacks *q, int value) {
    Push(&q->stack1, value);
}

// 出队操作:如果stack2为空,将stack1的所有元素弹出并压入stack2
int DequeueUsingStacks(QueueUsingStacks *q) {
    if (IsStackEmpty(&q->stack2)) {
        while (!IsStackEmpty(&q->stack1)) {
            int value;
            Pop(&q->stack1, &value);
            Push(&q->stack2, value);
        }
    }
    
    int value;
    Pop(&q->stack2, &value);
    return value;
}

四、总结

队列总结:

  1. 特点:先进先出(FIFO),队尾入队,队头出队

  2. 实现方式

    • 顺序循环队列:数组实现,需要处理"假溢出"

    • 链式队列:链表实现,动态扩展,无大小限制

  3. 应用场景:任务调度、消息队列、广度优先搜索等

树总结:

  1. 特点:非线性结构,一对多关系,层次结构

  2. 二叉树特殊形式

    • 满二叉树:所有层都满

    • 完全二叉树:除最后一层外都满,最后一层左侧连续

  3. 遍历方式

    • 深度优先:前序、中序、后序(递归或栈实现)

    • 广度优先:层序(队列实现)

  4. 应用场景:文件系统、数据库索引、表达式求值、哈夫曼编码等

相关推荐
青桔柠薯片2 小时前
数据结构:队列,二叉树
数据结构
杜家老五2 小时前
综合实力与专业服务深度解析 2026北京网站制作公司六大优选
数据结构·算法·线性回归·启发式算法·模拟退火算法
好好沉淀2 小时前
Elasticsearch 中获取返回匹配记录总数
开发语言·elasticsearch
寄存器漫游者2 小时前
数据结构:带头节点单链表
c语言·数据结构
2301_765703142 小时前
C++与自动驾驶系统
开发语言·c++·算法
Ll13045252982 小时前
Leetcode二叉树 part1
b树·算法·leetcode
MediaTea2 小时前
<span class=“js_title_inner“>Python:实例对象</span>
开发语言·前端·javascript·python·ecmascript
鹿角片ljp2 小时前
力扣9.回文数-转字符双指针和反转数字
java·数据结构·算法
热爱编程的小刘2 小时前
Lesson04---类与对象(下篇)
开发语言·c++·算法