栈和队列的实现及相关面试题

news2025/1/22 18:07:10

栈和队列

    • 概念与结构
    • 栈的功能
    • 栈的实现
      • 头文件Stack.h
        • 栈的结构体 Stack
      • 源文件Stack.c
        • 初始化 void StackInit(Stack* ps)
        • 压栈 void StackPush(Stack* ps, STDataType data)
        • 出栈 void StackPop(Stack* ps)
        • 返回栈顶的值 STDataType StackTop(Stack* ps)
        • 返回栈中元素的个数 int StackSize(Stack* ps)
        • 判断栈是否为空 bool StackEmpty(Stack* ps)
        • 销毁栈 void StackDestroy(Stack* ps)
  • 队列
    • 概念与结构
    • 队列的功能
    • 队列的实现
      • 头文件 Queue.h
        • 队列的结构体 Queue
      • 队列源文件 Queue.c
        • 初始化队列 void QueueInit(Queue* q)
        • 队尾入队列 void QueuePush(Queue* q, QDataType data)
        • 队头出队列 void QueuePop(Queue* q)
        • 获取队列头部元素 QDataType QueueFront(Queue* q)
        • 获取队列队尾元素 QDataType QueueBack(Queue* q)
        • 获取队列中有效元素个数 int QueueSize(Queue* q)
        • 判断队列是否为空 bool QueueEmpty(Queue* q)
        • 销毁队列 void QueueDestroy(Queue* q)
  • 栈和队列的结合笔试题
      • 力扣20. 有效的括号
        • 解题思路
        • 具体步骤
      • 答案
        • 力扣20.有效的括号

概念与结构

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

栈的功能

1.进栈(压栈):栈的插入操作,入数据在栈顶,如下图所示:
在这里插入图片描述
2.出栈:栈的删除操作,出数据也在栈顶,如下图所示:
在这里插入图片描述

栈的实现

这里栈的实现,我们可以使用链表或者顺序表的结构来实现,这里我们使用顺序表的结构来实现,因为数组栈相较于链表栈更容易实现。

头文件Stack.h

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

typedef int STDataType;
typedef struct Stack
{
	STDataType* _a;
	int _top;		// 栈顶
	int _capacity;  // 容量 
}Stack;

void StackInit(Stack* ps);
void StackPush(Stack* ps, STDataType data);

void StackPop(Stack* ps);
STDataType StackTop(Stack* ps);

int StackSize(Stack* ps);
bool StackEmpty(Stack* ps);

void StackDestroy(Stack* ps);
栈的结构体 Stack
typedef int STDataType;

typedef struct Stack
{
	STDataType* _a;
	int _top;		// 栈顶
	int _capacity;  // 容量 
}Stack;

本次变量我们以int类型来进行充当栈的存储数据类型,所以利用typedefSTDataType进行命名
再创建一种结构体类型Stack,这里的Stack结构体与顺序表中的结构体相同,内含一个**STDataType数组类型的成员变量**,一个表示栈顶的下标的整型变量_top,还有一个表示栈空间大小的整型变量_capacity

源文件Stack.c

初始化 void StackInit(Stack* ps)
void StackInit(Stack* ps)
{
	assert(ps);
	ps->_a = NULL;
	ps->_capacity = 0;
	ps->_top = -1;
}

先断言确保程序的正常运行。
将数组指针置空以防止野指针的使用,再将数组空间_capacity置为0,栈的下标置为-1,因为此时栈中没有一个数组元素。

压栈 void StackPush(Stack* ps, STDataType data)
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);
	
	if (ps->_capacity == ps->_top + 1)
	{
		int newcapacity = !ps->_capacity ? 4 : 2 * ps->_capacity;
		STDataType* tmp = (STDataType*)realloc(ps->_a, sizeof(STDataType) * newcapacity);
		if(!tmp)
		{
			perror("realloc");
			return;
		}
		ps->_a = tmp;
		tmp = NULL;
		ps->_capacity = newcapacity;
	}

	ps->_top++;
	ps->_a[ps->_top] = data;
}

