常见的数据结构---队列、树与堆的深入剖析

目录

一、队列

二、树

三、堆


在现代计算机科学与工程领域,队列是三种极其重要的基础数据结构,它们各自具有独特的特点和应用。在日常开发中,合理选择和使用这些数据结构可以显著提高程序的效率和可维护性。它们不仅奠定了算法设计的理论基础,还在系统开发、数据处理和实际应用中扮演着不可或缺的角色。

一、队列

队列(Queue)是一种特殊的线性数据结构,它遵循先进先出(First In First Out, FIFO)的原则。这意味着最早进入队列的元素将是最先被移除的元素。在队列中,插入操作通常发生在队尾(rear),而删除操作则发生在队头(front)。这种特性使得队列非常适合用来模拟现实生活中的排队现象,如银行或超市收银台前的顾客排队。

1. 队列的定义与特点

定义:队列是一种特殊的线性表,只允许在一端(称为队尾)插入数据,在另一端(称为队头)删除数据。

特点:

先进先出:队列中最早加入的元素最先被移除。

单向操作:数据只能从队尾插入,从队头移除。

受限性:与栈类似,操作位置有限,不支持随机访问。

2. 队列的基本操作

  1. 入队(Enqueue)在队尾插入一个元素。

  2. 出队(Dequeue)从队头移除一个元素。

  3. 获取队头元素(Front)查看队头元素,但不移除。

  4. 检查是否为空(IsEmpty)判断队列中是否还有数据。

  5. 检查是否已满(IsFull)(对于静态实现的队列)判断队列是否达到容量限制。

3. 队列的分类

3.1 普通队列

最简单的队列形式。

操作规则

入队:在队尾插入数据。

出队:从队头移除数据。

#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100

typedef struct {
    int data[MAX_SIZE];
    int front;  // 指向队头
    int rear;   // 指向队尾
} Queue;

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

// 判断队列是否为空
int isEmpty(Queue *q) {
    return q->front == q->rear;
}

// 判断队列是否已满
int isFull(Queue *q) {
    return q->rear == MAX_SIZE;
}

// 入队操作
void enqueue(Queue *q, int value) {
    if (isFull(q)) {
        printf("Queue is full\n");
        return;
    }
    q->data[q->rear++] = value;
}

// 出队操作
int dequeue(Queue *q) {
    if (isEmpty(q)) {
        printf("Queue is empty\n");
        return -1;
    }
    return q->data[q->front++];
}

int main() {
    Queue q;
    initQueue(&q);
    enqueue(&q, 10);
    enqueue(&q, 20);
    printf("Dequeued: %d\n", dequeue(&q));
    printf("Dequeued: %d\n", dequeue(&q));
    return 0;
}

3.2 循环队列

为了解决普通队列中由于数据出队导致的空间浪费问题,采用环状的存储方式。

特点:通过逻辑上的首尾相连使队列能高效利用存储空间。

实现:利用取模运算控制队列的首尾相连。

#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 5

typedef struct {
    int data[MAX_SIZE];
    int front;  // 指向队头
    int rear;   // 指向队尾
} CircularQueue;

// 初始化循环队列
void initCircularQueue(CircularQueue *q) {
    q->front = q->rear = 0;
}

// 判断循环队列是否为空
int isCircularQueueEmpty(CircularQueue *q) {
    return q->front == q->rear;
}

// 判断循环队列是否已满
int isCircularQueueFull(CircularQueue *q) {
    return (q->rear + 1) % MAX_SIZE == q->front;
}

// 入队操作
void circularEnqueue(CircularQueue *q, int value) {
    if (isCircularQueueFull(q)) {
        printf("Circular Queue is full\n");
        return;
    }
    q->data[q->rear] = value;
    q->rear = (q->rear + 1) % MAX_SIZE;
}

// 出队操作
int circularDequeue(CircularQueue *q) {
    if (isCircularQueueEmpty(q)) {
        printf("Circular Queue is empty\n");
        return -1;
    }
    int value = q->data[q->front];
    q->front = (q->front + 1) % MAX_SIZE;
    return value;
}

int main() {
    CircularQueue q;
    initCircularQueue(&q);
    circularEnqueue(&q, 10);
    circularEnqueue(&q, 20);
    printf("Dequeued: %d\n", circularDequeue(&q));
    printf("Dequeued: %d\n", circularDequeue(&q));
    return 0;
}

3.3 双端队列(Deque, Double-Ended Queue)

支持在队列的两端进行入队和出队操作。

分类

输入受限双端队列:只能从一端入队。

输出受限双端队列:只能从一端出队。

#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100

typedef struct {
    int data[MAX_SIZE];
    int front;  // 指向队头
    int rear;   // 指向队尾
} Deque;

// 初始化双端队列
void initDeque(Deque *dq) {
    dq->front = dq->rear = 0;
}

// 判断是否为空
int isDequeEmpty(Deque *dq) {
    return dq->front == dq->rear;
}

