面试题·栈和队列的相互实现·详解

A. 用队列实现栈

用队列实现栈
实现代码如下
看着是队列,其实实际实现更接近数组模拟

typedef struct {int* queue1;    // 第一个队列int* queue2;    // 第二个队列int size;       // 栈的大小int front1, rear1, front2, rear2;  // 两个队列的首尾指针
} MyStack;//初始化栈
MyStack* myStackCreate() {MyStack* stack = (MyStack*)malloc(sizeof(MyStack));stack->queue1 = (int*)malloc(sizeof(int) * 100);stack->queue2 = (int*)malloc(sizeof(int) * 100);stack->size = 0;stack->front1 = stack->rear1 = 0;stack->front2 = stack->rear2 = 0;return stack;
}//将元素 x 压入栈顶 
void myStackPush(MyStack* obj, int x) {obj->queue1[obj->rear1++] = x;  // 将元素加入 queue1 的尾部obj->size++;
}//移除并返回栈顶元素 
int myStackPop(MyStack* obj) {int i;// 将 queue1 中的元素(除了最后一个)转移到 queue2 中for (i = 0; i < obj->size - 1; i++) {obj->queue2[obj->rear2++] = obj->queue1[obj->front1++];}int top = obj->queue1[obj->front1++];  // 获取栈顶元素obj->size--;// 交换 queue1 和 queue2,为下次操作做准备int* temp = obj->queue1;obj->queue1 = obj->queue2;obj->queue2 = temp;obj->front1 = 0;obj->rear1 = obj->size;obj->front2 = 0;obj->rear2 = 0;return top;
}//返回栈顶元素 
int myStackTop(MyStack* obj) {int i;// 将 queue1 中的元素转移到 queue2 中for (i = 0; i < obj->size; i++) {obj->queue2[obj->rear2++] = obj->queue1[obj->front1++];}int top = obj->queue2[obj->rear2 - 1];  // 获取栈顶元素// 交换 queue1 和 queue2,为下次操作做准备int* temp = obj->queue1;obj->queue1 = obj->queue2;obj->queue2 = temp;obj->front1 = 0;obj->rear1 = obj->size;obj->front2 = 0;obj->rear2 = 0;return top;
}//如果栈是空的,返回 true;否则,返回 false 
bool myStackEmpty(MyStack* obj) {return obj->size == 0;
}void myStackFree(MyStack* obj) {free(obj->queue1);free(obj->queue2);free(obj);
}

虽然注释已经够详细了,但是我们还是来逐步分析一下
代码实际上并没有使用链式队列,而是使用了两个固定大小的数组来模拟栈的行为。

首先,我们定义了一个结构体MyStack,它包含两个整数数组queue1和queue2(其实在这里,它们更像栈,因为数组是按照后进先出的顺序使用的),以及它们的前后端指针和栈的大小。

myStackCreate

这个函数用于初始化栈。它分配了MyStack结构体的内存,并为两个数组分配了内存。然后,它初始化了所有的指针和大小为0。

myStackPush
这个函数用于将元素压入栈顶。它简单地将元素添加到queue1的尾部,并更新rear1指针和栈的大小。

myStackPop
这个函数用于移除并返回栈顶元素。但是,这里有一个问题:它实际上将整个queue1(除了最后一个元素)转移到了queue2,然后返回了queue1的最后一个元素。之后,它交换了两个数组,并重置了所有的指针。这样做在每次pop操作时都非常低效,因为它涉及到大量元素的移动。

一个更高效的方法是,每次pop时,只需要记录queue1的最后一个元素(即栈顶元素),然后将其余元素(如果有的话)从queue1转移到queue2,并交换两个队列的引用。但是,在交换后,不需要重置所有的指针,因为queue2的front2和rear2已经指向了正确的位置。

myStackTop
这个函数与myStackPop非常相似,但是它返回栈顶元素而不移除它。然而,与myStackPop一样,它也涉及到了不必要的元素移动和数组交换。