先断言确保程序的正常运行。
利用if语句对栈的内存空间进行检查,若空间不够就进行扩容,利用三目运算符进行扩容空间大小的选择判断,再利用realloc来为结构体中的数组重新分配空间,为防止分配失败,我们利用新命名的tmp来接收,即使分配失败我们也可以避免返回NULL来覆盖原有的数组ps->_a数据。
确保空间足够后,将栈的下标_top让自增1,再将数据放到数组的相应下标即可。

出栈 void StackPop(Stack* ps)
void StackPop(Stack* ps)
{
	assert(ps);
	assert(ps->_top > -1);
	ps->_top--;
}

先断言确保程序的正常运行,栈的下标为-1时数组中没有一个元素,不存在出栈。
这里的出栈只要让ps->_top自减1即可,因为我们是通过下标进行访问的。

返回栈顶的值 STDataType StackTop(Stack* ps)
STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->_top > -1);
	return ps->_a[ps->_top];
}

先断言确保程序的正常运行,栈的下标为-1时数组中没有一个元素,也就不存在栈顶的值了。
利用下标直接返回数组的值即可。

返回栈中元素的个数 int StackSize(Stack* ps)
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->_top + 1;
}

先断言确保程序的正常运行。
返回数组下标的值加1即可。

判断栈是否为空 bool StackEmpty(Stack* ps)
int StackEmpty(Stack* ps)
{
	assert(ps);
	return ps->_top == -1;
}

先断言确保程序的正常运行。
若为空则返回0,但栈顶下标为-1时,栈为空,所以返回 ps->_top == -1 这判断语句的值即可。

销毁栈 void StackDestroy(Stack* ps)
void StackDestroy(Stack* ps)
{
	assert(ps);
	free(ps->_a);
	ps->_a = NULL;
	ps->_capacity = 0;
	ps->_top = -1;
}

先断言确保程序的正常运行。
free栈中的数组ps->_a,再将ps->_capacityps->_top置为初始值即可。

队列

概念与结构

队列只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列要求先进先出
FIFO
(First In First Out)的方式。

队列的功能

  1. 入队列:进行插入操作的一端称为队尾。
  2. 出队列:进行删除操作的一端称为队头。

队列的实现

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
所以,在这里我们使用链表来实现队列。

头文件 Queue.h

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

typedef int QDataType;

typedef struct QListNode
{
	QDataType _data;
	struct QListNode* _next;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* _front;
	QNode* _rear;
}Queue;

void QueueInit(Queue* q);

void QueuePush(Queue* q, QDataType data);

void QueuePop(Queue* q);

QDataType QueueFront(Queue* q);

QDataType QueueBack(Queue* q);

int QueueSize(Queue* q);

bool QueueEmpty(Queue* q);

void QueueDestroy(Queue* q);
队列的结构体 Queue
typedef struct QListNode
{
	QDataType _data;
	struct QListNode* _next;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* _front;
	QNode* _rear;
}Queue;
  1. struct QListNode:定义一个链表结点的结构体。
  2. struct Queue:定义一个存放链表头结点_front与尾结点_rear的结构体,这就表示一个队列的结构体。

队列源文件 Queue.c

初始化队列 void QueueInit(Queue* q)
void QueueInit(Queue* q)
{
	assert(q);
	q->_front = q->_rear = NULL;
}

先断言确保程序的正常运行。
将队列中表示链表头结点与尾结点的指针都置为空即可,因为队列中没有任何数据。

队尾入队列 void QueuePush(Queue* q, QDataType data)
void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	QNode* node = (QNode*)malloc(sizeof(QNode));
	assert(node);
	node->_next = NULL;
	node->_data = data;
	if (q->_front == NULL)
	{
		q->_front = q->_rear = node;
		return;
	}
	q->_rear->_next = node;
	q->_rear = node;
}