// 判断是否已满
int isDequeFull(Deque *dq) {
    return (dq->rear + 1) % MAX_SIZE == dq->front;
}

// 从队头插入
void enqueueFront(Deque *dq, int value) {
    if (isDequeFull(dq)) {
        printf("Deque is full\n");
        return;
    }
    dq->front = (dq->front - 1 + MAX_SIZE) % MAX_SIZE;
    dq->data[dq->front] = value;
}

// 从队尾插入
void enqueueRear(Deque *dq, int value) {
    if (isDequeFull(dq)) {
        printf("Deque is full\n");
        return;
    }
    dq->data[dq->rear] = value;
    dq->rear = (dq->rear + 1) % MAX_SIZE;
}

// 从队头删除
int dequeueFront(Deque *dq) {
    if (isDequeEmpty(dq)) {
        printf("Deque is empty\n");
        return -1;
    }
    int value = dq->data[dq->front];
    dq->front = (dq->front + 1) % MAX_SIZE;
    return value;
}

// 从队尾删除
int dequeueRear(Deque *dq) {
    if (isDequeEmpty(dq)) {
        printf("Deque is empty\n");
        return -1;
    }
    dq->rear = (dq->rear - 1 + MAX_SIZE) % MAX_SIZE;
    return dq->data[dq->rear];
}

int main() {
    Deque dq;
    initDeque(&dq);
    enqueueRear(&dq, 10);
    enqueueRear(&dq, 20);
    enqueueFront(&dq, 5);
    printf("Dequeued from front: %d\n", dequeueFront(&dq));
    printf("Dequeued from rear: %d\n", dequeueRear(&dq));
    return 0;
}

3.4 优先队列(Priority Queue)

队列中的每个元素附加一个优先级,根据优先级决定元素的出队顺序。

实现方式:常用堆(Heap)结构实现。

#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100

typedef struct {
    int data[MAX_SIZE];
    int size;  // 当前优先队列大小
} PriorityQueue;

// 初始化优先队列
void initPriorityQueue(PriorityQueue *pq) {
    pq->size = 0;
}

// 插入元素到优先队列
void enqueuePriorityQueue(PriorityQueue *pq, int value) {
    if (pq->size >= MAX_SIZE) {
        printf("Priority Queue is full\n");
        return;
    }
    pq->data[pq->size++] = value;
    int i = pq->size - 1;
    // 上浮操作
    while (i > 0 && pq->data[i] < pq->data[(i - 1) / 2]) {
        int temp = pq->data[i];
        pq->data[i] = pq->data[(i - 1) / 2];
        pq->data[(i - 1) / 2] = temp;
        i = (i - 1) / 2;
    }
}

// 移除优先队列中的最小元素
int dequeuePriorityQueue(PriorityQueue *pq) {
    if (pq->size == 0) {
        printf("Priority Queue is empty\n");
        return -1;
    }
    int min = pq->data[0];
    pq->data[0] = pq->data[--pq->size];
    int i = 0;
    // 下沉操作
    while (2 * i + 1 < pq->size) {
        int smallest = 2 * i + 1;
        if (smallest + 1 < pq->size && pq->data[smallest + 1] < pq->data[smallest]) {
            smallest++;
        }
        if (pq->data[i] <= pq->data[smallest]) break;
        int temp = pq->data[i];
        pq->data[i] = pq->data[smallest];
        pq->data[smallest] = temp;
        i = smallest;
    }
    return min;
}

int main() {
    PriorityQueue pq;
    initPriorityQueue(&pq);
    enqueuePriorityQueue(&pq, 15);
    enqueuePriorityQueue(&pq, 10);
    enqueuePriorityQueue(&pq, 5);
    printf("Dequeued: %d\n", dequeuePriorityQueue(&pq));
    printf("Dequeued: %d\n", dequeuePriorityQueue(&pq));
    return 0;
}

4. 队列的实现方式

4.1 顺序队列(基于数组)

优点:实现简单。

缺点:如果不使用循环队列,出队会导致存储空间浪费。数组大小固定,可能导致空间浪费或溢出。

#include <stdio.h>
#define MAX_SIZE 100

typedef struct {
    int data[MAX_SIZE];
    int front;
    int rear;
} Queue;

void initQueue(Queue *q) {
    q->front = q->rear = 0;
}

int isEmpty(Queue *q) {
    return q->front == q->rear;
}

int isFull(Queue *q) {
    return (q->rear + 1) % MAX_SIZE == q->front;
}

void enqueue(Queue *q, int value) {
    if (isFull(q)) {
        printf("Queue is full\n");
        return;
    }
    q->data[q->rear] = value;
    q->rear = (q->rear + 1) % MAX_SIZE;
}

int dequeue(Queue *q) {
    if (isEmpty(q)) {
        printf("Queue is empty\n");
        return -1;
    }
    int value = q->data[q->front];
    q->front = (q->front + 1) % MAX_SIZE;
    return value;
}

