数据结构 | 栈和队列

文章目录

  • 栈和队列
    • 1. 栈:后进先出(LIFO)的数据结构
      • 1.1 概念与结构
      • 1.2 栈的实现
    • 2. 队列:先进先出(FIFO)的数据结构
      • 2.1 概念与结构
      • 2.2 队列的实现
    • 3. 栈和队列算法题
      • 3.1 有效的括号
      • 3.2 用队列实现栈
      • 3.3 用栈实现队列
      • 3.4 设计循环队列
    • 结论

栈和队列

在计算机科学中,栈和队列是两种基本且重要的数据结构,它们在处理数据存储和访问顺序方面有着独特的规则和应用。本文将详细介绍栈和队列的概念、结构、实现方式,并通过几个经典的算法题目来展示它们的实际应用。

1. 栈:后进先出(LIFO)的数据结构

1.1 概念与结构

栈是一种特殊的线性表,它只允许在一端(称为栈顶)进行插入和删除操作。另一端称为栈底。栈中的数据元素遵循后进先出(LIFO)的原则,即最后进入的数据项最先被移除。

  • 压栈(进栈/入栈):数据插入操作在栈顶进行。
  • 出栈:数据删除操作也在栈顶进行。

1.2 栈的实现

栈可以使用数组或链表来实现,但数组由于在尾部插入数据的效率较高,通常更受青睐。以下是栈的基本操作的C语言实现:

#include 
#include 
#include 

typedef int STDataType;

typedef struct Stack {
    STDataType* array;
    int top;
    int capacity;
} ST;

// 初始化栈
void STInit(ST* ps, int capacity) {
    ps->array = (STDataType*)malloc(sizeof(STDataType) * capacity);
    ps->top = -1;
    ps->capacity = capacity;
}

// 销毁栈
void STDestroy(ST* ps) {
    free(ps->array);
    ps->array = NULL;
    ps->top = -1;
    ps->capacity = 0;
}

// 入栈
void STPush(ST* ps, STDataType x) {
    if (ps->top >= ps->capacity - 1) {
        // 栈满,扩容或者报错
        return;
    }
    ps->array[++ps->top] = x;
}

// 出栈
void STPop(ST* ps) {
    if (ps->top < 0) {
        // 栈空,报错
        return;
    }
    ps->top--;
}

// 取栈顶元素
STDataType STTop(ST* ps) {
    if (ps->top < 0) {
        // 栈空,报错
        return -1;
    }
    return ps->array[ps->top];
}

// 获取栈中有效元素个数
int STSize(ST* ps) {
    return ps->top + 1;
}

// 栈是否为空
bool STEmpty(ST* ps) {
    return ps->top < 0;
}

2. 队列:先进先出(FIFO)的数据结构

2.1 概念与结构

队列是一种只允许在一端(称为队尾)进行插入操作,在另一端(称为队头)进行删除操作的线性表。队列遵循先进先出(FIFO)的原则。

  • 入队列:数据插入操作在队尾进行。
  • 出队列:数据删除操作在队头进行。

2.2 队列的实现

队列通常使用链表实现,因为如果使用数组,出队操作(尤其是在数组头部)的效率会较低。以下是队列的基本操作的C语言实现:

typedef int QDataType;

typedef struct QueueNode {
    QDataType val;
    struct QueueNode* next;
} QNode;

typedef struct Queue {
    QNode* front;
    QNode* rear;
    int size;
} Queue;

// 初始化队列
void QueueInit(Queue* pq) {
    pq->front = pq->rear = (QNode*)malloc(sizeof(QNode));
    if (pq->rear) {
        pq->rear->next = NULL;
    }
    pq->size = 0;
}

// 销毁队列
void QueueDestroy(Queue* pq) {
    QNode* current = pq->front;
    QNode* next;

    while (current != NULL) {
        next = current->next;
        free(current);
        current = next;
    }

    pq->front = pq->rear = NULL;
    pq->size = 0;
}

// 入队列,队尾添加元素
void QueuePush(Queue* pq, QDataType x) {
    QNode* newNode = (QNode*)malloc(sizeof(QNode));
    if (newNode == NULL) {
        return; // 内存分配失败
    }
    newNode->val = x;
    newNode->next = NULL;

    if (pq->rear) {
        pq->rear->next = newNode;
    }
    pq->rear = newNode;

    if (pq->front->next == NULL) { // 队列原来为空
        pq->front->next = newNode;
    }

    pq->size++;
}

// 出队列,队头删除元素
void QueuePop(Queue* pq) {
    if (QueueEmpty(pq)) {
        return; // 队列为空,无法出队
    }

    QNode* temp = pq->front->next;
    QNode* prev = pq->front;

    if (temp == pq->rear) { // 只有一个元素
        pq->front = pq->rear = NULL;
    } else {
        pq->front->next = temp->next;
        if (temp == pq->rear) {
            pq->rear = prev;
        }
    }

    free(temp);
    pq->size--;
}

// 取队头数据
QDataType QueueFront(Queue* pq) {
    if (QueueEmpty(pq)) {
        return -1; // 队列为空
    }
    return pq->front->next->val;
}

// 取队尾数据
QDataType QueueBack(Queue* pq) {
    if (QueueEmpty(pq)) {
        return -1; // 队列为空
    }
    return pq->rear->val;
}

// 队列判空
bool QueueEmpty(Queue* pq) {
    return (pq->size == 0);
}

// 队列有效元素个数
int QueueSize(Queue* pq) {
    return pq->size;
}

3. 栈和队列算法题

3.1 有效的括号

题目描述:给定一个只包括 '(', ')', '{', '}', '[', ']' 的字符串,判断字符串是否有效。

思路:使用栈来解决这个问题。遍历字符串中的每个字符,如果是左括号,就压入栈中。如果是右括号,则检查栈顶是否有对应的左括号,如果有,则弹出栈顶元素并继续遍历;如果没有,说明字符串无效。最后,如果栈为空,则字符串有效;否则,无效。

力扣题目链接:有效的括号

3.2 用队列实现栈

题目描述:使用队列实现栈的下列操作:push、pop、top 和 empty。

思路:使用两个队列来模拟栈的行为。对于入栈操作,直接添加到队列中;对于出栈操作,将除了最新的元素外的所有元素重新入队一次,最新的元素即为要出栈的元素。这样可以保证队列的 FIFO 特性来模拟栈的 LIFO 特性。

力扣题目链接:用队列实现栈

3.3 用栈实现队列

题目描述:使用栈实现队列的下列操作:push、pop、peek 和 empty。

思路:使用两个栈来实现队列的功能。入队操作直接压入一个栈中,出队操作则涉及到两个栈的配合:将一个栈中的所有元素依次弹出并压入另一个栈中,这样第一个弹出的元素就是队列的前端元素,可以作为出队操作的返回值。

力扣题目链接:用栈实现队列

3.4 设计循环队列

循环队列是一种特殊的队列,它可以高效地利用数组空间,避免队列满时的额外处理。循环队列可以通过Q.rear = Q.front来区分队空和队满的情况。

结论

栈和队列不仅是数据结构的基础,也是许多算法和应用的核心。理解它们的原理和实现对于任何软件开发者来说都是至关重要的。通过实际的编程练习和算法题目,我们可以进一步加深对这些概念的理解。

你可能感兴趣的:(数据结构与算法,数据结构,栈,队列,C语言)