《数据结构初阶》【顺序栈 + 链式队列 + 循环队列】
《数据结构初阶》【顺序栈 + 链式队列 + 循环队列】
- 前言:
- 什么是栈?
- 栈有哪些实现方式?我们要选择哪种实现方式?
- --------------------------------
- 什么是队列?
- 队列有哪些实现方式?我们要选择哪种实现方式?
- --------------------------------
- 什么是循环队列?
- 循环队列有哪些实现方式?我们要选择哪种实现方式?
- ----------------顺序栈----------------
- 栈的动态数组实现
- 顺序栈初始化时该如何初始化top?
- 头文件
- 实现文件
- 测试文件
- 运行结果
- 栈的oj练习
- [20. 有效的括号](https://leetcode.cn/problems/valid-parentheses/)
- 题目介绍
- 方法一:
- [232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/)
- 题目介绍
- 方法一:
- ----------------链式队列----------------
- 队列的链表实现
- 头文件
- 实现文件
- 测试文件
- 运行结果
- 队列的oj练习
- [225. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/)
- 题目介绍
- 方法一:
- ----------------循环队列----------------
- 循环队列的数组实现
- 循环队列要怎么进行判空or判满?
- 头文件
- 实现文件
- 测试文件
- 运行结果
- 循环队列的oj练习
- [622. 设计循环队列](https://leetcode.cn/problems/design-circular-queue/)
- 题目介绍
- 方法一:
往期《数据结构初阶》回顾:
【时间复杂度 + 空间复杂度】
【顺序表 + 单链表 + 双向链表】
【顺序表/链表 精选15道OJ练习】
前言:
Hi~ o( ̄▽ ̄)ブ 小伙伴们今天是五一小长假的最后一天了,大家假期玩的开心吗?
(🎮)今天也是立夏哦~🌞,不知不觉夏天就要到了🍉
🚩我们也终于要学习新的数据结构——“
栈
和队列
” 了,相信大家在此之前已经学习了《数据结构初阶》前面的内容并进行相应的练习了,接下来就让我们像夏天一样热情满满地开启新篇章吧!🔥
🎯 先来个剧透:
栈
就像自动贩卖机的出货口 🥤 → 最后塞进去的饮料总是最先滚出来超不公平!队列
就像奶茶店排队🧋 → 先来的人总能先尝到甜蜜超公平!循环队列
就像旋转餐厅🍽️ → 餐盘转完一圈又能重新使用超省空间!
温馨提示
:📢 博客内容总共1万两千多字,请耐心看完☕!!!相信当你认真读完这篇博客后,一定会收获满满的知识和启发!✨
什么是栈?
栈
:是一种只能在一端进行插入和删除操作的特殊线性表。
- 它按照后进先出(LIFO, Last In First Out) 的原则存储数据。
- 栈的操作只允许在 一端(称为
栈顶
) 进行,另一端称为栈底
,需要读数据的时候从栈顶开始弹出数据。- 先进入的数据被压入栈底,最后的数据在栈顶。
栈有哪些实现方式?我们要选择哪种实现方式?
栈的实现一般可以使用数组或者链表实现,这两种栈分别被称为:
顺序栈(Array Stack)
:基于数组实现,使用连续的内存空间存储栈元素。
链式栈(Linked Stack)
:基于链表实现,每个节点包含数据域和指向下一个节点的指针。栈的实现方式选择上,没有绝对的最优,而是取决于具体应用场景的需求。
相对而言使用折中方案:动态数组栈的结构实现更优一些,并且大部分现代语言标准库的栈实现也是使用这种方式实现的。
typedef int STKDateType; //使用typedf重新定义栈中数据的类型,方便后续修改中的数据的类型typedef struct Stack
{//1.记录栈的栈顶指针 ---> 一个int变量//2.记录栈的容量 ---> 一个int变量//3.动态数组 ---> 一个指针int top;int capacity;STKDateType* a;
}STK;
--------------------------------
什么是队列?
队列
:是一种只能在一端进行插入操作,在另一端进行删除操作的线性表。
- 它按照 先进先出(FIFO, First In First Out) 的原则存储和处理数据。
- 队列的操作在 两端 进行:队列中允许删除元素的一端是
队头
,队列中允许插入元素的一端是队尾
- 最早进入队列的元素位于队头,新元素总是插入到队尾,随着元素的插入,队尾位置会向后移动。
队列有哪些实现方式?我们要选择哪种实现方式?
注:由于队列本身的种类比较多,例如:循环队列、双端队列、优先队列……,我们这里只实现普通的队列。
和栈一样,普通队列的实现方式也可以使用数组或者链表实现,这两种队列分别被称为:
顺序队列(Array Queue)
:基于数组实现,使用固定大小的数组存储队列元素。
链式队列(Linked Queue)
:基于链表实现,每个节点包含数据域和指向下一个节点的指针。这两种实现方式各有应用场景,但链式队列更优一些并且现代系统开发中,链式队列也更通用。
typedef int QDateType; //使用typedf重新定义队列中的变量的数据类型,方便后续进行修改//队列节点的结构体
typedef struct QueueNode
{//1.队列节点中存储的数据 ---> 一个int变量//2.队列节点中存储下一个节点的位置 ---> 一个指针QDateType val;struct QueueNode* next;
}QNode;//队列的结构体(使用带头尾指针的结构)
typedef struct Queue
{//1.记录当前队列中元素的个数 ---> 一个int变量//2.指向头部的指针 ---> (用于:头删/取队头的元素)//3.指向尾部的指针 ---> (用于:尾插/取队尾的元素)int size;QNode* queHead;QNode* queTail;
}Que;
--------------------------------
什么是循环队列?
循环队列(Circular Queue)
:是将顺序队列的存储空间的最后一个位置绕到第一个位置,形成一个环形的结构。
- 通过环形缓冲区的方式解决普通顺序队列的"假溢出"问题,实现循环利用存储空间的目的。
- 其核心特点是将数组的首尾逻辑相连,使得当队列尾部到达数组末端时能够绕回到数组开头继续存储数据。
循环队列有哪些实现方式?我们要选择哪种实现方式?
从循环队列的定义中我们可以看出来循环队列是基于数组实现的队列,其实呢也可以使用链表来实现,但是较少使用。(有很多的缺陷)
综上:
数组是循环队列的标准实现方式
(90%的常见场景都是使用数组实现的)
typedef int CQDataType;typedef struct CircularQueue
{//1.使用动态数组模拟实现循环队列 ---> 定义一个int*类型的指针//2.记录队列中队头元素的位置 ---> 定义一个int类型的变量//3.记录队列中队尾元素的位置 ---> 定义一个int类型的变量//4.记录队列中当前元素的数量 ---> 定义一个int类型的变量//5.记录队列的容量 ---> 定义一个int类型的变量CQDataType* a;int front;int rear;int size;int capacity;
}CQ;
----------------顺序栈----------------
栈的动态数组实现
顺序栈初始化时该如何初始化top?
方案一:将top初始化为
-1
方案二:将top初始化为0
(推荐)
头文件
---------------------------------Stack.h---------------------------------#pragma once//任务1:定义头文件
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>//任务2:定义栈的结构体
typedef int STKDateType; //使用typedf重新定义栈中数据的类型,方便后续修改中的数据的类型
typedef struct Stack
{//1.记录栈的栈顶指针 ---> 一个int变量//2.记录栈的容量 ---> 一个int变量//3.动态数组 ---> 一个指针int top;int capacity;STKDateType* a;
}STK;//任务3:定义栈要实现的接口
//1.栈的初始化
//2.栈的销毁
//3.栈的入栈操作
//4.栈的出栈操作
//5.栈的取栈顶元素操作
//6.栈的判空操作
//7.栈的求栈中元素数量的操作void STKInit(STK* pstk);
void STKDestroy(STK* pstk);
void STKPush(STK* pstk,STKDateType x);
void STKPop(STK* pstk);
STKDateType STKTop(STK* pstk);
bool STKEmpty(STK* pstk);
int STKSize(STK* pstk);
实现文件
----------------------------------Stack.c---------------------------------#include "Stk.h"//1.实现:“栈的初始化”的函数
void STKInit(STK* pstk)
{assert(pstk); //确保指针非空pstk->top = 0;pstk->capacity = 0;pstk->a = NULL;
}//2.实现:“栈的销毁”的函数
void STKDestroy(STK* pstk)
{assert(pstk);pstk->top = 0;pstk->capacity = 0;free(pstk->a);pstk->a = NULL;
}//3.实现:“栈的入栈操作”的函数
void STKPush(STK* pstk,STKDateType x)
{//第一部分:确保指针非空assert(pstk);//第二部分:检查是否需要进行扩容if (pstk->top == pstk->capacity){//1.需要进行扩容 ---> 新容量是多少?int newCapacity = pstk->capacity == 0 ? 4 : pstk->capacity * 2;//2.开始进行扩容STKDateType* tmp = (STKDateType*)realloc(pstk->a, newCapacity * sizeof(STKDateType));//3.检查扩容是否成功if (tmp == NULL){perror("realloc false");return;}//4.更新指向动态数组的指针pstk->a = tmp;//5.更新栈的容量pstk->capacity = newCapacity;}//第三部分:实现入栈操作pstk->a[pstk->top] = x;pstk->top++;
}//4.实现:“栈的出栈操作”的函数
void STKPop(STK* pstk)
{assert(pstk);//确保指针不为空assert(pstk->top > 0);//确保栈不为空pstk->top--;
}//5.实现:“栈的取栈顶元素的操作”的函数
STKDateType STKTop(STK* pstk)
{assert(pstk);assert(pstk->top > 0);return pstk->a[pstk->top-1]; // 注意:千万别写成这个样子pstk->a[pstk->top--];,这样会先会修改top值}//6.实现:“栈的判空操作”的函数
bool STKEmpty(STK* pstk)
{assert(pstk);//if (pstk->top = 0) return true;//else return false;//1.栈为空 --> 返回true//2.栈不为空 ---> 返回falsereturn pstk->top == 0;
}//7.实现:“栈的求栈中元素数量的操作”的函数
int STKSize(STK* pstk)
{assert(pstk);return pstk->top;
}
测试文件
-------------------------------Test.c--------------------------------#include "Stack.h"void TestStack()
{STK stack;STKInit(&stack);printf("===== 栈测试开始 =====\n\n");/*--------------------“入栈测试”--------------------*/ printf("--- 测试1: 入栈测试 ---\n");printf("入栈 1, 2, 3, 4\n");STKPush(&stack, 1);STKPush(&stack, 2);STKPush(&stack, 3);STKPush(&stack, 4);printf("栈大小: %d, 栈顶元素: %d\n", STKSize(&stack), STKTop(&stack));printf("\n");/*--------------------“扩容测试”--------------------*/ printf("--- 测试2: 扩容测试 ---\n");printf("当前容量: %d\n", stack.capacity);printf("入栈 5 触发扩容\n");STKPush(&stack, 5);printf("扩容后容量: %d\n", stack.capacity);printf("栈大小: %d, 栈顶元素: %d\n", STKSize(&stack), STKTop(&stack));printf("\n");/*--------------------“出栈测试”--------------------*/ printf("--- 测试3: 出栈测试 ---\n");printf("出栈前栈顶: %d\n", STKTop(&stack));STKPop(&stack);printf("出栈后栈顶: %d\n", STKTop(&stack));printf("栈大小: %d\n", STKSize(&stack));printf("\n");/*--------------------“取栈顶元素测试”--------------------*/printf("--- 测试4: 取栈顶元素测试 ---\n");printf("当前栈顶: %d\n", STKTop(&stack));STKPush(&stack, 6);printf("入栈6后栈顶: %d\n", STKTop(&stack));printf("\n");/*--------------------“栈空测试”--------------------*/printf("--- 测试5: 栈空测试 ---\n");printf("连续出栈直到栈空...\n");while (!STKEmpty(&stack)) {printf("出栈: %d, 剩余大小: %d\n", STKTop(&stack), STKSize(&stack) - 1);STKPop(&stack);}printf("栈是否为空? %s\n", STKEmpty(&stack) ? "是" : "否");printf("\n");/*--------------------“销毁测试”--------------------*/ printf("--- 测试6: 销毁测试 ---\n");STKDestroy(&stack);printf("栈已销毁\n");printf("销毁后访问指针: %p\n", (void*)stack.a);printf("\n");printf("===== 栈测试结束 =====\n");
}int main()
{TestStack();return 0;
}
运行结果
栈的oj练习
20. 有效的括号
题目介绍
方法一:
//任务2:定义栈的结构体
typedef char STKDateType; //使用typedf重新定义栈中数据的类型,方便后续修改中的数据的类型
typedef struct Stack
{//1.记录栈的栈顶指针 ---> 一个int变量//2.记录栈的容量 ---> 一个int变量//3.动态数组 ---> 一个指针int top;int capacity;STKDateType* a;
}STK;//任务3:定义栈要实现的接口
//1.栈的初始化
//2.栈的销毁
//3.栈的入栈操作
//4.栈的出栈操作
//5.栈的取栈顶元素操作
//6.栈的判空操作
//7.栈的求栈中元素数量的操作void STKInit(STK* pstk);
void STKDestroy(STK* pstk);
void STKPush(STK* pstk,STKDateType x);
void STKPop(STK* pstk);
STKDateType STKTop(STK* pstk);
bool STKEmpty(STK* pstk);
int STKSize(STK* pstk);//1.实现:“栈的初始化”的函数
void STKInit(STK* pstk)
{assert(pstk); //确保指针非空pstk->top = 0;pstk->capacity = 0;pstk->a = NULL;
}//2.实现:“栈的销毁”的函数
void STKDestroy(STK* pstk)
{assert(pstk);pstk->top = 0;pstk->capacity = 0;free(pstk->a);pstk->a = NULL;
}//3.实现:“栈的入栈操作”的函数
void STKPush(STK* pstk,STKDateType x)
{//第一部分:确保指针非空assert(pstk);//第二部分:检查是否需要进行扩容if (pstk->top == pstk->capacity){//1.需要进行扩容 ---> 新容量是多少?int newCapacity = pstk->capacity == 0 ? 4 : pstk->capacity * 2;//2.开始进行扩容STKDateType* tmp = (STKDateType*)realloc(pstk->a, newCapacity * sizeof(STKDateType));//3.检查扩容是否成功if (tmp == NULL){perror("realloc false");return;}//4.更新指向动态数组的指针pstk->a = tmp;//5.更新栈的容量pstk->capacity = newCapacity;}//第三部分:实现入栈操作pstk->a[pstk->top] = x;pstk->top++;
}//4.实现:“栈的出栈操作”的函数
void STKPop(STK* pstk)
{assert(pstk);//确保指针不为空assert(pstk->top > 0);//确保栈不为空pstk->top--;
}//5.实现:“栈的取栈顶元素的操作”的函数
STKDateType STKTop(STK* pstk)
{assert(pstk);assert(pstk->top > 0);return pstk->a[pstk->top-1]; // 注意:千万别写成这个样子pstk->a[pstk->top--];,这样会先会修改top值}//6.实现:“栈的判空操作”的函数
bool STKEmpty(STK* pstk)
{assert(pstk);//if (pstk->top = 0) return true;//else return false;//1.栈为空 --> 返回true//2.栈不为空 ---> 返回falsereturn pstk->top == 0;
}//7.实现:“栈的求栈中元素数量的操作”的函数
int STKSize(STK* pstk)
{assert(pstk);return pstk->top;
}//实现:解决这道题的函数isValid
bool isValid(char* s)
{//1.创建一个栈STK stk;//2.初始化栈STKInit(&stk);//处理数据:while(*s) //遍历整个字符串的方法{//3.如果碰到是左括号就将左括号入栈if(*s=='('||*s=='['||*s=='{') STKPush(&stk,*s);//4.如果碰到是右括号:1)判断栈是否为空 2)判断右括号是否和栈顶的元素匹配if(*s==')'||*s==']'||*s=='}'){//1)如果栈为空 --> 也就是说字符串的前面并没有和它匹配的左括号if(STKEmpty(&stk)){STKDestroy(&stk);return false;} //2)判断右括号是否和栈顶有的元素匹配 --> 先判断不匹配时,再判断匹配时char top=STKTop(&stk);if(*s==')'&&top!='('||*s==']'&&top!='['||*s=='}'&&top!='{')//匹配失败{STKDestroy(&stk);return false;}else//匹配成功{STKPop(&stk);}}s++;}//5.判断栈是否为空 ---> 这才是判断字符串是不是有括号的标准(如果栈中最后还元素:说明这个字符中的左右括号数量是不匹配的)bool result=STKEmpty(&stk);//6.销毁栈STKDestroy(&stk);return result;
}
232. 用栈实现队列
题目介绍
方法一:
//任务1:定义头文件
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>//任务2:定义栈的结构体
typedef int STKDateType; //使用typedf重新定义栈中数据的类型,方便后续修改中的数据的类型
typedef struct Stack
{//1.记录栈的栈顶指针 ---> 一个int变量//2.记录栈的容量 ---> 一个int变量//3.动态数组 ---> 一个指针int top;int capacity;STKDateType* a;
}STK;//任务3:定义栈要实现的接口
//1.栈的初始化
//2.栈的销毁
//3.栈的入栈操作
//4.栈的出栈操作
//5.栈的取栈顶元素操作
//6.栈的判空操作
//7.栈的求栈中元素数量的操作void STKInit(STK* pstk);
void STKDestroy(STK* pstk);
void STKPush(STK* pstk,STKDateType x);
void STKPop(STK* pstk);
STKDateType STKTop(STK* pstk);
bool STKEmpty(STK* pstk);
int STKSize(STK* pstk);//1.实现:“栈的初始化”的函数
void STKInit(STK* pstk)
{assert(pstk); //确保指针非空pstk->top = 0;pstk->capacity = 0;pstk->a = NULL;
}//2.实现:“栈的销毁”的函数
void STKDestroy(STK* pstk)
{assert(pstk);pstk->top = 0;pstk->capacity = 0;free(pstk->a);pstk->a = NULL;
}//3.实现:“栈的入栈操作”的函数
void STKPush(STK* pstk,STKDateType x)
{//第一部分:确保指针非空assert(pstk);//第二部分:检查是否需要进行扩容if (pstk->top == pstk->capacity){//1.需要进行扩容 ---> 新容量是多少?int newCapacity = pstk->capacity == 0 ? 4 : pstk->capacity * 2;//2.开始进行扩容STKDateType* tmp = (STKDateType*)realloc(pstk->a, newCapacity * sizeof(STKDateType));//3.检查扩容是否成功if (tmp == NULL){perror("realloc false");return;}//4.更新指向动态数组的指针pstk->a = tmp;//5.更新栈的容量pstk->capacity = newCapacity;}//第三部分:实现入栈操作pstk->a[pstk->top] = x;pstk->top++;
}//4.实现:“栈的出栈操作”的函数
void STKPop(STK* pstk)
{assert(pstk);//确保指针不为空assert(pstk->top > 0);//确保栈不为空pstk->top--;
}//5.实现:“栈的取栈顶元素的操作”的函数
STKDateType STKTop(STK* pstk)
{assert(pstk);assert(pstk->top > 0);return pstk->a[pstk->top-1]; // 注意:千万别写成这个样子pstk->a[pstk->top--];,这样会先会修改top值}//6.实现:“栈的判空操作”的函数
bool STKEmpty(STK* pstk)
{assert(pstk);//if (pstk->top = 0) return true;//else return false;//1.栈为空 --> 返回true//2.栈不为空 ---> 返回falsereturn pstk->top == 0;
}//7.实现:“栈的求栈中元素数量的操作”的函数
int STKSize(STK* pstk)
{assert(pstk);return pstk->top;
}--------------------------------------------------------------------------------------typedef struct
{//使用双栈实现队列 ---> 队列的结构体中的成员:两个栈STK pushStk;STK popStk;
} MyQueue;int myQueuePeek(MyQueue* pque);//myQueuePeek会先被myQueuePop函数使用,这里先声明一下MyQueue* myQueueCreate()
{//实现:“队列的创建+初始化”操作//1)创建操作//1.分配队列的结构体的内存MyQueue*pque=(MyQueue*)malloc(sizeof(MyQueue));//2)初始化栈STKInit(&pque->pushStk);STKInit(&pque->popStk);//3)返回队列的结构体指针return pque;
}void myQueuePush(MyQueue* pque, int x)
{//实现:“队列的入队”操作 ---> 向pushStak栈中添加元素STKPush(&pque->pushStk,x);
}int myQueuePop(MyQueue* pque)
{//实现:“队列的出队”操作 //1.如果popStk栈中没有元素,需要先从pushStk栈中导入元素int front=myQueuePeek(pque);//2.弹出popStk栈中的栈顶的元素STKPop(&pque->popStk);return front;
}int myQueuePeek(MyQueue* pque)
{//实现:“队列的取队头元素”操作//1.如果popStk栈中没有元素,需要先从pushStk栈中导入元素if(STKEmpty(&pque->popStk)){//1.1:将栈pushStk栈中的元素添加到popStk栈中while(!STKEmpty(&pque->pushStk)){int top=STKTop(&pque->pushStk);STKPop(&pque->pushStk);STKPush(&pque->popStk,top);}}//2.取出popStk栈中的栈顶元素int front=STKTop(&pque->popStk);//3.返回popStk栈的栈顶元素即可return front;
}bool myQueueEmpty(MyQueue* pque)
{//实现:“队列的判空”操作 ---> 两个栈都为空的时候说明队列为空return STKEmpty(&pque->pushStk)&&STKEmpty(&pque->popStk);
}void myQueueFree(MyQueue* pque)
{//实现:“队列的销毁”操作//从内向外进行销毁//1.先销毁两个栈STKDestroy(&pque->popStk);STKDestroy(&pque->pushStk);//2.在销毁队列的结构体free(pque);
}
----------------链式队列----------------
队列的链表实现
头文件
--------------------------------Queue.h--------------------------------#pragma once//任务1:声明需要使用的头文件
//任务2:定义队列的结构体
//任务3:声明队列的接口函数//任务1:
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>//任务2:
typedef int QDateType; //使用typedf重新定义队列中的变量的数据类型,方便后续进行修改//队列节点的结构体
typedef struct QueueNode
{//1.队列节点中存储的数据 ---> 一个int变量//2.队列节点中存储下一个节点的位置 ---> 一个指针QDateType val;struct QueueNode* next; //注意这里next的类型可千万不要使用QNode哦,因为这时你还没有定义QNode,要先使用QueueNode进行代替 //(如果这是一个.c文件的话必须写成struct QueueNode,而如果这是一个.cpp文件的话写成QueueNode即可,因为在.cpp文件中这里你相当于定义的是“类”,而不是“结构体”)//总结://C语言:struct QueueNode* next; 解释:struct 结构体名 =类型名 ---> 类型名* next :结构体指针//C++:QueueNode* next; 解释:类名* next ---> 类指针
}QNode;//队列的结构体(使用带头尾指针的结构)
typedef struct Queue
{//1.记录当前队列中元素的个数 ---> 一个int变量//2.指向头部的指针 ---> (用于:头删/取队头的元素)//3.指向尾部的指针 ---> (用于:尾插/取队尾的元素)int size;QNode* queHead;QNode* queTail;
}Que;//任务3:
//1.队列的初始化
//2.队列的销毁//3.队列的入队
//4.队列的出队//5.队列的取队头元素
//6.队列的取队尾元素//7.队列的判空
//8.队列的求队列中元素的数量void QueInit(Que* pque);
void QueDestroy(Que* pque);void QuePush(Que* pque, QDateType x);
void QuePop(Que* pque);QDateType QueFront(Que* pque);
QDateType QueBack(Que* pque);bool QueEmpty(Que* pque);
int QueSize(Que* pque);
实现文件
--------------------------------Queue.c--------------------------------#include "Queue.h"//1.实现:“队列的初始化”操作
void QueInit(Que* pque)
{assert(pque);//1.将队列结构体中“元素的数量”置为0pque->size = 0;//2.将队列结构体中“指向队头/队尾的指针”置为空指针pque->queHead = NULL;pque->queTail = NULL;
}//2.实现:“队列的销毁”操作
void QueDestroy(Que* pque)
{assert(pque);//1.将队列结构体中“元素的数量”置为0pque->size = 0;//2.将队列中的所有的节点都释放掉//1)创建临时指针代替queHead进行遍历队列的节点QNode* tmp = pque->queHead;//2)使用tmp遍历所有的节点while (tmp != NULL){//3)创建后继指针存储当前遍历到的节点的下一位置QNode* next = tmp->next;//4)释放当前遍历到的节点free(tmp);//5)更新tmp指针tmp = next;}//将队列结构体中“指向队列头部/尾部的指针”置为空指针pque->queHead = NULL;pque->queTail = NULL;
}//3.实现:“队列的入队”操作
void QuePush(Que* pque, QDateType x)
{assert(pque);//思路:使用链表的尾插法实现入队的操作//1)先创建出来一个队列的节点QNode* newNode = (QNode*)malloc(sizeof(QNode));//1.1:判断新开辟的空间是否开辟成功哦if (newNode == NULL){perror("malloc fail");return;}//2)为这个节点进行赋值newNode->val = x;//3)将这个节点链接到队列链表的后面//3.1:将新开辟的节点的next指针域置空newNode->next = NULL;//3.2:将新开辟的节点链接到队列链表的尾部//3.3.1:如果当前的队列链表中没有节点if (pque->queTail == NULL){pque->queHead = pque->queTail = newNode;}//3.3.2:如果当前的队列链表中有节点else{pque->queTail->next = newNode;pque->queTail = newNode;}//4)队列中的元素的数量增加pque->size++;
}//4.实现:“队列的出队”操作
void QuePop(Que* pque)
{assert(pque);assert(pque->size > 0);//注意:当我们:1.删除某个数据结构中的元素时 2.从某个数据结构中取出元素的时//---> 需要判断当前的数据结构中是否还有元素//思路:使用链表的头删法实现出队操作//1)如果当前队列中只有一个元素if (pque->queHead->next == NULL){//1.1:将这一个节点删除掉free(pque->queHead);//1.2:将指向队头和队尾的指针都置为空pque->queHead = NULL;pque->queTail = NULL;}//2)如果队列中的元素有多个else{//2.1:创建一个临时的指针来存放queHead指针的下一个位置QNode* next = pque->queHead->next;//2.2:释放掉queHead指针指向的节点free(pque->queHead);//2.3:将queHead指针移动到下一个节点的位置pque->queHead = next;}//3)队列中的元素的数量减少pque->size--;
}//5.实现:“队列的取出队头元素”操作
QDateType QueFront(Que* pque)
{assert(pque);assert(pque->size > 0);return pque->queHead->val;
}//6.实现:“队列的取出队尾元素”操作
QDateType QueBack(Que* pque)
{assert(pque);assert(pque->size > 0);return pque->queTail->val;
}//7.实现:“队列的判空”
bool QueEmpty(Que* pque)
{assert(pque);return pque->size == 0; //队列中没有元素返回真;队列中有元素返回假
}//8.实现:“队列的求出队列中元素的个数”操作
int QueSize(Que* pque)
{assert(pque);return pque->size;
}
测试文件
--------------------------------Test.c--------------------------------#include "Queue.h"void TestQueue()
{Que queue;QueInit(&queue);printf("===== 队列测试开始 =====\n\n");/*--------------------“入队测试”--------------------*/printf("--- 测试1: 入队测试 ---\n");printf("入队 1, 2, 3\n");QuePush(&queue, 1);QuePush(&queue, 2);QuePush(&queue, 3);printf("队列大小: %d\n", QueSize(&queue));printf("队头元素: %d, 队尾元素: %d\n", QueFront(&queue), QueBack(&queue));printf("\n");/*--------------------“出队测试”--------------------*/printf("--- 测试2: 出队测试 ---\n");printf("出队前队头: %d\n", QueFront(&queue));QuePop(&queue);printf("出队后队头: %d\n", QueFront(&queue));printf("队列大小: %d\n", QueSize(&queue));printf("\n");/*--------------------“混合操作测试”--------------------*/printf("--- 测试3: 混合操作测试 ---\n");printf("入队 4, 5, 6\n");QuePush(&queue, 4);QuePush(&queue, 5);QuePush(&queue, 6);printf("出队: %d\n", QueFront(&queue));QuePop(&queue);printf("入队 7\n");QuePush(&queue, 7);printf("当前队列: 队头=%d, 队尾=%d, 大小=%d\n",QueFront(&queue), QueBack(&queue), QueSize(&queue));printf("\n");/*--------------------“销毁测试”--------------------*/printf("--- 测试4: 销毁测试 ---\n");QueDestroy(&queue);printf("队列已销毁\n");printf("销毁后头指针: %p, 尾指针: %p\n",(void*)queue.queHead, (void*)queue.queTail);printf("\n");printf("===== 队列测试结束 =====\n");
}int main()
{TestQueue();return 0;
}
运行结果
队列的oj练习
225. 用队列实现栈
题目介绍
方法一:
//任务2:
typedef int QDateType; //使用typedf重新定义队列中的变量的数据类型,方便后续进行修改//队列节点的结构体
typedef struct QueueNode
{//1.队列节点中存储的数据 ---> 一个int变量//2.队列节点中存储下一个节点的位置 ---> 一个指针QDateType val;struct QueueNode* next; //注意这里next的类型可千万不要使用QNode哦,因为这时你还没有定义QNode,要先使用QueueNode进行代替
}QNode;//队列的结构体(使用带头尾指针的结构)
typedef struct Queue
{//1.记录当前队列中元素的个数 ---> 一个int变量//2.指向头部的指针 ---> (用于:头删/取队头的元素)//3.指向尾部的指针 ---> (用于:尾插/取队尾的元素)int size;QNode* queHead;QNode* queTail;
}Que;//任务3:
//1.队列的初始化
//2.队列的销毁//3.队列的入队
//4.队列的出队//5.队列的取队头元素
//6.队列的取队尾元素//7.队列的判空
//8.队列的求队列中元素的数量void QueInit(Que* pque);
void QueDestroy(Que* pque);void QuePush(Que* pque, QDateType x);
void QuePop(Que* pque);QDateType QueFront(Que* pque);
QDateType QueBack(Que* pque);bool QueEmpty(Que* pque);
int QueSize(Que* pque);//1.实现:“队列的初始化”操作
void QueInit(Que* pque)
{assert(pque);//1.将队列结构体中“元素的数量”置为0pque->size = 0;//2.将队列结构体中“指向队头/队尾的指针”置为空指针pque->queHead = NULL;pque->queTail = NULL;
}//2.实现:“队列的销毁”操作
void QueDestroy(Que* pque)
{assert(pque);//1.将队列结构体中“元素的数量”置为0pque->size = 0;//2.将队列中的所有的节点都释放掉//1)创建临时指针代替queHead进行遍历队列的节点QNode* tmp = pque->queHead;//2)使用tmp遍历所有的节点while (tmp != NULL){//3)创建后继指针存储当前遍历到的节点的下一位置QNode* next = tmp->next;//4)释放当前遍历到的节点free(tmp);//5)更新tmp指针tmp = next;}//将队列结构体中“指向队列头部/尾部的指针”置为空指针pque->queHead = NULL;pque->queTail = NULL;
}//3.实现:“队列的入队”操作
void QuePush(Que* pque, QDateType x)
{assert(pque);//思路:使用链表的尾插法实现入队的操作//1)先创建出来一个队列的节点QNode* newNode = (QNode*)malloc(sizeof(QNode));//1.1:判断新开辟的空间是否开辟成功哦if (newNode == NULL){perror("malloc fail");return;}//2)为这个节点进行赋值newNode->val = x;//3)将这个节点链接到队列链表的后面//3.1:将新开辟的节点的next指针域置空newNode->next = NULL;//3.2:将新开辟的节点链接到队列链表的尾部//3.3.1:如果当前的队列链表中没有节点if (pque->queTail == NULL){pque->queHead = pque->queTail = newNode;}//3.3.2:如果当前的队列链表中有节点else{pque->queTail->next = newNode;pque->queTail = newNode;}//4)队列中的元素的数量增加pque->size++;
}//4.实现:“队列的出队”操作
void QuePop(Que* pque)
{assert(pque);assert(pque->size > 0);//注意:当我们:1.删除某个数据结构中的元素时 2.从某个数据结构中取出元素的时//---> 需要判断当前的数据结构中是否还有元素//思路:使用链表的头删法实现出队操作//1)如果当前队列中只有一个元素if (pque->queHead->next == NULL){//1.1:将这一个节点删除掉free(pque->queHead);//1.2:将指向队头和队尾的指针都置为空pque->queHead = NULL;pque->queTail = NULL;}//2)如果队列中的元素有多个else{//2.1:创建一个临时的指针来存放queHead指针的下一个位置QNode* next = pque->queHead->next;//2.2:释放掉queHead指针指向的节点free(pque->queHead);//2.3:将queHead指针移动到下一个节点的位置pque->queHead = next;}//3)队列中的元素的数量减少pque->size--;
}//5.实现:“队列的取出队头元素”操作
QDateType QueFront(Que* pque)
{assert(pque);assert(pque->size > 0);return pque->queHead->val;
}//6.实现:“队列的取出队尾元素”操作
QDateType QueBack(Que* pque)
{assert(pque);assert(pque->size > 0);return pque->queTail->val;
}//7.实现:“队列的判空”
bool QueEmpty(Que* pque)
{assert(pque);return pque->size == 0; //队列中没有元素返回真;队列中有元素返回假
}//8.实现:“队列的求出队列中元素的个数”操作
int QueSize(Que* pque)
{assert(pque);return pque->size;
}----------------------------------------------------------------------------------------typedef struct
{Que q1;Que q2;
} MyStack;MyStack* myStackCreate()
{MyStack* pstk=(MyStack*)malloc(sizeof(MyStack));QueInit(&(pstk->q1));QueInit(&(pstk->q2));return pstk;
}
// psrk:是指向结构体Mystack的指针
void myStackPush(MyStack* pstk, int x)
{ // & 结构成员 :(得到结构体MyStack的成员变量队列q1的地址)if(!QueEmpty(&(pstk->q1))){QuePush(&(pstk->q1),x);}else{QuePush(&(pstk->q2),x);}
}int myStackPop(MyStack* pstk)
{//假设法:得到“空/非空”队列,之后直接对“空/非空”队列进行操作,而不需要使用q1或q2进行操作Que*empty=&(pstk->q1); //这里的empty是结构体指针,而pstk->q1是结构体Que*noempty=&(pstk->q2);if(!QueEmpty(&(pstk->q1))){noempty=&(pstk->q1);empty=&(pstk->q2);}while(QueSize(noempty)>1){QuePush(empty,QueFront(noempty));QuePop(noempty);}int front=QueFront(noempty);QuePop(noempty);return front;
}int myStackTop(MyStack* pstk)
{if(!QueEmpty(&(pstk->q1))) return QueBack(&(pstk->q1));else return QueBack(&(pstk->q2));
}bool myStackEmpty(MyStack* pstk)
{return (QueEmpty(&(pstk->q1))&&QueEmpty(&(pstk->q2)));
}void myStackFree(MyStack* pstk)
{//销毁栈时注意销毁的顺序:1.先销毁栈结构体中的元素队列 2.再销毁栈的结构体//销毁顺序:“从内往外”进行销毁QueDestroy(&(pstk->q1));QueDestroy(&(pstk->q2));free(pstk);
}
----------------循环队列----------------
循环队列的数组实现
循环队列要怎么进行判空or判满?
循环队列(Circular Queue)
:判空和判满的实现需要考虑队头(front)
和队尾(rear)
指针的位置关系。由于循环队列会重复利用存储空间,当队头和队尾指针相遇时,既可能表示队列已满,也可能表示队列已空,因此需要特殊处理。
常见的实现方式:
预留空位法
、计数器法
、标志位法
三种方法的比较 :
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
预留空位法 | 实现简单,效率高 | 浪费一个存储空间 | 大多数常规情况 |
计数器法 | 不浪费空间 | 需要维护额外变量 | 需要最大化利用空间 |
标志位法 | 不浪费空间 | 逻辑稍复杂 | 对内存要求严格的场景 |
方法 | 判空条件 | 判满条件 |
---|---|---|
预留空位法 | front == rear | (rear + 1) % capacity == front |
计数器法 | count == 0 | count == capacity |
标志位法 | !isFull && front == rear | isFull && front == rear |
总结:预留空位法最常用,并且C++ STL中也是使用的预留空位法解决的如何判断循环队列空or满?
所以下面我们实现的循环队列的两个判空和判满的接口函数也是使用的这种方式进行的判断。
头文件
-----------------------------CircularQueue.h-----------------------------#pragma once//任务1:包含要使用的头文件
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>//任务2:定义循环队列的存储结构
typedef int CQDataType;typedef struct CircularQueue
{//1.使用动态数组模拟实现循环队列 ---> 定义一个int*类型的指针//2.记录队列中队头元素的位置 ---> 定义一个int类型的变量//3.记录队列中队尾元素的位置 ---> 定义一个int类型的变量//4.记录队列中当前元素的数量 ---> 定义一个int类型的变量//5.记录队列的容量 ---> 定义一个int类型的变量CQDataType* a;int front;int rear;int size;int capacity;
}CQ;//任务3:声明要使用的辅助函数
//1.用于循环链表的扩容(辅助函数)
//2.用于打印循环队列(辅助函数)void CQCheckCapacity(CQ* pcque);
void CQPrint(CQ* pcque);//任务4:声明循环队列的接口函数
//1.循环队列的初始化
//2.循环队列的销毁//3.循环队列的判空
//4.循环队列的判满//5.循环队列的入队(指的是:尾部入队,因为尽管是循环队列,但是它还是要严格遵循队列的FIFO(先进先出)原则,毕竟它还不是双端队列)
//6.循环队列的出队
//7.循环队列的取队头的元素
//8.循环队列的取队尾的元素void CQInit(CQ* pcque);
void CQDestroy(CQ** ppcque);bool CQEmpty(CQ* pcque);
bool CQFull(CQ* pcque);void CQPush(CQ* pcque, CQDataType x);
void CQPop(CQ* pcque);
CQDataType CQFront(CQ* pcque);
CQDataType CQRear(CQ* pcque);
实现文件
-----------------------------CircularQueue.c-----------------------------#include "CircularQueue.h"/*-----------------------------------------辅助工具函数-----------------------------------------*/
/*** @brief 检查并扩容循环队列的存储空间** @param pcque 指向循环队列的指针** @note 该函数完成以下工作:* 1. 检查队列是否已满* 2. 计算新容量(初始为0时设为4,否则2倍扩容)* 3. 使用realloc扩容存储数组* 4. 更新队列的容量字段** @warning 扩容后不处理元素位置的调整(循环队列可能需要特殊处理)*/
//1.实现:“循环队列的扩容”辅助函数
void CQCheckCapacity(CQ* pcque)
{if (pcque->capacity == 0) //特殊处理初始情况{int newCapacity = 4;CQDataType* tmp = (CQDataType*)malloc(newCapacity * sizeof(CQDataType));if (!tmp) {perror("malloc fail");exit(EXIT_FAILURE);}pcque->a = tmp;pcque->capacity = newCapacity;}else if (pcque->size == pcque->capacity-1) //容量为capacity的循环队列最多只能存储capacity-1个元素{//1.1:判断需要扩容的容量的大小int newCapacity = pcque->capacity * 2;//1.2:使用realloc进行扩容CQDataType* tmp = (CQDataType*)realloc(pcque->a, newCapacity*sizeof(CQDataType));if (tmp == NULL){perror("realloc fail");return;}//更新数组的指针 + 循环队列的容量pcque->a = tmp;pcque->capacity = newCapacity;}
}//2.实现:“打印循环队列”的辅助函数
void CQPrint(CQ* pcque)
{assert(pcque); //断言检查:确保队列指针有效,防止对空指针进行解引用//情况1:循环队列为空队列if (CQEmpty(pcque)) return;//情况2:循环队列非空队列//2.1:定义一个变量记录打印循环队列中的元素的数量int count = 0;//2.2:循环打印队列中的元素int pcur = pcque->front; //注意:这里一定要使用一个临时的指针进行遍历循环队列,防止pcque指针的移动导致队列结构错乱while (count < pcque->size) {printf("%d ", pcque->a[pcur]);pcur = (pcur + 1) % pcque->capacity;count++;}printf("\n");
}/*-----------------------------------------核心功能函数-----------------------------------------*/
/*** @brief 初始化循环队列** @param pcque 指向需要初始化的循环队列结构体的指针** @note 该函数完成以下工作:* 1. 检查指针有效性(断言保护)* 2. 初始化队列的基本状态:* - 当前元素数量(size)置为0* - 当前队列容量(capacity)置为0* - 队头指针(front)置为0* - 队尾指针(rear)置为0* - 数据存储数组指针(a)置为NULL** @warning* - 使用前必须确保pcque指针有效* - 该初始化不会分配存储空间,首次插入元素时会自动扩容*/
//1.实现:“循环队列的初始化”操作
void CQInit(CQ* pcque)
{assert(pcque); //断言检查:确保队列指针有效,防止对空指针进行解引用pcque->size = 0;pcque->capacity = 0;pcque->front = 0;pcque->rear = 0;pcque->a = NULL;
}//2.实现:“循环队列的销毁”操作
void CQDestroy(CQ** ppcque)
{assert(ppcque); //断言检查1:确保传入的指针是非空指针,防止对空指针进行解引用assert(*ppcque); //断言检查2:确保队列指针有效,防止对空指针进行解引用(建议加上)/*----------第一步:释放动态数组内存----------*/free((*ppcque)->a);(*ppcque)->a = NULL;/*----------第一步:重置队列状态----------*/(*ppcque)->size = (*ppcque)->capacity = 0;(*ppcque)->front = (*ppcque)->rear = 0;/*----------第一步:释放队列结构体内存----------*/free(*ppcque);*ppcque = NULL; //真正修改外部指针//(函数的形参需要是二级指针,如果使用的是一级指针,参数是值传递,在函数内部将ppcque置空,对外部指针无影响,外部指针将变为野指针)
}/*** @brief 判断循环队列是否为空** @param pcque 指向循环队列结构体的指针(需确保非NULL)* @return bool 返回true表示队列为空,false表示非空** @note 该函数实现循环队列的判空逻辑:* 1. 使用断言确保指针有效性(调试阶段捕获空指针)* 2. 直接比较队头指针(front)和队尾指针(rear)* 3. 当front == rear时判定为空队列*///3.实现:“循环队列的判空”操作
bool CQEmpty(CQ* pcque)
{assert(pcque); //断言检查:确保队列指针有效,防止对空指针进行解引用//判满的核心逻辑:队尾指针和队头指针指向同一个位置return pcque->front == pcque->rear; //循环队列:空为true;非空为false
}/*** @brief 判断循环队列是否已满** @param pcque 指向循环队列结构体的指针* @return bool 返回true表示队列已满,false表示未满** @note 该函数实现循环队列的判满逻辑:* 1. 使用取模运算实现循环指针计算* 2. 通过(rear+1) % capacity == front判断队列满* 3. 预留一个空位区分队列空和满的状态** @details 循环队列判满的数学原理:* - 队列容量为capacity时,实际可以使用的是capacity-1个空间* - 当(rear+1) % capacity == front时:* a) 若front == rear,队列为空* b) 否则,队列已满* - 这样设计避免了使用额外标志位*/
//4.实现:“循环队列的判满”操作
bool CQFull(CQ* pcque)
{assert(pcque);//断言检查:确保队列指针有效,防止对空指针进行解引用//判满的核心逻辑:队尾指针的下一个位置(取模后)等于队头指针return (pcque->rear + 1) % (pcque->capacity) == pcque->front; //循环队列:满为true;不满为false/* 示例说明:* 假设capacity=4(实际可使用的是3个位置):** 情况1:队列满* front=0, rear=3* (3+1)%4 = 0 == front → 满** 情况2:队列未满* front=0, rear=1* (1+1)%4 = 2 != front → 未满** 情况3:队列空* front=0, rear=0* (0+1)%4 = 1 != front → 未满(与判空条件区分)*/
}/*** @brief 向循环队列尾部插入一个元素** @param pcque 指向循环队列的指针(需确保非NULL)* @param x 要插入的元素值** @note 该函数执行以下操作:* 1. 检查队列指针有效性(调试阶段断言保护)* 2. 检查并扩容队列容量(通过CQCheckCapacity)* 3. 在队尾位置存入元素* 4. 更新队尾指针(实现循环移动)*/
//5.实现:“循环队列的入队”操作
void CQPush(CQ* pcque, CQDataType x)
{assert(pcque); //断言检查:确保队列指针有效,防止对空指针进行解引用CQCheckCapacity(pcque);//1.将入队的元素的存储在动态数组中pcque->a[pcque->rear] = x;//2.将循环队列的尾指针向后移动pcque->rear++;//3.更新尾指针指向的正确的位置(处理越界的情况)pcque->rear = pcque->rear % (pcque->capacity);//4.更新当前循环队列中元素的数量pcque->size++;
}/*** @brief 从循环队列头部移除一个元素(出队操作)** @param pcque 指向循环队列的指针(需确保非NULL)** @note 该函数执行以下操作:* 1. 检查队列指针有效性(断言保护)* 2. 检查队列是否为空(空队列直接返回)* 3. 移动队头指针实现出队* 4. 处理指针越界(循环修正)*/
//6. 实现:“循环队列的出队”操作
void CQPop(CQ* pcque)
{assert(pcque);//断言检查:确保队列指针有效,防止对空指针进行解引用//情况1:循环队列为空if (CQEmpty(pcque)) return;//情况2:循环队列非空//1.将循环队列的头指针向后移动pcque->front++;//2.更新头指针指向正确的位置(处理越界的情况)pcque->front = pcque->front % (pcque->capacity);//3.更新当前循环队列中元素的数量pcque->size--;
}//7.实现:“循环队列的获取队头的元素”操作
CQDataType CQFront(CQ* pcque)
{assert(pcque); //断言检查:确保队列指针有效,防止对空指针进行解引用//情况1:循环队列为空if (CQEmpty(pcque)) return -1;//情况2:循环队列非空return pcque->a[pcque->front];
}/*** @brief 获取循环队列的队尾元素** @param pcque 指向循环队列的指针(需确保非NULL)* @return CQDataType 返回队尾元素的值,队列为空时返回-1(需根据实际数据类型调整)** @note 该函数执行以下操作:* 1. 检查队列指针有效性(断言保护)* 2. 检查队列是否为空(空队列返回-1)* 3. 计算队尾元素的实际位置* 4. 返回队尾元素值*/
//8.实现:“循环队列的获取队尾的元素”操作
CQDataType CQRear(CQ* pcque)
{assert(pcque); //断言检查:确保队列指针有效,防止对空指针进行解引用//情况1:循环队列为空if (CQEmpty(pcque)) return -1;//情况2:循环队列非空//返回队尾的元素比返回队头的元素要复杂://原因:front指针指向的是队头的元素的位置;rear指针指向的是队尾的元素的后一个位置//怎么让队尾指针指向它的前一个位置?真的是pcirque->tail--;这么简单吗?//情况1:rear指向数组索引为0的位置(特殊情况-->rear应该指向的是capacity的位置,而不是数组下标为-1的位置)//情况2:rear指向数组索引的其他位置(正常情况)return pcque->a[(pcque->rear - 1 + pcque->capacity) % (pcque->capacity)];/* 关键计算:获取队尾元素的实际位置1. pcque->rear - 1 :理论上队尾元素在前一个位置2. + pcque->capacity :防止负数(当rear=0时)3. % pcque->capacity :确保结果在合法范围内示例:- 当capacity=5, rear=0时:(0-1+5)%5=4- 当capacity=5, rear=3时:(3-1+5)%5=2*/
}
测试文件
----------------------------------Test.c----------------------------------#include "CircularQueue.h"void TestCircularQueue()
{CQ cque;CQInit(&cque);printf("===== 循环队列测试开始 =====\n\n");/*--------------------“入队测试”--------------------*/printf("--- 测试1: 入队操作 ---\n");for (int i = 1; i <= 3; i++){printf("入队 %d\n", i);CQPush(&cque, i);CQPrint(&cque);}/*--------------------“判满测试”--------------------*/printf("--- 测试2: 队列满检查 ---\n");printf("队列是否已满? %s\n", CQFull(&cque) ? "是" : "否");printf("\n");/*--------------------“获取队头和队尾元素的测试”--------------------*/printf("--- 测试3: 访问队首/队尾元素 ---\n");printf("队首元素: %d\n", CQFront(&cque));printf("队尾元素: %d\n", CQRear(&cque));printf("\n");/*--------------------“出队测试”--------------------*/printf("--- 测试4: 出队操作 ---\n");for (int i = 0; i < 3; i++){printf("出队%d 得到:\n", CQFront(&cque));CQPop(&cque);CQPrint(&cque);}/*--------------------“再次入队测试”(测试循环特性)--------------------*/printf("--- 测试5: 循环特性测试 ---\n");for (int i = 6; i <= 8; i++){printf("入队 %d\n", i);CQPush(&cque, i);CQPrint(&cque);}/*--------------------“判空测试”--------------------*/printf("--- 测试6: 队列空检查 ---\n");printf("队列是否为空? %s\n", CQEmpty(&cque) ? "是" : "否");printf("\n");/*--------------------“销毁测试”--------------------*/printf("--- 测试7: 队列销毁 ---\n");CQ* pQueue = (CQ*)malloc(sizeof(CQ));CQInit(pQueue);for (int i = 10; i <= 12; i++){CQPush(pQueue, i);}printf("销毁前:\n");CQPrint(pQueue);CQDestroy(&pQueue);printf("销毁后,指针状态为 %s\n",pQueue == NULL ? "空" : "非空");printf("\n===== 所有测试完成 =====\n");
}int main()
{TestCircularQueue();return 0;
}
运行结果
循环队列的oj练习
622. 设计循环队列
题目介绍
方法一:
typedef struct
{//使用数组实现循环队列//1.定义队列的头指针 ---> 一个int变量(数组的下标)//2.定义队列的尾指针 ---> 一个int变量(数组下标,指向下一个插入位置)//3.记录队列中元素的数量 --> 一个int变量(实际可用空间为k,数组大小为k+1)//4.存储队列元素的数组 --> 一个int指针(动态分配)int head;int tail;int size;int* a;
} MyCircularQueue;// 函数前置声明
bool myCircularQueueIsFull(MyCircularQueue* pcirque);
bool myCircularQueueIsEmpty(MyCircularQueue* pcirque);MyCircularQueue* myCircularQueueCreate(int k)
{//实现:“循环队列的创建+初始化”操作//1)创建//1.1:分配队列结构体内存MyCircularQueue*pcirque=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));//1.2:分配储存元素数组的内存pcirque->a=(int*)malloc((k+1)*sizeof(int));//2)初始化pcirque->head=0;pcirque->tail=0;pcirque->size=k;//3)返回队列的结构体指针return pcirque;
}bool myCircularQueueEnQueue(MyCircularQueue* pcirque, int value)
{//实现:“循环队列的入队”操作//0.需要注意的是:循环队列中插入数据时需要先判断队列是否已经满了if(myCircularQueueIsFull(pcirque)) return false;//1.将入队的元素的值存入循环队列中pcirque->a[pcirque->tail]=value;//2.将循环队列的尾指针向后进行移动pcirque->tail++;//3.更新尾指针指向的位置使其指向合法pcirque->tail%=(pcirque->size+1);return true;}bool myCircularQueueDeQueue(MyCircularQueue* pcirque)
{//实现:“循环队列的出队”操作//0.出队操作要先判断队列是否为空if(myCircularQueueIsEmpty(pcirque)) return false;//将1和2步合成一步pcirque->head=(pcirque->head+1)%(pcirque->size+1);return true;
}int myCircularQueueFront(MyCircularQueue* pcirque)
{//实现:“循环队列的获取队头的元素”操作//0.需要先判断队列是否为空if(myCircularQueueIsEmpty(pcirque)) return -1;return pcirque->a[pcirque->head];
}int myCircularQueueRear(MyCircularQueue* pcirque)
{//0.需要先判断队列是否为空if(myCircularQueueIsEmpty(pcirque)) return -1;//返回队尾的元素比返回队头的元素要复杂,//原因:head指针指向的是队头的元素的位置;tail指针指向的时队尾的元素的后一个位置//怎么让队尾指针指向它的前一个位置?真的是pcirque->tail--这么简单吗?//情况1:tail指向数组索引为0的位置(特殊情况-->tail应该指向的是size+1的位置)//情况2:tail指向数组索引的其他位置(正常情况)return pcirque->a[(pcirque->tail-1+pcirque->size+1)%(pcirque->size+1)];
}bool myCircularQueueIsEmpty(MyCircularQueue* pcirque)
{//实现:“循环队列的判空”操作//空:true;不空:falsereturn pcirque->head==pcirque->tail;}bool myCircularQueueIsFull(MyCircularQueue* pcirque)
{//实现:“循环队列的判满”操作//满:true;不满:falsereturn (pcirque->tail+1)%(pcirque->size+1)==pcirque->head;}void myCircularQueueFree(MyCircularQueue* pcirque)
{//从内向外进行销毁//1.内部销毁:将动态数组销毁掉//2.外部销毁:将循环队列销毁掉free(pcirque->a);free(pcirque);
}
相关文章:
《数据结构初阶》【顺序栈 + 链式队列 + 循环队列】
《数据结构初阶》【顺序栈 链式队列 循环队列】 前言:什么是栈?栈有哪些实现方式?我们要选择哪种实现方式?--------------------------------什么是队列?队列有哪些实现方式?我们要选择哪种实现方式&…...
TCP和UDP
一、基本概念 1. TCP(传输控制协议, Transmission Control Protocol) 面向连接(Connection-oriented):在传输数据前,要建立连接(三次握手)可靠:保证数据按顺…...
AI小智本地前后端部署
AI小智本地部署 1.安装phpstudy 1.1.安装该软件是为了获得web环境:MySQLApacherediophpmyadmin,介绍如下: ✅ 1. MySQL(数据库) 作用:关系型数据库管理系统,存储结构化数据,如用…...
springboot+mysql+element-plus+vue完整实现汽车租赁系统
目录 一、项目介绍 二、项目截图 1.项目结构图 三、系统详细介绍 管理后台 1.登陆页 2.管理后台主页 3.汽车地点管理 4.汽车类别 5.汽车品牌 6.汽车信息 7.用户管理 8.举报管理 9.订单管理 10.轮播图管理 11.交互界面 12.图表管理 汽车租赁商城 1.首页 2.汽…...
直方图比较
目录 1、直方图比较的概念 2、直方图比较的主要原因 3、典型应用场景 4、基础直方图比较 5、多通道直方图比较 6、实时直方图检测 1、直方图比较的概念 直方图比较是通过数学方法计算两个直方图之间的相似度或差异度的技术。在计算机视觉中,直方图是对图像特征…...
【计算机视觉】3d人体重建:PIFu/PIFuHD:高精度三维人体数字化技术指南
深度解析PIFu/PIFuHD:高精度三维人体数字化技术指南 一、项目概述与技术突破1.1 技术定位与核心价值1.2 性能指标对比1.3 技术演进路线 二、环境配置与模型部署2.1 硬件要求2.2 软件安装基础环境配置附加组件安装 2.3 模型下载 三、核心算法解析3.1 网络架构设计多层…...
HTML05:超链接标签及应用
链接标签 <a href"path" target"目标窗口位置">链接文本或图像</a>文本超链接图像超链接 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>链接标签学习</title&…...
JAVA设计模式——(十一)建造者模式(Builder Pattern)
JAVA设计模式——(十一)建造者模式(Builder Pattern) 介绍理解实现Phone类具体手机类建造者测试 应用 介绍 建造者模式用于将对象的创建和表示进行分离,即对象创建的过程单独提取出来,作为建造者的职能&am…...
JavaScript 笔记 --- part7 --- JS进阶 (part2)
JS进阶(part2) 内置构造函数 Object: 用于创建对象 String: 用于创建字符串 Number: 用于创建数字 Array: 用于创建数组 Boolean: 用于创建布尔值 Function: 用于创建函数 Date: 用于创建日期对象 RegExp: 用于创建正则表达式对象 Error: 用于创建错误对象 Map: 用于…...
JavaScript 笔记 --- part8 --- JS进阶 (part3)
JS 进阶(part3) 深浅拷贝 只针对引用类型 浅拷贝 浅拷贝只拷贝外面一层的属性, 如果对象里面还有对象, 那么这个对象的引用会被拷贝过去, 所以修改其中一个属性会影响到另一个对象 const obj {name: "zhangsan",age: 18,child: {name: "xiaoming",ag…...
LeetCode 热题 100 46. 全排列
LeetCode 热题 100 | 46. 全排列 大家好,今天我们来解决一道经典的算法题——全排列。这道题在 LeetCode 上被标记为中等难度,要求给定一个不含重复数字的数组 nums,返回其所有可能的全排列。全排列是排列组合中的经典问题,通常可…...
双目视觉的核心目标
通过计算左右图像中同一物体的像素点位置差(视差),进而还原出物体在三维空间中的真实位置。 双目视觉的核心流程: 1️⃣ 相机标定(calibration) 获取左右相机的内参、外参和畸变参数。 获取投影矩阵 Q&a…...
《类和对象(上)》
引言: 上次我们学习了C的一些入门基础,但其实还没有入门C,想要入门C,肯定是要把类和对象这部分学透彻,这次先来学习类和对象(上) 一:类的定义 1. 类定义格式: class为…...
强化学习ppo算法在大语言模型上跑通
最近在研究强化学习,目标是想在我的机械臂上跑出效果。ppo算法是强化学习领域的经典算法,在网上检索ppo算法,出现的大部分文章都是互相抄袭,上来都列公式,让人看得云里雾里。偶然间发现一个deepspeed使用的example(链接…...
告别散乱的 @ExceptionHandler:实现统一、可维护的 Spring Boot 错误处理
Spring Boot 的异常处理机制一直都烂得可以。即便到了 2025 年,有了这么多进步和新版本,开发者们发现自己还是在跟 ControllerAdvice、分散各处的 ExceptionHandler 方法以及五花八门的响应结构较劲。这真的是一团糟。 无论你是在构建 REST API、微服务…...
Ubuntu安装编译环境
1. 安装基础编译工具链(GCC, G, Make 等) sudo apt update # 只更新索引信息,不安装软件 sudo apt install build-essential这会安装以下核心组件: • gcc (GNU C 编译器) • g (GNU C 编译器) • make (构建工具) • libc-…...
Scrapy爬虫实战:如何用Rules实现高效数据采集
Scrapy是一个强大的Python爬虫框架,而其中的Rules类则为爬虫提供了更高级的控制方式。本文将详细介绍如何在Scrapy中使用Rules,以及各个参数的具体作用,并结合实际场景说明Rules的必要性。 为什么需要Rules? 在Web爬取过程中&…...
ERP系统源码,有演示,开发文档、数据库文档齐全,支持二次开发
一套开箱即用的云端ERP系统源代码,小型工厂ERP系统源码 SaaS ERP是一套开箱即用的云端ERP系统,有演示,开发文档,数据库文档齐全,自主版权落地实例,适合项目二开。 SaaS ERP具有高度的灵活性和可扩展性&am…...
如何将腾讯云的测试集成到自己的SpringBoot中
1.创建Util 我们将之前测试的test复制过来, 1.将方法里面的固定参数设置出来private 2.将方法里面的变化参数设置作为传入参数 3.返回String类型的URL地址 完整代码如下: package org.huangyingyuan.utils;import com.qcloud.cos.COSClient; import…...
Java后端开发day41--IO流(一)--FileOutputStreamFileInputStream
(以下内容全部来自上述课程) IO流:存储和读取数据的解决方案 I:input O:output 流:像水流一样传输数据 1. 流的分类 纯文本文件:Windows自带的记事本打开就能读懂 2. IO流的体系 3 字节流 3.1 FileOutputStream 操…...
Spring 框架中 @Configuration 注解详解
在 Spring 框架的开发过程中,Configuration注解是一个极为重要的存在,它让开发者能够以一种更加简洁、灵活的方式来管理应用程序的配置信息,极大地提升了开发效率和代码的可维护性。 本文将深入剖析Configuration注解的方方面面,…...
手机打电话时由对方DTMF响应切换多级IVR语音应答(一)
手机打电话时由对方DTMF响应切换多级IVR语音应答(一) --本地AI电话机器人 一、前言 经前面的系列篇章中,我们实现了拦截手机打电话的声音、根据通话对方声音提取DTMF字符。由此,我们通往AI电话机器人的道路就畅通无阻了。 如果…...
GM DC Monitor v2.0 - 平台自定义-使用说明
平台支持对LOGO、登录页背景图、平台名称、小标题名称、网址、告警中心、知识库名称进行自定义,自定义完以后,平台将更加适合您的工作场景! LOGO自定义建议使用100*80的png背景透明图片,大小不超过200k 登录背景建议使用1920*71…...
实验-数字电路设计2-复用器和七段数码管(数字逻辑)
目录 一、实验内容 二、实验步骤 2.1 复用器的设计 2.2 七段数码管的设计 三、调试过程 3.1 复用器调试过程 3.2 七段数码管的调试过程 四、实验使用环境 五、实验小结和思考 一、实验内容 a) 介绍 在这次实验中,你将熟悉 Logisim 的操作流程ÿ…...
HTTP/HTTPS协议(请求响应模型、状态码)
目录 HTTP/HTTPS协议简介 HTTP协议 HTTPS协议 请求 - 响应模型 HTTP请求 (二)HTTP响应 HTTPS协议与HTTP协议在请求 - 响应模型中的区别 HTTP/HTTPS协议简介 HTTP协议 定义 HTTP(HyperText Transfer Protocol)即超文本传输…...
详解RabbitMQ工作模式之路由模式
目录 路由模式 概念介绍 工作原理 特点 应用场景 实现步骤 代码案例 引入依赖 常量类 编写生产者代码 编写消费者1代码 编写消费者2代码 运行代码 路由模式 概念介绍 路由模式是发布订阅模式的变种, 在发布订阅基础上, 增加路由key。 发布订阅模式是⽆条件的将所有…...
青少年编程与数学 02-018 C++数据结构与算法 26课题、数据压缩算法
青少年编程与数学 02-018 C数据结构与算法 26课题、数据压缩算法 一、无损压缩算法1. Huffman编码2. Lempel-Ziv-Welch (LZW) 编码3. Run-Length Encoding (RLE) 二、有损压缩算法1. DEFLATE(ZIP压缩)2. Brotli3. LZMA4. Zstandard (Zstd) 总结 课题摘要…...
Sim Studio 是一个开源的代理工作流程构建器。Sim Studio 的界面是一种轻量级、直观的方式,可快速构建和部署LLMs与您最喜欢的工具连接
一、软件介绍 文末提供程序和源码下载 Sim Studio开源程序 是一个功能强大、用户友好的平台,用于构建、测试和优化代理工作流程,Sim Studio 是一个开源的代理工作流程构建器。Sim Studio 的界面是一种轻量级、直观的方式,可快速构建和部署…...
基于Boost库、Jsoncpp、cppjieba、cpp-httplib等构建Boost搜索引擎
⭐️个人主页:小羊 ⭐️所属专栏:项目 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 项目背景技术栈和项目环境正排索引和倒排索引数据去标签与清洗下载数据源去标签 建立索引构建正排索引构建倒排索引 建立搜索引擎h…...
文本三剑客
目录 1.文本三剑客 2.awk 常见的内置变量 3.sed 选项: 脚本语法: 查找替换: 步进: 1.文本三剑客 awk;sed;grep 读取方式为:vim先将整个文件放入内存中进行编辑;awk是先将文…...
使用 Microsoft 365 Copilot 上传手机图片,实现更高效的信息提取
过去,如果你想从手机上的图片中提取信息或获取洞察,流程往往十分繁琐:需要先把图片发到邮箱,再下载到电脑,最后才能上传到 Copilot 聊天框中。 现在不必如此了!在你熟悉的 Microsoft 365 Windows 应用或网…...
simulink使能子系统的四种配置
目录 前言 一、模型 二、特性 组合1:使能模块为保持,输出为保持 组合2:使能模块为保持,输出为重置 组合3:使能模块为重置,输出为保持 组合4:使能模块为重置,输出为重置 仓库…...
一、Hadoop历史发展与优劣势
引言:大数据浪潮中的“大象”——Hadoop 的足迹与权衡 当数据以前所未有的速度和规模涌现,大数据时代悄然来临,传统的数据处理方式显得力不从心。在这样的背景下,Hadoop 应运而生,如同一头强健而可靠的大象࿰…...
conda配置好的pytorch在jupyter中如何配置
配置 其实不用再配置了 如下图(主要是激活pytorch环境,再jupyter notebook) jupyter运行快捷键shiftenter 新建文件夹folder,新建notebook 使用 帮助文档(两种方式) ctrl/ 注释...
文本三剑客试题
目录 1找出/etc/passwd文件下的root开头的行 2找出/etc/passwd 含有root 的行 3找出/etc/passwd 文件中 root开头或 mail 开头的行 4过滤出 /etc/passwd文件中已bin开头的行,并显示行号 5过滤掉/etc/passwd文件中 root开头的行 6.在当前目录下所有.cc 的文件中…...
vscode docker 调试
目录 启动docker: vscode docker 调试 如果已经安装docker并且启动了。 启动docker: docker exec -it nlf /bin/bash vscode docker 调试 按照图中1 2 3 的顺序,进入,可以加载docker进行调试了。...
【程序人生】“阶段总结“-安危相易
好久没有坐下静下心回顾过去一段时间内发生的事以及经历过后的感想。今天趁着五一假期的机会细细盘一盘过去这段时间内的点点感悟吧...... 记得上一次的阶段总结停留在了24年的11月底。当初计划的是每月月底会抽出时间来进行一次深度的回顾与阶段总结,但是计划总赶…...
【Linux】深入理解Linux基础IO:从文件描述符到缓冲区设计
目录 一、文件理解(复习) 1、理解概念复习 (1)狭义理解 (2)广义理解 (3)文件操作的归类认知 (4)系统角度 2、C语言文件复习 (1࿰…...
【纪念我的365天】我的创作纪念日
机缘 最开始接触csdn时我从没想过我会是博客的创作者,最初我认为它是一个为我解决问题的作业神器,开始接触编程时什么都不懂,为各种问题查阅资料,可偏偏就是无法越过这道坎。于是机遇巧合之下遇到一个人他教我,也是他…...
方法:批量识别图片区域文字并重命名,批量识别指定区域内容改名,基于QT和阿里云的实现方案,详细方法
基于QT和阿里云的图片区域文字识别与批量重命名方案 项目场景 企业档案管理:批量处理扫描合同、发票等文档,根据编号或关键信息自动重命名文件医疗影像管理:识别X光、CT等医学影像中的患者信息,按姓名+检查日期重命名电商订单处理:从订单截图中提…...
民宿管理系统5
管理员管理: 新增管理员信息: 前端效果: 前端代码: <body> <div class"layui-fluid"><div class"layui-row"><div class"layui-form"><div class"layui-form-i…...
AI日报 · 2025年5月05日|雅诗兰黛与微软合作成立 AI 创新实验室,加速美妆产品研发与营销
1、苹果与 Anthropic 深化合作,内部测试 AI 驱动的新版 Xcode 据多方报道,苹果公司正与人工智能初创公司 Anthropic 合作,开发集成 AI 功能的新一代 Xcode 开发平台。该平台旨在利用 Anthropic 强大的 Claude Sonnet 模型,为开发…...
Matlab实现基于CNN-GRU的锂电池SOH估计
Matlab实现基于CNN-GRU的锂电池SOH估计 目录 Matlab实现基于CNN-GRU的锂电池SOH估计效果一览基本介绍程序设计参考资料 效果一览 基本介绍 锂电池SOH估计!基于CNN-GRU的锂电池健康状态估计。CNN-GRU模型通过融合局部特征提取与长期依赖建模,显著提升了锂…...
神经网络在专家系统中的应用:从符号逻辑到连接主义的融合创新
自人工智能作为一个学科面世以来,关于它的研究途径就存在两种不同的观点。一种观点主张对人脑的结构及机理开展研究,并通过大规模集成简单信息处理单元来模拟人脑对信息的处理,神经网络是这一观点的代表。关于这方面的研究一般被称为连接机制…...
【Hive入门】Hive安全管理与权限控制:基于SQL标准的授权GRANT REVOKE深度解析
目录 引言 1 Hive权限模型概述 2 SQL标准授权基础 2.1 核心概念解析 2.2 授权模型工作流程 3 GRANT/REVOKE语法详解 3.1 基础授权语法 3.2 权限回收语法 3.3 参数说明 4 授权场景 4.1 基础授权示例 4.2 列级权限控制 4.3 视图权限管理 5 权限查询与验证 5.1 查看…...
详解RabbitMQ工作模式之发布订阅模式
目录 发布订阅模式 概念 概念介绍 特点和优势 应用场景 注意事项 代码案例 引入依赖 常量类 编写生产者代码 编写消费者1代码 运行代码 发布订阅模式 概念 RabbitMQ的发布订阅模式(Publish/Subscribe)是一种消息传递模式,它允许消…...
JobHistory Server的配置和启动
在 Hadoop 集群里,JobHistory Server(JHS)负责为所有已完成的 MapReduce 作业提供元数据与 Web 可视化;只有它启动并配置正确,开发者才能通过 http://<host>:19888 查看作业的执行详情、计数器和任务日志…...
刷leetcodehot100返航版--哈希表5/5
回顾一下之前做的哈希,貌似只有用到 unordered_set:存储无序元素unordered_map:存储无序键值对 代码随想录 常用代码模板2——数据结构 - AcWing C知识回顾-CSDN博客 1.两数之和5/5【30min】 1. 两数之和 - 力扣(LeetCode&…...
【STM32 学习笔记】GPIO输入与输出
GPIO详解 一、GPIO基本概念 GPIO(通用输入输出)是微控制器与外部设备交互的核心接口,具有以下特性: 可编程控制输入/输出模式支持数字信号的读取与输出集成多种保护机制复用功能支持片上外设连接 二、GPIO位结构解析 2.1 保护二…...
网狐飞云娱乐三端源码深度实测:组件结构拆解与部署Bug复盘指南(附代码分析)
本文基于“网狐系列三网通飞云娱乐电玩”源码包,从项目结构、界面逻辑、三端兼容性、机器人机制、本地部署实践等多维角度进行全面剖析,并附录多个真实报错修复案例与源码片段。本组件适用于本地学习、框架研究与技术测试,不具备线上部署条件…...