int main() {
    Queue q;
    initQueue(&q);
    enqueue(&q, 10);
    enqueue(&q, 20);
    printf("Dequeued: %d\n", dequeue(&q));
    printf("Dequeued: %d\n", dequeue(&q));
    return 0;
}

4.2 链式队列(基于链表)

优点:动态分配存储空间,无需提前固定大小。

缺点:需要额外的指针开销。

#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node *next;
} Node;

typedef struct {
    Node *front;
    Node *rear;
} Queue;

void initQueue(Queue *q) {
    q->front = q->rear = NULL;
}

int isEmpty(Queue *q) {
    return q->front == NULL;
}

void enqueue(Queue *q, int value) {
    Node *newNode = (Node *)malloc(sizeof(Node));
    newNode->data = value;
    newNode->next = NULL;
    if (q->rear) {
        q->rear->next = newNode;
    } else {
        q->front = newNode;
    }
    q->rear = newNode;
}

int dequeue(Queue *q) {
    if (isEmpty(q)) {
        printf("Queue is empty\n");
        return -1;
    }
    Node *temp = q->front;
    int value = temp->data;
    q->front = temp->next;
    if (q->front == NULL) {
        q->rear = NULL;
    }
    free(temp);
    return value;
}

int main() {
    Queue q;
    initQueue(&q);
    enqueue(&q, 10);
    enqueue(&q, 20);
    printf("Dequeued: %d\n", dequeue(&q));
    printf("Dequeued: %d\n", dequeue(&q));
    return 0;
}

4.3 循环队列

原理:利用取模运算实现首尾相连。

操作公式

入队位置:(rear+1)%capacity

出队位置:(front+1)%capacity

优点:充分利用存储空间。

缺点:实现稍微复杂。

#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 5

typedef struct {
    int data[MAX_SIZE];
    int front;  // 指向队头
    int rear;   // 指向队尾
} CircularQueue;

// 初始化循环队列
void initCircularQueue(CircularQueue *q) {
    q->front = q->rear = 0;
}

// 判断循环队列是否为空
int isCircularQueueEmpty(CircularQueue *q) {
    return q->front == q->rear;
}

// 判断循环队列是否已满
int isCircularQueueFull(CircularQueue *q) {
    return (q->rear + 1) % MAX_SIZE == q->front;
}

// 入队操作
void circularEnqueue(CircularQueue *q, int value) {
    if (isCircularQueueFull(q)) {
        printf("Circular Queue is full\n");
        return;
    }
    q->data[q->rear] = value;
    q->rear = (q->rear + 1) % MAX_SIZE;
}

// 出队操作
int circularDequeue(CircularQueue *q) {
    if (isCircularQueueEmpty(q)) {
        printf("Circular Queue is empty\n");
        return -1;
    }
    int value = q->data[q->front];
    q->front = (q->front + 1) % MAX_SIZE;
    return value;
}

int main() {
    CircularQueue q;
    initCircularQueue(&q);
    circularEnqueue(&q, 10);
    circularEnqueue(&q, 20);
    printf("Dequeued: %d\n", circularDequeue(&q));
    printf("Dequeued: %d\n", circularDequeue(&q));
    return 0;
}

5. 队列的典型应用场景

  1. 广度优先搜索(BFS):在图算法中,使用队列存储当前层次的节点。

  2. 任务调度:在操作系统中,用队列管理任务执行顺序,如打印任务、进程调度等。

  3. 数据缓冲:在网络通信中,队列用于暂存数据以待后续处理。

  4. 生产者-消费者模型:队列作为中间缓冲区,连接生产者与消费者。

  5. 消息队列:在分布式系统中,用于异步通信和任务分发。

6. 队列的优势与局限性

优势

简单易用:先进先出的操作模式清晰且自然。

操作高效:逻辑简单,易于实现和使用。插入和删除操作的时间复杂度为 O(1)O(1)O(1)。

应用广泛:从算法设计到工程实践,队列都有重要作用。

 局限性

操作受限:只能在两端进行操作,不支持随机访问。

空间利用问题:普通队列在频繁出队时可能造成空间浪费。

普通队列效率低:出队后空间不可复用。

7、小结

队列作为一种基础数据结构,因其先进先出的特性,在数据的有序处理、任务调度、广度搜索等场景中发挥了重要作用。
通过对队列的分类、操作及实现方式的深入理解,开发者能够灵活运用队列解决各种复杂问题,并优化程序性能。

顺序队列适用于固定大小的简单队列场景,但会导致存储空间浪费。链式队列通过动态分配解决空间问题,适合大小不确定的队列,但需额外的内存指针。循环队列通逻辑上的首尾相连实现存储空间的高效利用,是一种改进的顺序队列方式。

二、树

树是一种 非线性数据结构,以分层的方式存储数据。它由一个根节点(Root)和多个子节点(Child)组成,每个节点可能有多个子节点,但只有一个父节点(Parent)。树特别适用于分层组织和快速查找的场景。