myStackEmpty
这个函数检查栈是否为空,通过检查栈的大小是否为0来实现。

myStackFree
这个函数释放了栈所使用的所有内存。

改进建议

  1. 避免不必要的元素移动:在pop和top操作中,只需要移动除了栈顶元素之外的其他元素。
  2. 减少指针重置:在交换数组后,不需要总是重置所有的前端和后端指针。
  3. 考虑使用真正的链式队列:如果你想要一个链式栈,你应该使用链式队列(通过节点连接)而不是数组。链式实现会更灵活,并且在处理大量数据时可能更高效。
  4. 错误处理:在分配内存时,应该检查malloc是否成功,并在失败时返回错误或进行其他处理。
  5. 边界检查:在pop和top操作中,应该检查栈是否为空,以避免访问空指针或未定义的内存。
  6. 优化内存使用:如果你知道栈的大小上限,可以使用固定大小的数组。但是,如果你想要一个可以动态增长的栈,那么链式实现可能更合适。

下面是优化后的代码

在这个版本中,我们使用了一个指针queue和两个布尔值(但实际上我们只需要一个,因为!isQueue1就等同于isQueue2)来追踪当前正在使用的队列。当栈满时,我们尝试扩容,并将元素从queue2(如果它包含元素)复制到queue1。我们还添加了一个检查,以便在queue1使用了超过一半的空间时,将元素从queue1复制到queue2,并切换队列,以保持两个队列之间的负载均衡。

我们还在myStackFree函数中释放了内存,并处理了malloc和realloc可能返回NULL的情况。

