当前位置: 首页 > news >正文

栈与队列及其基础应用

一.栈

1.栈的定义

栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。其结构可以参考羽毛球筒

在现实中我们通常用栈解决对称性问题,如经典的括号匹配问题。

 2.栈的实现

由上结构图可看出,我们用一个指针bottom指向栈底元素,一个指针top指向栈顶元素的下一个位置。实现栈可以用数组。

typedef int STDataType;
typedef struct Stack {
    STDataType *a;//一个动态创建的数组
    int top;//栈顶数据的下个位置,也是当前栈内元素个数
    int capacity;//当前栈的最大数据容量
} ST;

为了实现栈的增删改查等功能,我们可以定义若干接口

//初始化
void StackInit(ST *pst);
//销毁
void StackDestroy(ST *pst);
//入栈
void StackPush(ST *pst, STDataType x);
//出栈
void StackPop(ST *pst);
//获取栈顶元素
STDataType StackTop(ST* pst);
//获取栈是否为空
bool StackEmpty(ST* pst);
//获取栈中元素的个数
int StackSize(ST *pst);

现在对这些接口实现。

#include "Stack.h"
//注意top的指向会影响到他的初始值
//初始化
void StackInit(ST* pst) {
	assert(pst != NULL);
	pst->a= NULL;
    //top指向栈顶元素的下一个空间,即表示当前栈内没有元素
    pst->top = 0;
    pst->capacity = 0;
}
//销毁
void StackDestroy(ST* pst) {
    assert(pst != NULL);
    free(pst->a);
    pst->top=pst->capacity=0;
}
//入栈
//先判断当前空间是否足够,不够就动态开辟
//将入栈元素的值x赋予top,更新top位置
void StackPush(ST* pst, STDataType x) {
    assert(pst != NULL);
    if (pst->top == pst->capacity) {
        int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
        STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
        if (tmp == NULL) {
            perror("realloc fail");
            return;
        }
    }
    pst->a[pst->top] = x;
    pst->top++;
}

//出栈
//从栈顶出元素,直接令top--
void StackPop(ST* pst){
    assert(pst != NULL);
    pst->top--;
}
//获取栈顶元素
//top为栈顶元素下个位置,返回top-1处值即可
STDataType StackTop(ST* pst) {
    assert(pst);
    assert(pst->top != 0);

    return pst->a[pst->top - 1];
}
//获取栈是否为空
bool StackEmpty(ST* pst) {
    assert(pst != NULL);
    return pst->top == 0;
}
//获取栈中元素的个数
int StackSize(ST* pst) {
    assert(pst != NULL);
    return pst->top;
}

二.队列

1.队列的定义

队列也是一种特殊的线性表,其只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头

在实际应用中我们常用队列解决公平性问题。如排队取号,以及并发编程中锁的竞争分配问题。 

队列由于其队尾插入,队头删除的特性,若使用数组实现会比较困难:无论是插入还是删除,都需要考虑空间大小和数据移动的问题,因此在这里我仅展示用链表实现。因此队列的功能我们仅对链表进行尾插头删即可

声明队列节点结构

typedef int QDataType;

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

 为了避免次次寻找队头队尾,直接定义另一个结构存储头尾指针

typedef struct Queue
{
	QNode* phead;//队头
	QNode* ptail;//队尾
	int size;//队列数据数量
}Queue;

要实现的接口

void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);

// 队尾插入
void QueuePush(Queue* pq, QDataType x);
// 队头删除
void QueuePop(Queue* pq);

// 取队头和队尾的数据
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);

int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);

 实现

#include"Queue.h"

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}

void QueueDestroy(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);

		cur = next;
	}

	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

// 队尾插入
//申请节点,插入节点后更新size
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);

	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}

	newnode->next = NULL;
	newnode->val = x;

	if (pq->ptail == NULL)//若此时队列没有节点
	{
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}

	pq->size++;
}

// 队头删除
//存头节点下个位置的节点
//释放头节点位置节点
//更新头节点和size
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->size != 0);

	/*QNode* next = pq->phead->next;
	free(pq->phead);
	pq->phead = next;

	if (pq->phead == NULL)
		pq->ptail = NULL;*/

	// 一个节点
	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else // 多个节点
	{
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}

	pq->size--;
}
//输出队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->phead);

	return pq->phead->val;
}