1. 树的定义与特点

定义

树是由节点组成的结构,具有以下特点:

树中只有一个根节点。除根节点外的每个节点有且仅有一个父节点。节点之间通过边连接形成一个无环的层次结构。

树的特点

递归定义:树是一个根节点和若干子树的集合,每棵子树本质上也是一棵树。

无环性:从根节点到任意节点仅有一条路径。

层次结构:树具有明显的分层结构,数据之间存在父子关系。

节点关系父节点:直接指向当前节点的节点。子节点:被当前节点指向的节点。兄弟节点:具有同一个父节点的节点。叶子节点:没有子节点的节点。根节点:没有父节点的节点。

2. 树的基本概念

根节点(Root):没有父节点的节点。

父节点(Parent):直接指向其他节点的节点。

子节点(Child):被某个节点直接指向的节点。

叶子节点(Leaf):没有子节点的节点。

度(Degree):节点的子节点个数称为该节点的度,树的最大度称为树的度。

层次(Level):树中节点的层次从根节点开始,根节点为第 1 层,其子节点为第 2 层,以此类推。

高度(Height):节点高度:从当前节点到叶子节点的最长路径上的边数。树高度:根节点的高度。

深度(Depth):节点的深度是从根节点到当前节点的路径长度。

路径(Path):从一个节点到另一个节点经过的所有节点和边。

森林(Forest):由多棵互不相交的树组成的集合。

3. 树的分类

按节点连接关系分类

普通树:节点可以有任意数量的子节点。

// 树的节点定义
typedef struct TreeNode {
    int value;
    struct TreeNode **children; // 子节点指针数组
    int childCount;             // 子节点数
} TreeNode;

二叉树:每个节点最多有两个子节点,分别是左子节点和右子节点。
特殊的二叉树

// 定义二叉树节点
typedef struct TreeNode {
    int value;
    struct TreeNode* left;
    struct TreeNode* right;
} TreeNode;

搜索二叉树(BST):左子树的所有节点小于根节点,右子树的所有节点大于根节点。

// 插入
TreeNode* insertBST(TreeNode* root, int value) {
    if (!root) return createTreeNode(value);
    if (value < root->value)
        root->left = insertBST(root->left, value);
    else if (value > root->value)
        root->right = insertBST(root->right, value);
    return root;
}

// 搜索
TreeNode* searchBST(TreeNode* root, int value) {
    if (!root || root->value == value) return root;
    if (value < root->value)
        return searchBST(root->left, value);
    return searchBST(root->right, value);
}

平衡二叉树:左右子树高度差不超过 1。
满二叉树:所有层的节点数都达到最大值。
完全二叉树:所有层均满,最后一层的节点从左到右连续排列。
多叉树:每个节点可以有多个子节点,例如 N 叉树。
Trie 树:一种特殊的多叉树,用于快速存储和检索字符串。
红黑树、AVL树:自平衡二叉搜索树,用于保证操作的效率。

#include <stdio.h>
#include <stdlib.h>

// AVL 树节点定义
typedef struct AVLNode {
    int value;
    int height;
    struct AVLNode* left;
    struct AVLNode* right;
} AVLNode;

// 获取节点高度
int getHeight(AVLNode* node) {
    return node ? node->height : 0;
}

// 计算平衡因子
int getBalanceFactor(AVLNode* node) {
    return getHeight(node->left) - getHeight(node->right);
}

// 创建新节点
AVLNode* createAVLNode(int value) {
    AVLNode* node = (AVLNode*)malloc(sizeof(AVLNode));
    node->value = value;
    node->height = 1;
    node->left = NULL;
    node->right = NULL;
    return node;
}

// 右旋
AVLNode* rightRotate(AVLNode* y) {
    AVLNode* x = y->left;
    AVLNode* T = x->right;

    x->right = y;
    y->left = T;

    y->height = 1 + (getHeight(y->left) > getHeight(y->right) ? getHeight(y->left) : getHeight(y->right));
    x->height = 1 + (getHeight(x->left) > getHeight(x->right) ? getHeight(x->left) : getHeight(x->right));

    return x;
}

// 左旋
AVLNode* leftRotate(AVLNode* x) {
    AVLNode* y = x->right;
    AVLNode* T = y->left;

    y->left = x;
    x->right = T;

    x->height = 1 + (getHeight(x->left) > getHeight(x->right) ? getHeight(x->left) : getHeight(x->right));
    y->height = 1 + (getHeight(y->left) > getHeight(y->right) ? getHeight(y->left) : getHeight(y->right));

    return y;
}