先断言确保程序的正常运行。
定义一个node指针,指向malloc所申请的一块大小为QNode的空间,再断言node,以确保node指针的正常使用,再将其初始化即可。
利用if语句判断队列中的头结点是否为空,若为空则将刚刚申请的node赋给队列的头结点与尾结点,然后直接退出函数即可
若不为空,则向链表进行尾插,再将node指向尾结点的指针赋给q->_rear即可

队头出队列 void QueuePop(Queue* q)
void QueuePop(Queue* q)
{
	assert(q);
	assert(q->_front);

	if (q->_front == q->_rear)
	{
		free(q->_front);
		q->_front = q->_rear = NULL;
		return;
	}

	QNode* pcur = q->_front->_next;

	free(q->_front);
	q->_front = pcur;
}

先断言确保程序的正常运行,若q->_front为空,则不存在出队列这一说法。
利用if语句判断队列是否只有一个结点这一特殊情况,若为特殊情况,则先用free释放掉唯一的结点,再将q->_frontq->_rear置为空即可
若为正常情况,则进行单链表的头删。

获取队列头部元素 QDataType QueueFront(Queue* q)
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(q->_front);
	return q->_front->_data;
}

先断言确保程序的正常运行,若q->_front为空,则不存在获取队列头部元素这一说法。
若通过,直接返回头结点的值即可。

获取队列队尾元素 QDataType QueueBack(Queue* q)
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->_rear);
	return q->_rear->_data;
}

先断言确保程序的正常运行,若q->_rear为空,则不存在获取队列尾部元素这一说法。
若通过,直接返回尾结点的值即可。

获取队列中有效元素个数 int QueueSize(Queue* q)
int QueueSize(Queue* q)
{
	assert(q);
	QNode* pcur = q->_front;
	int x = 0;
	while (pcur)
	{
		x++;
		pcur = pcur->_next;
	}
	return x;
}

先断言确保程序的正常运行。
定义一个指向队列头结点的指针pcur和一个记录队列中有效元素的整型变量x,再利用while循环进行链表的遍历,最后在返回x即可

判断队列是否为空 bool QueueEmpty(Queue* q)
bool QueueEmpty(Queue* q)
{
	assert(q);
	return q->_front == NULL;
}

先断言确保程序的正常运行。
若队列中的头结点为空,则返回1,反之返回0

销毁队列 void QueueDestroy(Queue* q)
void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* pcur = q->_front;

	while (pcur)
	{
		QNode* del = pcur;
		pcur = pcur->_next;
		free(del);
	}

	q->_front = q->_rear = NULL;
}

先断言确保程序的正常运行。
再定义一个指向队列头结点的指针pcur,再利用while循环依次删除即可,最后将q->_frontq->_rear 置为空。

栈和队列的结合笔试题

力扣20. 有效的括号

题目如下:
在这里插入图片描述

解题思路

这道题目就很适合使用栈来解决,我们可以假定有一个" { [ ( ) ] } ( ) "这样的字符串 ,根据括号的顺序,我们可以先让指向字符串首元素的指针s遍历整个字符串,在遍历的同时把左括号'{' '[' '('放入栈中,当遇到右括号'}' ']' ')'这个时候就是利用栈判断的时候,根据逻辑在遇到右括号前,上一个遇到的一定是其相应的左括号,所以若栈顶不是右括号的相应括号或栈中为空,则一定不满足条件;若s遍历了整个字符串,且此时栈中为空时则说明整个字符串都满足条件。

在这里插入图片描述