//输出队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->ptail);

	return pq->ptail->val;
}

//求当前队列的数据个数
int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

//当前队列数据个数0则为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->size == 0;
}

 

三.栈和队列的简单应用

1.用双栈实现队列

该问题的核心是:如何用后进先出的结构,实现一个先进先出的结构。这个核心会同时影响到插入和删除的设计逻辑。

不妨先大致分析一下出队的逻辑

 

我们向Stack1中插入数据[1,2,3]。若要进行出队,应该出最先入队的1

然而在Stack1中,1显然位于栈底,想要将其最先出队,不难观察出:

我们只要将Stack1中数据以此出栈到Stack2中即可。

 此时将Stack2的栈顶出栈,就完成了出队的操作。

typedef struct{
    int*a;
    int top;
    int capacity;
}Stack;

void stackInit(Stack*pst){
    assert(pst!=NULL);
    pst->top=pst->capacity=0;
    pst->a=NULL;
}

void stackPush(Stack*pst,int x){
    assert(pst!=NULL);
    if(pst->top==pst->capacity){
        int newcapacity=pst->capacity==0?4:pst->capacity*2;
        int *tmp=(int*)realloc(pst->a,newcapacity*sizeof(int));
        pst->a=tmp;
        pst->capacity=newcapacity;
    }
    
    
    pst->a[pst->top]=x;
    pst->top++;
}

void stackPop(Stack*pst){
    assert(pst!=NULL);
    pst->top--;

}

int stackTop(Stack*pst){
    return pst->a[pst->top-1];
}

bool stackEmpty(Stack*pst){
    return pst->top==0;
}

void stackFree(Stack*pst){
    free(pst->a);
    pst->a=NULL;
}
//至此都是栈的结构和接口,因为C语言库没有内置栈。。

//可以看到这里的队列由双栈实现
typedef struct {
    Stack stack1;
    Stack stack2;
} MyQueue;

//建队,即将队中的两个栈初始化即可
MyQueue* myQueueCreate() {
   MyQueue*obj=(MyQueue*)malloc(sizeof(MyQueue));
   stackInit(&(obj->stack1));
   stackInit(&(obj->stack2));
   return obj;
}

int myQueuePop(MyQueue* obj){
    // 当stack2为空时,转移所有元素
    if(stackEmpty(&obj->stack2)){
        while(!stackEmpty(&obj->stack1)){
            int tmp = stackTop(&obj->stack1);
            stackPush(&obj->stack2, tmp);
            stackPop(&obj->stack1);
        }
    }
    int x = stackTop(&obj->stack2);
    stackPop(&obj->stack2);
    return x;
}
void myQueuePush(MyQueue* obj, int x){
    stackPush(&obj->stack1,x);
}
int myQueuePeek(MyQueue* obj) {
    if (stackEmpty(&obj->stack2)) {
        // 循环转移所有元素
        while (!stackEmpty(&obj->stack1)) {
            int tmp = stackTop(&obj->stack1);
            stackPush(&obj->stack2, tmp);
            stackPop(&obj->stack1);
        }
    }
    return stackTop(&obj->stack2); // 正确返回队首元素
}

bool myQueueEmpty(MyQueue* obj) {
    return stackEmpty(&obj->stack1)&&stackEmpty(&obj->stack2);
}

void myQueueFree(MyQueue* obj) {
    stackFree(&obj->stack1);
    stackFree(&obj->stack2);
}

这段代码就是上面提到要实现出队时,将栈满的元素一个个入栈到栈空的栈中

a.取stack1栈顶元素值

b.将stack1栈顶元素入空栈stack2

c.将当前stack1栈顶元素出栈,更新新的栈顶元素

由此直至stack1变为空栈,stack2栈满,stack1的栈底为stack2的栈顶。

 while(!stackEmpty(&obj->stack1)){
            int tmp = stackTop(&obj->stack1);
            stackPush(&obj->stack2, tmp);
            stackPop(&obj->stack1);
        }

 

然而上述代码存在一定的问题。 