#include <stdlib.h>  
#include <stdbool.h>  #define INITIAL_CAPACITY 100  typedef struct {  int* queue;  int capacity;  int top;  bool isQueue1; // 表示当前正在使用queue1还是queue2  
} MyStack;  MyStack* myStackCreate() {  MyStack* obj = (MyStack*)malloc(sizeof(MyStack));  if (!obj) return NULL;  obj->queue = (int*)malloc(INITIAL_CAPACITY * sizeof(int));  if (!obj->queue) {  free(obj);  return NULL;  }  obj->capacity = INITIAL_CAPACITY;  obj->top = -1; // 栈顶指针初始化为-1,表示空栈  obj->isQueue1 = true; // 初始时使用queue1  return obj;  
}  void myStackPush(MyStack* obj, int x) {  if (obj->top == obj->capacity - 1) { // 栈满,扩容  int newCapacity = obj->capacity * 2;  int* newQueue = (int*)realloc(obj->isQueue1 ? obj->queue : (obj->queue - INITIAL_CAPACITY), newCapacity * sizeof(int));  if (!newQueue) return; // 扩容失败  obj->queue = newQueue + (obj->isQueue1 ? 0 : INITIAL_CAPACITY); // 调整指针,确保obj->queue始终指向当前使用的队列  obj->capacity = newCapacity;  // 如果之前使用queue2,现在切换回queue1,则需要复制数据  if (!obj->isQueue1) {  for (int i = 0; i <= obj->top; i++) {  obj->queue[i] = obj->queue[i + INITIAL_CAPACITY];  }  obj->isQueue1 = true; // 切换回queue1  }  }  obj->queue[++obj->top] = x; // 压栈  
}  int myStackPop(MyStack* obj) {  if (obj->top == -1) return -1; // 栈空  int x = obj->queue[obj->top--]; // 弹出栈顶元素  // 如果queue1已经使用了超过一半的空间,且queue2是空的,则考虑将queue1的元素移到queue2,并切换队列  if (obj->isQueue1 && obj->top > obj->capacity / 4) {  for (int i = 0; i <= obj->top; i++) {  obj->queue[i + INITIAL_CAPACITY] = obj->queue[i];  }  obj->isQueue1 = false; // 切换到queue2  obj->top += INITIAL_CAPACITY; // 更新top指针  }  return x;  
}  int myStackTop(MyStack* obj) {  if (obj->top == -1) return -1; // 栈空  return obj->queue[obj->top]; // 返回栈顶元素  
}  bool myStackEmpty(MyStack* obj) {  return obj->top == -1; // 检查栈是否为空  
}  void myStackFree(MyStack* obj) {  if (obj) {  free(obj->queue - (obj->isQueue1 ? 0 : INITIAL_CAPACITY)); // 释放队列内存  free(obj);  }  
}

B. 用栈实现队列

用栈实现队列

那用链栈来实现一下看看

// 链表节点定义  
typedef struct Node {  int data;  struct Node* next;  
} Node;  // 栈结构定义
typedef struct Stack {  Node* top;  int size;  
} Stack;  // MyQueue结构体定义,包含两个栈  
typedef struct MyQueue {  Stack* stack1;  Stack* stack2;  
} MyQueue;  
// 创建新的节点  
Node* createNode(int data) {  Node* newNode = (Node*)malloc(sizeof(Node));  if (!newNode) {  perror("Memory allocation failed");  exit(EXIT_FAILURE);  }  newNode->data = data;  newNode->next = NULL;  return newNode;  
}  // 初始化栈  
Stack* createStack() {  Stack* stack = (Stack*)malloc(sizeof(Stack));  if (!stack) {  perror("Memory allocation failed");  exit(EXIT_FAILURE);  }  stack->top = NULL;  stack->size = 0;  return stack;  
}  // 压栈操作  
void push(Stack* stack, int data) {  Node* newNode = createNode(data);  newNode->next = stack->top;  stack->top = newNode;  stack->size++;  
}  // 出栈操作  
int pop(Stack* stack) {  if (stack->size == 0) {  printf("Stack is empty.\n");  exit(EXIT_FAILURE);  }  Node* temp = stack->top;  int data = temp->data;  stack->top = temp->next;  free(temp);  stack->size--;  return data;  
}  // 查看栈顶元素  
int peek(Stack* stack) {  if (stack->size == 0) {  printf("Stack is empty.\n");  exit(EXIT_FAILURE);  }  return stack->top->data;  
}  // 判断栈是否为空  
int isEmpty(Stack* stack) {  return stack->size == 0;  
}  // 释放栈内存  
void freeStack(Stack* stack) {  Node* current = stack->top;  Node* temp;  while (current != NULL) {  temp = current;  current = current->next;  free(temp);  }  free(stack);  
}MyQueue* myQueueCreate() {  MyQueue* queue = (MyQueue*)malloc(sizeof(MyQueue));  if (!queue) {  perror("Memory allocation failed for MyQueue");  exit(EXIT_FAILURE);  }  queue->stack1 = createStack();  queue->stack2 = createStack();  return queue;  
}  void myQueuePush(MyQueue* queue, int data) {  push(queue->stack1, data);  
}  int myQueuePop(MyQueue* queue) {  if (isEmpty(queue->stack2)) {  while (!isEmpty(queue->stack1)) {  push(queue->stack2, pop(queue->stack1));  }  }  if (isEmpty(queue->stack2)) {  return -1; // 队列为空,无法弹出元素  }  return pop(queue->stack2);  
}  int myQueuePeek(MyQueue* queue) {  if (isEmpty(queue->stack2)) {  while (!isEmpty(queue->stack1)) {  push(queue->stack2, pop(queue->stack1));  }  }  if (isEmpty(queue->stack2)) {  return -1; // 队列为空,无法查看队首元素  }  return peek(queue->stack2);  
}  bool myQueueEmpty(MyQueue* queue) {  return isEmpty(queue->stack1) && isEmpty(queue->stack2);  
}  void myQueueFree(MyQueue* queue) {  freeStack(queue->stack1);  freeStack(queue->stack2);  free(queue);  
}

这段代码实现了一个基于两个栈的队列结构 MyQueue。下面我们来逐步解释这段代码:

  1. 链表节点定义

    • Node 结构体定义了链表节点, 包含一个整数值 data 和指向下一个节点的指针 next
  2. 栈结构定义

    • Stack 结构体定义了一个栈, 包含指向栈顶元素的指针 top 和栈的大小 size
  3. MyQueue 结构体定义

    • MyQueue 结构体包含两个 Stack 指针, stack1stack2, 用于实现队列的功能。
  4. 创建新的节点:

    • createNode 函数用于创建一个新的链表节点, 并分配内存。如果内存分配失败, 则输出错误信息并退出程序。
  5. 初始化栈:

    • createStack 函数用于创建一个新的栈, 将栈顶指针设为 NULL, 并将栈的大小设为 0。如果内存分配失败, 则输出错误信息并退出程序。
  6. 压栈操作:

    • push 函数用于将一个元素压入栈, 首先创建一个新节点, 然后将新节点的 next 指针指向当前栈顶元素, 最后更新栈顶指针和栈的大小。
  7. 出栈操作:

    • pop 函数用于从栈中弹出一个元素, 首先检查栈是否为空, 如果为空则输出错误信息并退出程序。然后获取栈顶元素的数据, 更新栈顶指针, 释放栈顶元素的内存, 并更新栈的大小。
  8. 查看栈顶元素:

    • peek 函数用于查看栈顶元素, 首先检查栈是否为空, 如果为空则输出错误信息并退出程序。然后返回栈顶元素的数据。
  9. 判断栈是否为空:

    • isEmpty 函数用于判断栈是否为空, 返回栈的大小是否为 0。
  10. 释放栈内存:

    • freeStack 函数用于释放栈占用的内存, 遍历链表并逐个释放每个节点的内存, 最后释放栈本身的内存。
  11. 创建 MyQueue:

    • myQueueCreate 函数用于创建一个新的 MyQueue 对象, 分配内存并初始化两个 Stack 对象。如果内存分配失败, 则输出错误信息并退出程序。
  12. 入队操作:

    • myQueuePush 函数用于将一个元素入队, 直接将元素压入 stack1 即可。
  13. 出队操作:

    • myQueuePop 函数用于从队列中出队一个元素。首先检查 stack2 是否为空, 如果为空则将 stack1 中的元素全部弹出并压入 stack2。然后从 stack2 中弹出并返回队首元素。如果两个栈都为空, 则返回 -1 表示队列为空。
  14. 查看队首元素:

    • myQueuePeek 函数用于查看队首元素。与出队操作类似, 首先检查 stack2 是否为空, 如果为空则将 stack1 中的元素全部弹出并压入 stack2。然后返回 stack2 的栈顶元素。如果两个栈都为空, 则返回 -1 表示队列为空。
  15. 判断队列是否为空:

    • myQueueEmpty 函数用于判断队列是否为空, 返回 stack1stack2 是否同时为空。
  16. 释放 MyQueue 内存:

    • myQueueFree 函数用于释放 MyQueue 对象占用的内存, 首先释放 stack1stack2 的内存, 然后释放 MyQueue 对象本身的内存。

那么到这里,本篇文章就结束了,这两题虽然没有实用的意义,但是用来理解栈和队列还是非常不错的

期待与你的下次相见!!!
下期预告~二叉树(上)-堆

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

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

相关文章

6.Redis之String命令

1.String类型基本介绍 redis 所有的 key 都是字符串, value 的类型是存在差异的~~ 一般来说,redis 遇到乱码问题的概率更小~~ Redis 中的字符串,直接就是按照二进制数据的方式存储的. (不会做任何的编码转换【讲 mysql 的时候,知道 mysql 默认的字符集, 是拉丁文,插入中文…

Jenkins--从入门到入土

Jenkins–从入门到入土 文章目录 Jenkins--从入门到入土〇、概念提要--什么是CI/DI&#xff1f;1、CI&#xff08;Continuous Integration&#xff0c;持续集成&#xff09;2、DI&#xff08;DevOps Integration&#xff0c;DevOps 集成&#xff09;3、解决的问题 一、Jenkins安…

world machine学习笔记(4)

选择设备&#xff1a; select acpect&#xff1a; heading&#xff1a;太阳的方向 elevation&#xff1a;太阳的高度 select colour&#xff1a;选择颜色 select convexity&#xff1a;选择突起&#xff08;曲率&#xff09; select height&#xff1a;选择高度 falloff&a…

用常识滚雪球:拼多多的内生价值,九年的变与不变

2024年5月22日&#xff0c;拼多多公布了今年一季度财报&#xff0c;该季度拼多多集团营收868.1亿元&#xff0c;同比增长131%&#xff0c;利润306.0亿&#xff0c;同比增长了202%&#xff0c;数据亮眼。 市场对拼多多经历了“看不见”、“看不懂”、“跟不上”三个阶段。拼多多…

Vue.js条件渲染与列表渲染指南

title: Vue.js条件渲染与列表渲染指南 date: 2024/5/26 20:11:49 updated: 2024/5/26 20:11:49 categories: 前端开发 tags: VueJS前端开发数据绑定列表渲染状态管理路由配置性能优化 第1章&#xff1a;Vue.js基础与环境设置 1.1 Vue.js简介 Vue.js (读音&#xff1a;/vju…

SwiftUI中的Slider的基本使用

在SwiftUI中&#xff0c;可以使用Slider视图创建一个滑动条&#xff0c;允许用户从范围中选择一个值。通过系统提供的Slider&#xff0c;用起来也很方便。 Slider 先看一个最简单的初始化方法&#xff1a; State private var sliderValue: Float 100var body: some View {V…

ollama 使用,以及指定模型下载地址

ollama windows 使用 官网&#xff1a; https://ollama.com/ windows 指定 models 下载地址 默认会下载在C盘 &#xff0c;占用空间 在Windows系统中&#xff0c;可以通过设置环境变量OLLAMA_MODELS来指定模型文件的下载和存储路径。具体操作步骤如下&#xff1a; 1.打开系统…

【九十四】【算法分析与设计】练习四蛮力法练习,排列问题和组合问题,求解最大连续子序列和问题,求解幂集问题,求解0/1背包问题,求解任务分配问题

求解最大连续子序列和问题 给定一个有n&#xff08;n≥1&#xff09;个整数的序列&#xff0c;要求求出其中最大连续子序列的和。 例如&#xff1a; 序列&#xff08;-2&#xff0c;11&#xff0c;-4&#xff0c;13&#xff0c;-5&#xff0c;-2&#xff09;的最大子序列和为20…

第 33 次CCF认证

1. 词频统计 题目描述 样例输入 代码 #include <bits/stdc.h>using namespace std;int main() {int n,m;cin>>n>>m;vector<int> ans1(m,0),ans2(m,0);while (n --) {int t;cin>>t;vector<int> vis(m1,0);for (int i 1;i < t;i ) {i…

数据结构(五)

数据结构&#xff08;五&#xff09; 常见的排序算法内部排序交换插入选择归并基数 外部排序基于归并的 常见的排序算法 内部排序 交换 冒泡&#xff1a;每一次运行总会将最小的或者最大的放到前面&#xff0c;如果需要交换&#xff0c;一直在交换 快速排序*&#xff1a;经过…

2024最新前端面试八股文【基础篇293题】

⼀、HTML、HTTP、web综合问题 1 前端需要注意哪些SEO 2 <img> 的 title 和 alt 有什么区别 3 HTTP的⼏种请求⽅法⽤途 4 从浏览器地址栏输⼊url到显示⻚⾯的步骤 5 如何进⾏⽹站性能优化 6 HTTP状态码及其含义 7 语义化的理解 8 介绍⼀下你对浏览器内核的理解 9 …

【操作系统】发展与分类(手工操作、批处理、分时操作、实时操作)

2.操作系统发展与分类 思维导图 手工操作阶段&#xff08;此阶段无操作系统&#xff09; 需要人工干预 缺点&#xff1a; 1.用户独占全机&#xff0c;资源利用率低&#xff1b; 2.CPU等待手工操作&#xff0c;CPU利用不充分。 批处理阶段&#xff08;操作系统开始出现&#x…

正运动控制器:视觉纠偏和找孔

一、用户主界面CCD参数设置 通过主界面CCD参数设置&#xff0c;学习如何操作计算相机中心与电批中心的偏移量&#xff0c;以及相机标定的功能。 1、相机中心与电批中心的偏移量计算 1.1、在用户主界面点击CCD参数按钮&#xff0c;进入CCD设置界面。 主界面 CCD参数设置界面 1…

制作电子画册速成攻略,快来试试

​当今社会&#xff0c;数字媒体日益普及&#xff0c;电子画册作为一种崭新的展示方式&#xff0c;受到了越来越多人的青睐。它不仅形式新颖&#xff0c;互动性强&#xff0c;而且制作起来也并不复杂。想知道如何快速掌握制作电子画册的技巧吗&#xff1f;我来教你吧。 接下来&…

推荐13款常用的Vscode插件,提高前端日常开发效率

1. Live Server Live Server 插件是一个用于前端开发的扩展&#xff0c;它的主要作用是提供一个本地开发服务器&#xff0c;以便实时预览和调试网页应用程序。其最大特点在于热重载&#xff0c;即开发者可实时预览代码效果。 因为Live Server 允许开发者在浏览器中实时预览您正…

Llama 3没能逼出GPT-5!OpenAI怒“卷”To B战场,新企业级 AI 功能重磅推出!

Meta 是本周当之无愧的AI巨星&#xff01;刚刚推出的 Llama 3 凭借着强大的性能和开源生态的优势在 LLM 排行榜上迅速跃升。 按理说&#xff0c;Llama 3在开源的状态下做到了 GPT-3.7 的水平&#xff0c;必然会显得用户&#xff08;尤其是企业用户&#xff0c;他们更具备独立部…

C#调用HttpClient.SendAsync报错:System.Net.Http.HttpRequestException: 发送请求时出错。

C#调用HttpClient.SendAsync报错&#xff1a;System.Net.Http.HttpRequestException: 发送请求时出错。 var response await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);问题出在SSL/TLS&#xff0c;Windows Server 2012不支持…

Vue3解决“找不到模块“@/components/xxx.vue”或其相应的类型声明”

文章目录 前言背景问题描述解决方案总结 前言 在使用 Vue 3 开发项目时&#xff0c;遇到“找不到模块 ‘/components/xxx.vue’ 或其相应的类型声明”的错误是一个常见问题。这通常与 TypeScript 和模块解析相关的配置不当有关。本文将详细介绍如何解决此问题&#xff0c;确保…

2024-6-遥远的救世主

2024-6-遥远的救世主 2024-4-18 豆豆 fatux&#xff1a; 2021.5.26 看完电视剧《天道》之后购买本书&#xff0c;断断续续一直没有读完。 非常好奇&#xff0c;一个什么样的作者能写出如此奇书。老丁&#xff0c;一个智者&#xff0c;智者是多么孤独&#xff0c;因为找不到同…

信息安全等级保护测评: 登陆日志

文章目录 引言I 登录日志表结构设计II 日志处理2.1 封装日志入库2.2 收集登陆信息2.3 查询接口引言 等保测评是信息安全等级保护测评的简称,是对信息和信息载体按照重要性等级分级别进行检测、评估的过程。 背景:近期AIS监控平台(网页版)等保测评,发现没有登陆日志,现要…