// 插入节点
AVLNode* insertAVL(AVLNode* root, int value) {
    if (!root) return createAVLNode(value);

    if (value < root->value)
        root->left = insertAVL(root->left, value);
    else if (value > root->value)
        root->right = insertAVL(root->right, value);
    else
        return root;

    root->height = 1 + (getHeight(root->left) > getHeight(root->right) ? getHeight(root->left) : getHeight(root->right));
    int balance = getBalanceFactor(root);

    // 左左
    if (balance > 1 && value < root->left->value)
        return rightRotate(root);

    // 右右
    if (balance < -1 && value > root->right->value)
        return leftRotate(root);

    // 左右
    if (balance > 1 && value > root->left->value) {
        root->left = leftRotate(root->left);
        return rightRotate(root);
    }

    // 右左
    if (balance < -1 && value < root->right->value) {
        root->right = rightRotate(root->right);
        return leftRotate(root);
    }

    return root;
}

3.2 按数据组织方式分类

堆(Heap):完全二叉树,满足堆序性质。

并查集树:用于解决连通性问题,支持合并和查找操作。

4. 树的基本操作

遍历:深度优先遍历(DFS):前序遍历:根 → 左 → 右。中序遍历:左 → 根 → 右。后序遍历:左 → 右 → 根。广度优先遍历(BFS):层次遍历:按层次从上到下,从左到右依次访问节点。

插入:根据树的性质插入节点,例如在二叉搜索树中插入时需保持排序规则。

删除:移除指定节点后需调整树结构以保持性质,例如删除二叉搜索树节点时需替换为合适的继承节点。

查找:按树的规则查找特定节点,如二叉搜索树的查找效率为 O(log⁡n)O(\log n)O(logn)。

高度计算:递归或迭代计算节点的高度。

5. 树的存储方式

链式存储

每个节点通过指针指向其子节点和父节点。常见于动态树结构,如链表表示的二叉树。

