数据结构day5——队列和树

目录

一、队列:先进先出的数据缓冲区

队列的核心概念

队列的典型应用场景

队列的基本操作

队列的两种 C 语言实现方式

1. 顺序队列(基于数组的实现)

2. 循环队列(解决假溢出问题)

二、树:一对多的层次结构

树的基本概念

树的存储方式

二叉树:最常用的树结构

二叉树的定义

二叉树的特点

特殊的二叉树

二叉树的重要特性

二叉树的 C 语言实现与遍历

三、总结


在数据结构的世界里,队列和树是两种截然不同却又同样重要的结构。队列以其 "先进先出" 的特性成为处理有序数据的利器,而树则凭借 "一对多" 的层次关系,成为解决复杂问题的强大工具。本文将带你深入了解这两种结构的原理、特性,并通过 C 语言实现它们的核心功能。

一、队列:先进先出的数据缓冲区

队列(Queue)是一种限定仅在一端进行插入操作,另一端进行删除操作的线性表,这种特性使其成为处理有序数据的理想选择。

队列的核心概念

  • 队尾(Rear):允许插入元素的一端
  • 队头(Front):允许删除元素的一端
  • 核心特性:先进先出(First In First Out,简称 FIFO)—— 最早进入队列的元素将最早被取出

队列的典型应用场景

队列最核心的应用是作为缓冲区,协调处理速度不同的设备或模块:

  • CPU(高速设备)与硬盘、键盘、鼠标(低速设备)之间的数据交互
  • 网络数据传输中的数据包排队处理
  • 打印任务队列管理
  • 广度优先搜索(BFS)算法的实现基础

队列的基本操作

操作名称 功能描述 时间复杂度
initQueue 初始化队列 O(1)
enQueue 向队尾插入元素(入队) O(1)
deQueue 从队头删除元素(出队) O(1)
getFront 获取队头元素(不删除) O(1)
isEmpty 判断队列是否为空 O(1)
isFull 判断队列是否已满 O(1)
size 获取队列中元素个数 O(1)

队列的两种 C 语言实现方式

1. 顺序队列(基于数组的实现)

顺序队列使用数组作为底层存储,但存在 "假溢出" 问题(队尾已满但队头有空闲空间)。

#include 
#include 
#include 

#define MAX_QUEUE_SIZE 100

typedef struct {
    int data[MAX_QUEUE_SIZE];
    int front;  // 队头指针(指向队头元素)
    int rear;   // 队尾指针(指向队尾元素的下一个位置)
} SeqQueue;

// 初始化队列
void initQueue(SeqQueue *queue) {
    queue->front = 0;
    queue->rear = 0;
}

// 判断队列是否为空
bool isEmpty(SeqQueue *queue) {
    return queue->front == queue->rear;
}

// 判断队列是否已满
bool isFull(SeqQueue *queue) {
    return queue->rear == MAX_QUEUE_SIZE;
}

// 入队操作
bool enQueue(SeqQueue *queue, int value) {
    if (isFull(queue)) {
        printf("队列已满,无法入队!\n");
        return false;
    }
    queue->data[queue->rear++] = value;
    return true;
}

// 出队操作
bool deQueue(SeqQueue *queue, int *value) {
    if (isEmpty(queue)) {
        printf("队列为空,无法出队!\n");
        return false;
    }
    *value = queue->data[queue->front++];
    return true;
}

// 获取队头元素
bool getFront(SeqQueue *queue, int *value) {
    if (isEmpty(queue)) {
        printf("队列为空,无队头元素!\n");
        return false;
    }
    *value = queue->data[queue->front];
    return true;
}

// 获取队列大小
int size(SeqQueue *queue) {
    return queue->rear - queue->front;
}
2. 循环队列(解决假溢出问题)

循环队列通过将数组视为环形结构,解决了顺序队列的假溢出问题,是实际应用中更常用的队列形式。

#include 
#include 
#include 

#define MAX_CIRCLE_QUEUE_SIZE 100