具体步骤
  1. 先将我们上面写的栈的头文件与源文件的源代码复制过来
  2. 再新建一个栈并初始化,利用while循环遍历字符串,当字符s为左括号时将其压栈,若不为右括号,则进行判断,如果栈中为空,则说明不满足条件,若不为空,则定义一个字符变量top,将栈顶的值赋给top,再利用if循环判断括号是否同类对应,若不为对应,则销毁站并返回,代码如下:
    while(*s)
    {
        if(*s=='['||*s=='('||*s=='{')
        {
            StackPush(&st,*s);
        }
        else 
        {
			if(!StackEmpty(&st))
			{
				StackDestroy(&st);
            	return false;
			}
			char top = StackTop(&st);
			StackPop(&st);
            if((*s==']'&&top!='[')
            ||(*s==')'&&top!='(')
            ||(*s=='}'&&top!='{'))
            {
				StackDestroy(&st);
                return false;
            }
        }
        s++;
    }
  1. 如此循环,遍历每一个字符,遍历完全之后跳出循环,定义布尔变量ret若栈中为空,这说明整个字符串都满足条件返回1 ,反之返回 0
	bool ret=!StackEmpty(&st);
	StackDestroy(&st);
    return ret;

答案

代码如下:

力扣20.有效的括号

typedef char STDataType;
typedef struct Stack
{
	STDataType* _a;
	int _top;		
	int _capacity;  
}Stack;

void StackInit(Stack* ps);
void StackPush(Stack* ps, STDataType data);

void StackPop(Stack* ps);
STDataType StackTop(Stack* ps);

int StackSize(Stack* ps);
int StackEmpty(Stack* ps);

void StackDestroy(Stack* ps);


void StackInit(Stack* ps)
{
	assert(ps);
	ps->_a = NULL;
	ps->_capacity = 0;
	ps->_top = -1;
}

void StackPush(Stack* ps, STDataType data)
{
	assert(ps);
	
	if (ps->_capacity == ps->_top + 1)
	{
		int newcapacity = !ps->_capacity ? 4 : 2 * ps->_capacity;
		STDataType* tmp = (STDataType*)realloc(ps->_a, sizeof(STDataType) * newcapacity);
		assert(tmp);
		ps->_a = tmp;
		tmp = NULL;
		ps->_capacity = newcapacity;
	}

	ps->_top++;
	ps->_a[ps->_top] = data;
}

void StackPop(Stack* ps)
{
	assert(ps);
	assert(ps->_top > -1);
	ps->_top--;
}

STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->_top > -1);
	return ps->_a[ps->_top];
}

int StackSize(Stack* ps)
{
	assert(ps);
	return ps->_top + 1;
}

int StackEmpty(Stack* ps)
{
	assert(ps);
	return ps->_top > -1;
}

void StackDestroy(Stack* ps)
{
	assert(ps);
	free(ps->_a);
	ps->_a = NULL;
	ps->_capacity = 0;
	ps->_top = -1;
}

bool isValid(char* s) 
{
    Stack st;
    StackInit(&st);
    while(*s)
    {
        if(*s=='['||*s=='('||*s=='{')
        {
            StackPush(&st,*s);
        }
        else 
        {
			if(!StackEmpty(&st))
			{
				StackDestroy(&st);
            	return false;
			}
			char top = StackTop(&st);
			StackPop(&st);
            if((*s==']'&&top!='[')
            ||(*s==')'&&top!='(')
            ||(*s=='}'&&top!='{'))
            {
				StackDestroy(&st);
                return false;
            }
        }
        s++;
    }
	bool ret=!StackEmpty(&st);
	StackDestroy(&st);
    return ret;
}

好了,以上就为本期的全部内容喜欢请多多关注吧!!!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一个 不用充钱 也能让你变强的 VSCode 插件

今天给大家推荐一款不用充钱也能让你变强的 vscode 插件 通义灵码&#xff08;TONGYI Lingma&#xff09;&#xff0c;可以称之为 copilot 的替代甜品 &#x1f4aa; 什么是 通义灵码&#xff08;TONGYI Lingma&#xff09; 通义灵码&#xff08;TONGYI Lingma&#xff09;&am…

《向量数据库指南》——TruLens 用于语言模型应用跟踪和评估