int myQueuePop(MyQueue* obj){
    // 当stack2为空时,转移所有元素
    if(stackEmpty(&obj->stack2)){
        while(!stackEmpty(&obj->stack1)){
            int tmp = stackTop(&obj->stack1);
            stackPush(&obj->stack2, tmp);
            stackPop(&obj->stack1);
        }
    }
    int x = stackTop(&obj->stack2);
    stackPop(&obj->stack2);
    return x;
}
void myQueuePush(MyQueue* obj, int x){
    stackPush(&obj->stack1,x);
}
int myQueuePeek(MyQueue* obj) {
    if (stackEmpty(&obj->stack2)) {
        // 循环转移所有元素
        while (!stackEmpty(&obj->stack1)) {
            int tmp = stackTop(&obj->stack1);
            stackPush(&obj->stack2, tmp);
            stackPop(&obj->stack1);
        }
    }
    return stackTop(&obj->stack2); // 正确返回队首元素
}

在进行转移元素的操作时,我们默认向空栈中转移。在初次创建栈时空栈即为Stack2(因为上述接口中首先向Stack1插入元素),但若测试用例中对同一个myQueue进行出队时,此时的空栈又变成了Stack1。这里建议使用if-else判断空栈或者用假设法判定空栈,否则可能无法通过某些测试用例。

2.用两个队列实现栈

 

这题的思想其实与第一个完全一致。在这里直接放出代码,并解决以上提到的问题 

#define LEN 20
typedef struct queue {
    int *data;
    int head;
    int rear;
    int size;
} Queue;

typedef struct {
    Queue *queue1, *queue2;
} MyStack;

Queue *initQueue(int k) {
    Queue *obj = (Queue *)malloc(sizeof(Queue));
    obj->data = (int *)malloc(k * sizeof(int));
    obj->head = -1;
    obj->rear = -1;
    obj->size = k;
    return obj;
}

void enQueue(Queue *obj, int e) {
    if (obj->head == -1) {
        obj->head = 0;
    }
    obj->rear = (obj->rear + 1) % obj->size;
    obj->data[obj->rear] = e;
}

int deQueue(Queue *obj) {
    int a = obj->data[obj->head];
    if (obj->head == obj->rear) {
        obj->rear = -1;
        obj->head = -1;
        return a;
    }
    obj->head = (obj->head + 1) % obj->size;
    return a;
}

int isEmpty(Queue *obj) {
    return obj->head == -1;
}

MyStack *myStackCreate() {
    MyStack *obj = (MyStack *)malloc(sizeof(MyStack));
    obj->queue1 = initQueue(LEN);
    obj->queue2 = initQueue(LEN);
    return obj;
}

void myStackPush(MyStack *obj, int x) {
    if (isEmpty(obj->queue1)) {
        enQueue(obj->queue2, x);
    } else {
        enQueue(obj->queue1, x);
    }
}

int myStackPop(MyStack *obj) {
    if (isEmpty(obj->queue1)) {
        while (obj->queue2->head != obj->queue2->rear) {
            enQueue(obj->queue1, deQueue(obj->queue2));
        }
        return deQueue(obj->queue2);
    }
    while (obj->queue1->head != obj->queue1->rear) {
        enQueue(obj->queue2, deQueue(obj->queue1));
    }
    return deQueue(obj->queue1);
}

int myStackTop(MyStack *obj) {
    if (isEmpty(obj->queue1)) {
        return obj->queue2->data[obj->queue2->rear];
    }
    return obj->queue1->data[obj->queue1->rear];
}

bool myStackEmpty(MyStack *obj) {
    if (obj->queue1->head == -1 && obj->queue2->head == -1) {
        return true;
    }
    return false;
}

void myStackFree(MyStack *obj) {
    free(obj->queue1->data);
    obj->queue1->data = NULL;
    free(obj->queue1);
    obj->queue1 = NULL;
    free(obj->queue2->data);
    obj->queue2->data = NULL;
    free(obj->queue2);
    obj->queue2 = NULL;
    free(obj);
    obj = NULL;
}

3.设计循环队列

循环队列我们可以理解为一个座位有限的图书馆,当座位满时就不能再坐人,但是一旦有人离开,就可以再来一个人坐在那个空位。

我们可以直接用笔画一画这个结构,在这里rear为队尾元素的下一个位置。假设这是一个可以存放五个数据的循环队列。先对这个队列进行入队。

 