typedef struct {
    int data[MAX_CIRCLE_QUEUE_SIZE];
    int front;  // 队头指针
    int rear;   // 队尾指针
} CircleQueue;

// 初始化循环队列
void initCircleQueue(CircleQueue *queue) {
    queue->front = 0;
    queue->rear = 0;
}

// 判断队列是否为空
bool isCircleQueueEmpty(CircleQueue *queue) {
    return queue->front == queue->rear;
}

// 判断队列是否已满(预留一个空位置区分满和空)
bool isCircleQueueFull(CircleQueue *queue) {
    return (queue->rear + 1) % MAX_CIRCLE_QUEUE_SIZE == queue->front;
}

// 入队操作
bool enCircleQueue(CircleQueue *queue, int value) {
    if (isCircleQueueFull(queue)) {
        printf("循环队列已满,无法入队!\n");
        return false;
    }
    queue->data[queue->rear] = value;
    queue->rear = (queue->rear + 1) % MAX_CIRCLE_QUEUE_SIZE;  // 环形移动
    return true;
}

// 出队操作
bool deCircleQueue(CircleQueue *queue, int *value) {
    if (isCircleQueueEmpty(queue)) {
        printf("循环队列为空,无法出队!\n");
        return false;
    }
    *value = queue->data[queue->front];
    queue->front = (queue->front + 1) % MAX_CIRCLE_QUEUE_SIZE;  // 环形移动
    return true;
}

// 获取队列大小
int circleQueueSize(CircleQueue *queue) {
    return (queue->rear - queue->front + MAX_CIRCLE_QUEUE_SIZE) % MAX_CIRCLE_QUEUE_SIZE;
}

循环队列 vs 顺序队列:循环队列通过取模运算实现环形结构,内存利用率更高,解决了顺序队列的假溢出问题,是实际开发中的首选队列实现方式。

二、树:一对多的层次结构

树(Tree)是一种非线性数据结构,它的节点之间呈现一对多的层次关系,非常适合表示具有层级特性的数据(如文件系统、组织结构等)。

树的基本概念

  • 树是n(n≥0)个节点的有限集合

    • 当 n=0 时,称为空树
    • 当 n>0 时,有且仅有一个特定的节点称为根节点(Root)
    • 其余节点可分为 m 个互不相交的有限集合,每个集合本身也是一棵树,称为根节点的子树(Subtree)
  • 节点的度:节点拥有的子树个数;

  • 叶节点:度为 0 的节点(没有子树的节点);

  • 分支节点:度不为 0 的节点;

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

  • 树的深度(高度):从根节点开始计数,根为第 1 层,其孩子为第 2 层,以此类推。

树的存储方式

树的存储结构主要有两种:

  • 顺序结构:适合存储完全二叉树等特殊树结构,内存连续但不够灵活;
  • 链式结构:通过指针连接节点,内存利用率高且灵活,是大多数树的首选存储方式。

重要特性:任何树都可以通过适当的旋转转换为二叉树,这为树的处理提供了统一思路。

二叉树:最常用的树结构

二叉树(Binary Tree)是一种特殊的树,每个节点最多拥有两棵子树,是实际应用中最广泛的树结构。

二叉树的定义

二叉树是 n 个节点的有限集合,要么为空树,要么由一个根节点和两棵互不相交的左子树右子树组成。

二叉树的特点
  1. 每个节点最多有两棵子树(左子树和右子树);
  2. 左子树和右子树是有顺序的,次序不能颠倒
  3. 即使节点只有一棵子树,也要区分是左子树还是右子树。
特殊的二叉树
  • 斜树:所有节点都只有左子树的称为左斜树,所有节点都只有右子树的称为右斜树(本质上退化为线性结构);
  • 满二叉树:所有分支节点都有左右子树,且所有叶节点都在同一层;
  • 完全二叉树:对一棵有 n 个节点的二叉树按层序编号,如果编号 i(1≤i≤n)的节点与同深度的满二叉树中编号 i 的节点位置相同,则为完全二叉树。