TruLens 用于语言模型应用跟踪和评估 TruLens 是一个用于评估语言模型应用(如 RAG)的性能的开源库。通过 TruLens,我们还可以利用语言模型本身来评估输出、检索质量等。 构建语言模型应用时,多数人最关心的问题是 AI 幻觉(hallucination)。RAG 通过为语言模型提供检索上下文…

校招面试技巧:如何回答好一个问题

今天分享的主题是“如何回答好一个问题”。通过与同学们进行交流&#xff0c;发现一个比较普遍的现象&#xff0c;大家在回答问题时&#xff0c;时常会让面试官觉得自己在背八股&#xff0c;从而影响面试官的印象。那么我们应该怎么避免这种情况呢&#xff1f; 1.基础知识点 …

Redis - 订阅发布替换 Etcd 解决方案

为了减轻项目的中间件臃肿&#xff0c;由于我们项目本身就应用了 Redis&#xff0c;正好 Redis 的也具备订阅发布监听的特性&#xff0c;正好应对 Etcd 的功能&#xff0c;所以本次给大家讲解如何使用 Redis 消息订阅发布来替代 Etcd 的解决方案。接下来&#xff0c;我们先看 R…

解决Python Requests库中处理重定向时的多重Cookie问题

问题背景 在更新至f73bda06e9版本后&#xff0c;用户发现某些请求会引发CookieConflictError。具体来说&#xff0c;任何设置饼干且重定向到设置相同饼干的页面的请求都会引发CookieConflictError。 重现步骤 1、更新Requests至上述版本。 2、从中国以外的任何地方向baidu…

Java 设计模式——中介者模式

目录 1.概述2.结构3.案例实现3.1.抽象中介类3.2.抽象同事类3.3.具体同事类3.4.具体中介类3.5.测试 4.优缺点5.使用场景 1.概述 &#xff08;1&#xff09;一般来说&#xff0c;同事类之间的关系是比较复杂的&#xff0c;多个同事类之间互相关联时&#xff0c;他们之间的关系会…

Redis最新2023年面试题高级面试题及附答案解析(2)【Redis最新2023年面试题高级面试题及附答案解析-第三十九刊】

文章目录 Redis最新2023年面试题高级面试题及附答案解析(2)01、Redis 集群方案应该怎么做&#xff1f;都有哪些方案&#xff1f;02、Redis 的内存用完了会发生什么&#xff1f;03、怎么测试 Redis 的连通性&#xff1f;04、Redis 集群会有写操作丢失吗&#xff1f;为什么&#…

C/C++ 实现获取硬盘序列号

获取硬盘的序列号、型号和固件版本号&#xff0c;此类功能通常用于做硬盘绑定或硬件验证操作&#xff0c;通过使用Windows API的DeviceIoControl函数与物理硬盘驱动程序进行通信&#xff0c;发送ATA命令来获取硬盘的信息。 以下是该程序的主要功能和流程&#xff1a; 定义常量…

青年作家考公引热议,体制内可能不是你想的那样

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 阿福 运营 / SandLiu 卷圈 监制 / 姝琦 封面 / 姝琦Midjourney 产品统筹 / bobo 场地支持 / 声湃轩天津录音间 最近&#xff0c;班宇、陈春成、王苏辛三名青年作家出现在了武汉市文联所属事业单位专项招聘拟聘用人员名…

如何有效防止公司内部的信息泄露?

信息泄露对公司可能带来严重影响&#xff0c;因此采取一系列措施以确保信息安全至关重要。以下是一些建议&#xff1a; 部署综合的防泄密软件&#xff1a; 在公司内部&#xff0c;使用专业的防泄密软件如华企盾DSC系统&#xff0c;涵盖文件加密、U盘管控、桌面行为管理、日志审…

前端 react 面试题 (一)

文章目录 vue与react的区别。react的生命周期有哪些及它们的作用。setState是同步的还是异步的。如何更新数据后&#xff0c;立刻获取最新的dom或者更新后的数据。使用回调函数&#xff1a;在生命周期方法中处理&#xff1a; 函数式组件和class组件的区别。class组件函数式组件…