接着开始出队 (注意队列从队尾入队,队头出队)

其实从刚刚插入的图我们就能发现一个比较大的问题,当栈空和栈满时的front指针和rear指针指向的都是同一个位置。。。他有一个专门的称为,叫假溢出

为了解决假溢出,可以对要存储k个数据的循环队列,初始化k+1个空间。

若此时的k为4,开辟k+1个元素。此时的队空队满如下:

直接来做这道题 

typedef struct {
    int front;//指向队头
    int rear;//指向队尾的下一个元素
    int capacity;//空间容量
    int *elements;//用数组实现循环队列
} MyCircularQueue;

//建立存k个数据的循环队列,这里开辟的空间为k+1
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue *obj = (MyCircularQueue *)malloc(sizeof(MyCircularQueue));
    obj->capacity = k + 1;
    obj->rear = obj->front = 0;
    obj->elements = (int *)malloc(sizeof(int) * obj->capacity);
    return obj;
}

//入队,成功return true
//若此时队满,直接return false
//这时判断队满的条件是rear的下一个元素为队头
//注意这里的取模操作,是为了消除rear+1大于capacity,并且也是让rear和front循环起来的基本操作
//更新rear位置
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if ((obj->rear + 1) % obj->capacity == obj->front) {
        return false;
    }
    obj->elements[obj->rear] = value;
    obj->rear = (obj->rear + 1) % obj->capacity;
    return true;
}

//出队,成功return true
//若此时队空,直接return false
//更新front位置
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (obj->rear == obj->front) {
        return false;
    }
    obj->front = (obj->front + 1) % obj->capacity;
    return true;
}

//求队头元素
//若队空,return
//返回front处值
int myCircularQueueFront(MyCircularQueue* obj) {
    if (obj->rear == obj->front) {
        return -1;
    }
    return obj->elements[obj->front];
}

//求队尾元素
//若队空,return
//rear为队尾元素的下一个,因此返回rear-1处位置即可
//但此时rear恰好为0,为让其正确指向队尾元素,-1加上capacity再模capacity
int myCircularQueueRear(MyCircularQueue* obj) {
    if (obj->rear == obj->front) {
        return -1;
    }
    return obj->elements[(obj->rear - 1 + obj->capacity) % obj->capacity];
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->rear == obj->front;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->rear + 1) % obj->capacity == obj->front;
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->elements);
    free(obj);
}

http://www.dtcms.com/a/122376.html

相关文章:

  • 【Kafka基础】topic命令行工具kafka-topics.sh:基础操作命令解析
  • STM32低功耗
  • 数据结构--堆
  • 软件测试之功能测试详解
  • C++语法学习之路
  • Mac监控新风尚:酷炫界面,性能监控更直观!
  • 数字图像处理作业4
  • SQLite 中日期型数据定义及处理(Delphi 版本)
  • IDEA :物联网ThingsBoard-gateway配置,运行Python版本,连接thingsboard,接入 MQTT 设备
  • [ACM_1] 输入输出 | 多行 | 多组 | getline(cin,s) | cin处理
  • 【MySQL】——事务的隔离性
  • Dubbo的简单介绍
  • 数据分析-Excel-学习笔记Day1
  • LeetCode Hot100 刷题笔记(2)—— 子串、普通数组、矩阵
  • Ubuntu22.04——YOLOv8模型训练到RK3588设备部署和推理
  • 实现抗隐私泄漏的AI人工智能推理
  • Linux进程控制:fork、exit与waitpid的江湖恩怨
  • C# 根据指定路径、文件格式、创建日期清理文件夹内文件,包括子目录
  • 从Transformer到世界模型:AGI核心架构演进
  • 微信小程序 -- 原生封装table
  • UV安装与使用
  • asp.net core 项目发布到 IIS 服务器
  • 场外期权只适合上涨行情吗?
  • CSS语言的游戏AI
  • ResNet改进(18):添加 CPCA通道先验卷积注意力机制
  • 从个人博客到电商中台:EdgeOne Pages的MCP Server弹性架构×DeepSeek多场景模板实战解析
  • 1.VTK 使用CMakeLists
  • Linux 编程中的 I/O 复用
  • Element UI 设置 el-table-column 宽度 width 为百分比无效
  • React九案例中