二叉树的重要特性
  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(向下取整)。
二叉树的 C 语言实现与遍历

二叉树的链式存储结构(二叉链表)是最常用的实现方式:

#include 
#include 

// 二叉树节点结构
typedef struct BiTreeNode {
    int data;
    struct BiTreeNode *lchild;  // 左子树指针
    struct BiTreeNode *rchild;  // 右子树指针
} BiTreeNode, *BiTree;

// 创建二叉树节点
BiTreeNode* createNode(int data) {
    BiTreeNode *node = (BiTreeNode*)malloc(sizeof(BiTreeNode));
    if (node == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }
    node->data = data;
    node->lchild = NULL;
    node->rchild = NULL;
    return node;
}

// 二叉树的遍历
// 1. 前序遍历(根->左->右)
void preOrder(BiTree tree) {
    if (tree != NULL) {
        printf("%d ", tree->data);  // 访问根节点
        preOrder(tree->lchild);     // 遍历左子树
        preOrder(tree->rchild);     // 遍历右子树
    }
}

// 2. 中序遍历(左->根->右)
void inOrder(BiTree tree) {
    if (tree != NULL) {
        inOrder(tree->lchild);      // 遍历左子树
        printf("%d ", tree->data);  // 访问根节点
        inOrder(tree->rchild);      // 遍历右子树
    }
}

// 3. 后序遍历(左->右->根)
void postOrder(BiTree tree) {
    if (tree != NULL) {
        postOrder(tree->lchild);    // 遍历左子树
        postOrder(tree->rchild);    // 遍历右子树
        printf("%d ", tree->data);  // 访问根节点
    }
}

// 4. 层序遍历(广度优先遍历,需要借助队列)
void levelOrder(BiTree tree) {
    if (tree == NULL) return;
    
    CircleQueue queue;  // 使用前面定义的循环队列
    initCircleQueue(&queue);
    enCircleQueue(&queue, (int)tree);  // 存储节点地址
    
    while (!isCircleQueueEmpty(&queue)) {
        BiTreeNode *node;
        deCircleQueue(&queue, (int*)&node);  // 取出节点地址
        printf("%d ", node->data);
        
        // 左孩子入队
        if (node->lchild != NULL) {
            enCircleQueue(&queue, (int)node->lchild);
        }
        // 右孩子入队
        if (node->rchild != NULL) {
            enCircleQueue(&queue, (int)node->rchild);
        }
    }
}

// 示例:构建一棵简单的二叉树
BiTree createExampleTree() {
    // 构建节点
    BiTreeNode *root = createNode(1);
    root->lchild = createNode(2);
    root->rchild = createNode(3);
    root->lchild->lchild = createNode(4);
    root->lchild->rchild = createNode(5);
    root->rchild->lchild = createNode(6);
    
    return root;
}

// 主函数测试
int main() {
    BiTree tree = createExampleTree();
    
    printf("前序遍历:");
    preOrder(tree);  // 输出:1 2 4 5 3 6
    printf("\n");
    
    printf("中序遍历:");
    inOrder(tree);   // 输出:4 2 5 1 6 3
    printf("\n");
    
    printf("后序遍历:");
    postOrder(tree); // 输出:4 5 2 6 3 1
    printf("\n");
    
    printf("层序遍历:");
    levelOrder(tree); // 输出:1 2 3 4 5 6
    printf("\n");
    
    return 0;
}

三、总结

队列和树是两种互补的数据结构:

  • 队列以 "先进先出" 为核心,循环队列解决了顺序队列的假溢出问题,广泛用于缓冲机制、任务调度等场景;
  • 以 "一对多" 的层次关系为特征,二叉树作为最常用的树结构,通过前序、中序、后序和层序四种遍历方式可全面访问其节点。

掌握这两种结构的原理与实现,能帮助你解决更多复杂的编程问题。队列的有序性和树的层次性,为处理不同类型的数据提供了高效的解决方案,是每个程序员必备的基础知识。

你可能感兴趣的:(数据结构,数据结构)