typedef struct TreeNode {
    int value;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;

顺序存储:使用数组存储完全二叉树。左子节点的索引为 2i+1,右子节点的索引为 2i+2。

#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 100 // 最大节点数

typedef struct CompleteBinaryTree {
    int data[MAX_SIZE]; // 数组存储完全二叉树的节点值
    int size;           // 当前节点数
} CompleteBinaryTree;

// 初始化完全二叉树
void initTree(CompleteBinaryTree* tree) {
    tree->size = 0;
}

// 插入节点
void insertNode(CompleteBinaryTree* tree, int value) {
    if (tree->size >= MAX_SIZE) {
        printf("Tree is full. Cannot insert more nodes.\n");
        return;
    }
    tree->data[tree->size] = value;
    tree->size++;
}

// 获取左子节点索引
int getLeftChildIndex(int index) {
    return 2 * index + 1;
}

// 获取右子节点索引
int getRightChildIndex(int index) {
    return 2 * index + 2;
}

// 打印树的层序遍历
void printTree(CompleteBinaryTree* tree) {
    printf("Tree: ");
    for (int i = 0; i < tree->size; i++) {
        printf("%d ", tree->data[i]);
    }
    printf("\n");
}

// 打印节点的左右子节点
void printChildren(CompleteBinaryTree* tree, int index) {
    if (index >= tree->size) {
        printf("Invalid index\n");
        return;
    }

    printf("Node %d: ", tree->data[index]);
    int leftIndex = getLeftChildIndex(index);
    int rightIndex = getRightChildIndex(index);

    if (leftIndex < tree->size)
        printf("Left Child = %d ", tree->data[leftIndex]);
    else
        printf("Left Child = NULL ");

    if (rightIndex < tree->size)
        printf("Right Child = %d ", tree->data[rightIndex]);
    else
        printf("Right Child = NULL ");

    printf("\n");
}

int main() {
    CompleteBinaryTree tree;
    initTree(&tree);

    // 插入节点
    insertNode(&tree, 1);
    insertNode(&tree, 2);
    insertNode(&tree, 3);
    insertNode(&tree, 4);
    insertNode(&tree, 5);
    insertNode(&tree, 6);

    // 打印树
    printTree(&tree);

    // 打印某些节点的子节点
    printChildren(&tree, 0); // 根节点
    printChildren(&tree, 1); // 第二层左节点
    printChildren(&tree, 2); // 第二层右节点

    return 0;
}

6. 树的应用场景

  1. 二叉树:二叉搜索树(BST):高效查找、插入、删除。堆:优先队列的实现。
  2. Trie 树:快速字符串匹配、前缀查询。
  3. 红黑树、AVL 树:数据库索引、动态集合操作。
  4. 决策树:机器学习中的分类和回归算法。
  5. 表达式树:表达式求值。
  6. 文件系统:文件和目录的层次管理。

7. 树的优势与局限性

优势
  1. 自然表示分层结构,适合数据的分级存储。
  2. 支持高效的查找、插入和删除操作。
  3. 可扩展性强,灵活适应不同场景。
局限性
  1. 链式存储需要额外的指针开销。
  2. 树结构的实现较为复杂,维护特性(如平衡)需付出额外代价。
  3. 不适合随机访问,性能劣于数组。

8. 小结

树作为一种基础数据结构,在理论研究和实际工程中都具有重要地位,广泛应用于文件系统、数据库索引、路由表等场景。通过不同种类的树(如二叉树、AVL树、红黑树等),我们可以根据需求选择适合的结构来优化数据操作效率。掌握树的基本知识和实现方法,是深入学习算法与数据结构的重要一步。

二叉树及其变种:解决高效数据存储与操作问题。Trie 树:适合字符串处理场景。红黑树、AVL 树:优化动态数据的平衡性。掌握树的基本原理、特性及其适用场景,是深入理解算法与系统设计的关键。

三、堆

1、堆的定义与特性

堆(Heap)是一种特殊的基于树的抽象数据类型(特殊的完全二叉树),堆通常用于实现优先队列,在这种结构中,最高优先级的元素总是位于根节点。

堆满足以下特性:对于任意给定的节点i,如果Pi的父节点,那么P的键值(或元素)要么大于等于(最大堆, max-heap),要么小于等于(最小堆, min-heap)i的键值。

大顶堆(Max Heap):任意节点的值都不小于其子节点的值,堆顶为最大值。
小顶堆(Min Heap):任意节点的值都不大于其子节点的值,堆顶为最小值。

完全二叉树 (Complete Binary Tree): 堆通常以完全二叉树的形式实现,这意味着除了最底层外,所有层都是满的,并且最底层的节点尽可能靠左排列。

存储结构:通常使用数组来存储堆,逻辑上的父子节点关系可以通过索引计算:
父节点索引:i
左子节点索引:2i + 1
右子节点索引:2i + 2
若需要访问父节点:(i - 1) / 2

2、堆的基本操作

  1. 插入(Insert):在堆尾添加新元素。将新元素“上浮”(Bubble Up),以保持堆的性质。

  2. 删除堆顶元素(Remove Top/Pop):将堆顶元素替换为最后一个元素。删除最后一个元素。将新堆顶“下沉”(Bubble Down),以保持堆的性质。

  3. 堆化(Heapify):将无序数组调整为堆结构。从最后一个非叶节点开始,对每个节点执行下沉操作。

  4. 堆排序(Heap Sort):利用堆性质进行排序。大顶堆适用于升序排序,小顶堆适用于降序排序。

  5. 获取最大/最小元素 (Get-Max/Get-Min): 对于最大堆或最小堆,根节点就是具有最大或最小键值的元素。

3、应用场景

优先队列 (Priority Queue): 堆是最常用的优先队列实现方式之一。在优先队列中,每次取出的都是具有最高优先级的元素。

堆排序 (HeapSort): 一种高效的排序算法,时间复杂度为O(n log n)。

选择问题 (Selection Problem): 如寻找数组中的第k大或第k小元素。

图算法: 例如Dijkstra算法和Prim算法,其中需要频繁访问或更新最小权重的边。

内存管理: 某些操作系统使用堆来管理动态内存分配。

4、代码实现

1. 最大堆(大顶堆)
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 100

typedef struct {
    int data[MAX_SIZE];
    int size;
} MaxHeap;

// 初始化堆
void initHeap(MaxHeap* heap) {
    heap->size = 0;
}

// 上浮操作
void bubbleUp(MaxHeap* heap, int index) {
    while (index > 0) {
        int parent = (index - 1) / 2;
        if (heap->data[index] > heap->data[parent]) {
            // 交换
            int temp = heap->data[index];
            heap->data[index] = heap->data[parent];
            heap->data[parent] = temp;
            index = parent;
        } else {
            break;
        }
    }
}

// 插入元素
void insert(MaxHeap* heap, int value) {
    if (heap->size >= MAX_SIZE) {
        printf("Heap is full!\n");
        return;
    }
    heap->data[heap->size] = value;
    bubbleUp(heap, heap->size);
    heap->size++;
}

// 下沉操作
void bubbleDown(MaxHeap* heap, int index) {
    while (index * 2 + 1 < heap->size) {
        int leftChild = index * 2 + 1;
        int rightChild = index * 2 + 2;
        int largerChild = leftChild;

        if (rightChild < heap->size && heap->data[rightChild] > heap->data[leftChild]) {
            largerChild = rightChild;
        }

        if (heap->data[index] < heap->data[largerChild]) {
            // 交换
            int temp = heap->data[index];
            heap->data[index] = heap->data[largerChild];
            heap->data[largerChild] = temp;
            index = largerChild;
        } else {
            break;
        }
    }
}

// 删除堆顶元素
int removeTop(MaxHeap* heap) {
    if (heap->size == 0) {
        printf("Heap is empty!\n");
        return -1;
    }
    int top = heap->data[0];
    heap->data[0] = heap->data[--heap->size];
    bubbleDown(heap, 0);
    return top;
}

// 打印堆
void printHeap(MaxHeap* heap) {
    for (int i = 0; i < heap->size; i++) {
        printf("%d ", heap->data[i]);
    }
    printf("\n");
}

int main() {
    MaxHeap heap;
    initHeap(&heap);

    insert(&heap, 10);
    insert(&heap, 20);
    insert(&heap, 15);
    insert(&heap, 30);
    insert(&heap, 25);

    printf("Heap elements: ");
    printHeap(&heap);

    printf("Removed top: %d\n", removeTop(&heap));
    printf("Heap after removal: ");
    printHeap(&heap);

    return 0;
}

2. 堆排序

void heapSort(int arr[], int n) {
    // 构建大顶堆
    for (int i = n / 2 - 1; i >= 0; i--) {
        bubbleDown(arr, n, i);
    }

    // 逐步将堆顶移到数组末尾,缩小堆的范围
    for (int i = n - 1; i > 0; i--) {
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;
        bubbleDown(arr, i, 0); // 对新的堆顶进行下沉
    }
}

5、小结

堆是一种功能强大的数据结构,适用于优先级处理和高效排序。它利用了完全二叉树的特性,结合数组实现,简化了存储和计算,同时保持了良好的时间复杂度,是许多算法和系统开发中的核心工具。

总结来说,队列、树和堆都是非常重要的基础数据结构,各自在不同的场景中发挥着不可替代的作用。队列适合处理按顺序排队的任务,树则为数据的组织和快速检索提供了有效的解决方案,而堆则常用于需要优先级排序的场合。通过深入理解它们的实现与应用,开发者可以根据实际需求选择最适合的结构,提高程序的性能与可靠性。掌握这些数据结构,不仅能提升代码的效率,还能为进一步学习复杂算法打下坚实的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/928360.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java 整合图片处理相关一套:传输、保存、重命名、删除、AI图片文字识别、图片映射、vue3裁剪、设置黑白色、设置负片、提高照片品质

目录 一、vue3 axios spring boot文件传输 二、Java图片保存到本地 三、Java 本地图片重命名 四、Java 删除本地图片 五、 JavaAI图片文字识别 六、Java映射图片地址&#xff0c;前端直接访问图片 七、vue3 图片裁剪 八、Java 设置图片黑白色 九、Java 设置图片负片 …

web三、 window对象,延时器,定时器,时间戳,location对象(地址),本地存储-localStorage,数组去重new Set

一、 window对象 window对象 是一个全局对象&#xff0c;也可以说是JavaScript中的 顶级对象 像document、alert()、console.log()这些都是window的属性&#xff0c;基本BOM的属性和方法都是window的 所有通过 var定义 在全局作用域中的 变量 、 函数 都会变成window对象的属…

【AI系统】昇腾异构计算架构 CANN

昇腾异构计算架构 CANN 本文将介绍昇腾 AI 异构计算架构 CANN&#xff08;Compute Architecture for Neural Networks&#xff09;&#xff0c;这是一套为高性能神经网络计算需求专门设计和优化的架构。CANN 包括硬件层面的达芬奇架构和软件层面的全栈支持&#xff0c;旨在提供…

Spark和MapReduce场景应用和区别

文章目录 Spark和MapReduce场景应用和区别一、引言二、MapReduce和Spark的应用场景1. MapReduce的应用场景2. Spark的应用场景 三、MapReduce和Spark的区别1. 内存使用和性能2. 编程模型和易用性3. 实时计算支持 四、使用示例1. MapReduce代码示例2. Spark代码示例 五、总结 Sp…

CSS函数

目录 一、背景 二、函数的概念 1. var()函数 2、calc()函数 三、总结 一、背景 今天我们就来说一说&#xff0c;常用的两个css自定义属性&#xff0c;也称为css函数。本文中就成为css函数。先来看一下官方对其的定义。 自定义属性&#xff08;有时候也被称作CSS 变量或者级…

【C语言】递归的内存占用过程

递归 递归是函数调用自身的一种编程技术。在C语言中&#xff0c;递归的实现会占用内存栈&#xff08;Call Stack&#xff09;&#xff0c;每次递归调用都会在栈上分配一个新的 “栈帧&#xff08;Stack Frame&#xff09;”&#xff0c;用于存储本次调用的函数局部变量、返回地…

大数据新视界 -- 大数据大厂之 Hive 数据压缩算法对比与选择(下)(20 / 30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

【golang】单元测试,以及出现undefined时的解决方案

单元测试 要对某一方法进行测试时&#xff0c;例如如下这一简单减法函数&#xff0c;选中函数名后右键->转到->测试 1&#xff09;Empty test file 就是一个空文件&#xff0c;我们可以自己写测试的逻辑 但是直接点绿色箭头运行会出问题&#xff1a; 找不到包。我们要在…

ETL工具观察:ETLCloud与MDM是什么关系?

一、什么是ETLCloud ETLCloud数据中台是一款高时效的数据集成平台&#xff0c;专注于解决大数据量和高合规要求环境下的数据集成需求。 工具特点 1.离线与实时集成&#xff1a;支持离线数据集成&#xff08;ETL、ELT&#xff09;和变更数据捕获&#xff08;CDC&#xff09;实…

人形机器人训练、机器臂远程操控、VR游戏交互、影视动画制作,一副手套全部解决!

广州虚拟动力基于自研技术推出了多节点mHand Pro动捕数据手套&#xff0c;其最大的特点就是功能集成与高精度捕捉&#xff0c;可以用于人形机器人训练、机器臂远程操控、VR游戏交互、影视动画制作等多种场景。 一、人形机器人训练 mHand Pro动捕数据手套双手共装配16个9轴惯性…

IDL学习笔记(二)IDL处理卫星数据

IDL处理卫星数据 HDF文件数据集属性通用属性 常用HDF4操作函数常用的HDF5操作函数读取HDF文件的一般步骤 HDF4文件读取-----数据信息查询HDF4文件读取示例-----目标数据TIFF输出提取modis产品中数据&#xff0c;与某一点经纬度最接近的点有效结果&#xff0c;并按每行内容为日期…

动态规划-----路径问题

动态规划-----路径问题 下降最小路径和1&#xff1a;状态表示2&#xff1a;状态转移方程3 初始化4 填表顺序5 返回值6 代码实现 总结&#xff1a; 下降最小路径和 1&#xff1a;状态表示 假设&#xff1a;用dp[i][j]表示&#xff1a;到达[i,j]的最小路径 2&#xff1a;状态转…

Redis+Caffeine 多级缓存数据一致性解决方案

RedisCaffeine 多级缓存数据一致性解决方案 背景 之前写过一篇文章RedisCaffeine 实现两级缓存实战&#xff0c;文章提到了两级缓存RedisCaffeine可以解决缓存雪等问题也可以提高接口的性能&#xff0c;但是可能会出现缓存一致性问题。如果数据频繁的变更&#xff0c;可能会导…

2024年大热,Access平替升级方案,也适合Excel用户

欢迎各位看官&#xff0c;您来了&#xff0c;就对了&#xff01; 您多半是Access忠实粉丝&#xff0c;至少是excel用户&#xff0c;亦或是WPS用户吧。那就对了&#xff0c;今天的分享肯定对您有用。 本文1100字&#xff0c;阅读时长2分50秒&#xff01; 现实总是不尽人意&am…

解决idea使用maven打包时无法将本地lib库文件和resource目录中的资源文件打包进jar文件的问题!!!

一、问题复现 1&#xff09;项目结构如下 我们看到项目中手动添加了本地lib资源&#xff0c;同时bootspring的配置文件和mapper文件也放在了resouces目录中。 2&#xff09;上述结构的项目在使用maven打包时&#xff0c;最终生成的jar文件中将不包含lib库文件&#xff0c;甚…

PKO-LSSVM-Adaboost班翠鸟优化最小二乘支持向量机结合AdaBoost分类模型

PKO-LSSVM-Adaboost班翠鸟优化最小二乘支持向量机结合AdaBoost分类模型 目录 PKO-LSSVM-Adaboost班翠鸟优化最小二乘支持向量机结合AdaBoost分类模型效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.PKO-LSSVM-Adaboost班翠鸟优化最小二乘支持向量机结合AdaBoost分类模…

FPGA实战篇(呼吸灯实验)

1.呼吸灯简介 呼吸灯采用 PWM 的方式&#xff0c;在固定的频率下&#xff0c;通过调整占空比的方式来控制 LED 灯亮度的变化。 PWM&#xff08;Pulse Width Modulation &#xff09;&#xff0c;即脉冲宽度调制&#xff0c;它利用微处理器输出的 PWM 信号&#xff0c;实现对…

家政小程序开发,打造便捷家政生活小程序

目前&#xff0c;随着社会人就老龄化和生活压力的加重&#xff0c;家政服务市场的需求正在不断上升&#xff0c;家政市场的规模也正在逐渐扩大&#xff0c;发展前景可观。 在市场快速发展的影响下&#xff0c;越来越多的企业开始进入到市场中&#xff0c;同时家政市场布局也发…

《Python基础》之Pandas库

目录 一、简介 二、Pandas的核心数据结构 1、Series 2、DataFrame 三、数据读取与写入 1、数据读取 2、数据写入 四、数据清洗与处理 1、处理缺失值 2、处理重复值 3、数据转换 五、数据分析与可视化 1、统计描述 2、分组聚合 3、数据可视化 六、高级技巧 1、时…

Elasticsearch在liunx 中单机部署

下载配置 1、下载 官网下载地址 2、上传解压 tar -zxvf elasticsearch-XXX.tar.gz 3、新建组和用户 &#xff08;elasticsearch 默认不允许root账户&#xff09; #创建组 es groupadd es #新建用户 useradd ryzhang -g es 4、更改文件夹的用户权限 chown -R ryzhang …