浏览器怎么更新?4个高效设置方法!

“我在使用浏览器时&#xff0c;有时候会提示说浏览器版本太低&#xff0c;需要更新后才能使用。有什么方法可以更新浏览器呢&#xff1f;快给我支支招吧&#xff01;” 在快速发展的科技时代&#xff0c;浏览器更新是确保网络安全和性能优化的关键步骤。如果浏览器的版本太低&…

windows系统下修改nginx配置后reload重加载后不生效解决方案

windows系统下修改nginx配置后reload重加载后不生效解决方案 1、Nginx配置在重启&#xff08;reload&#xff09;后也不生效的原因2、通过服务启动的Nginx&#xff0c;通过stop命令是关闭不了的&#xff1a;(Access is denied)。 1、Nginx配置在重启&#xff08;reload&#xf…

大语言模型量化方法对比:GPTQ、GGUF、AWQ

在过去的一年里&#xff0c;大型语言模型(llm)有了飞速的发展&#xff0c;在本文中&#xff0c;我们将探讨几种(量化)的方式&#xff0c;除此以外&#xff0c;还会介绍分片及不同的保存和压缩策略。 说明&#xff1a;每次加载LLM示例后&#xff0c;建议清除缓存&#xff0c;以…

前端跨界面之间的通信解决方案

主要是这两个方案&#xff0c;其他的&#xff0c;还有 SharedWorker 、IndexedDB、WebSocket、Service Worker 如果是&#xff0c;父子嵌套 iframe 还可以使用 window.parent.postMessage(“需要传递的参数”, ‘*’) 1、localStorage 核心点 同源&#xff0c;不能跨域(协议、端…

9.MyBatis-Plus

1、前期准备 a. 创建数据库 CREATE TABLE USER (id BIGINT(20)NOT NULL COMMENT 主键ID,NAME VARCHAR(30)NULL DEFAULT NULL COMMENT 姓名,age INT(11)NULL DEFAULT NULL COMMENT 年龄,email VARCHAR(50)NULL DEFAULT NULL COMMENT 邮箱,PRIMARY KEY (id) );INSERT INTO user…

短路语法 [SUCTF 2019]EasySQL1

打开题目 输入字符的时候啥也不回显。只有输入数字的时候页面有回显 但是当我们输入union&#xff0c;from&#xff0c;sleep&#xff0c;where&#xff0c;order等&#xff0c;页面回显nonono&#xff0c;很明显过滤了这些关键词 最开始我的思路是打算尝试双写绕过 1;ununion…

TS-08-A-2D、TS-08-B-1H插装式电磁比例溢流阀放大器

TS-08-A-2D、TS-08-B-1H插装式电磁比例溢流阀放大器持续的电磁铁、高效能的电磁铁结构、可选的线圈电压和终端、工业化通用插孔、紧凑的结构。 螺纹插装式、先导滑阀式减压溢流阀&#xff0c;利用可变电流输入可实现指定范围内的输出压力连续调节。输出压力与 DC 电流输入成比…

[Jenkins] 物理机 安装 Jenkins

这里介绍Linux CentOS系统直接Yum 安装 Jenkins&#xff0c;不同系统之间类似&#xff0c;操作命令差异&#xff0c;如&#xff1a;Ubuntu用apt&#xff1b; 0、安装 Jenkins Jenkins是一个基于Java语言开发的持续构建工具平台&#xff0c;主要用于持续、自动的构建/测试你的软…

Linux学习教程(第三章 Linux文件和目录管理)1

第三章 Linux文件和目录管理&#xff08;初识Linux命令&#xff09; 对初学者来说&#xff0c;管理 Linux 系统中的文件和目录&#xff0c;是学习 Linux 至关重要的一步。 为了方便管理文件和目录&#xff0c;Linux 系统将它们组织成一个以根目录 / 开始的倒置的树状结构。Li…