目录
一.栈
1.栈的定义
2.顺序栈的功能实现
2.1.顺序栈的定义
2.2.顺序栈的初始化
2.3.顺序栈的判空
2.4.顺序栈的入栈
2.5.顺序栈的出栈
2.6.顺序栈的取栈顶元素
2.7.顺序栈的求栈的大小
2.8.顺序栈的销毁
2.9.完整程序
Stack.h
Stack.c
test.c
二.队列
1.队列的定义
2.链式队列的功能实现
2.1.链式队列的定义
2.2.链式队列的初始化
2.3.链式队列的判空
2.4.链式队列的入队
2.5.链式队列的出队
2.6.链式队列的取队头元素
2.7.链式队列的取队尾元素
2.8.链式队列的求队列的大小
2.9.链式队列的销毁
2.10.完整程序
Queue.h
Queue.c
test.c
三.栈与队列刷题
题一:有效的括号
题二:用队列实现栈
题三:用栈实现队列
题四:设计循环队列
一.栈
1.栈的定义
栈是只允许在一端进行插入或删除操作的线性表
逻辑结构:与普通线性表相同
数据的运算:插入,删除操作有区别
特点:后进先出,即Last In First Out(LIFO)
几个重要术语:
- 空栈;
- 栈顶:允许插入和删除的一端
- 栈底:不允许插入和删除的一端
- 栈顶元素
- 栈底元素
2.顺序栈的功能实现
2.1.顺序栈的定义
typedef int STDataType;
typedef struct Stack
{
STDataType* a;//动态开辟数组
int top;//标识栈顶位置
int capacity;//栈可以容纳的数据个数
}ST;
首先,定义动态数组a,采用动态数组的方式主要是便于后期容量的扩充;然后,定义一个变量top用于标识栈顶位置;最后,定义一个变量capacity用于统计栈可以容纳的数据个数。
2.2.顺序栈的初始化
void StackInit(ST* ps)
{
//判空
assert(ps);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
首先,将指向栈中数据内存空间的指针a初始化为NULL;然后,将指向栈顶元素的变量top初始化为0;最后,再将标识栈当前容量的capacity初始化为0即可。
调试分析:
2.3.顺序栈的判空
bool StackEmpty(ST* ps)
{
//判空
assert(ps);
return ps->top == 0;
}
判断一个栈是否为空,若为空则返回true,否则返回false。这里需要特别说明的一点是,我们将top指向栈顶元素的下一个位置,而非指向栈顶元素本身。
注意:
- 当top初始化为:top=0;此时top指向栈顶元素的下一个位置
- 当top初始化为:top=-1;此时top指向栈顶元素
调试分析:
2.4.顺序栈的入栈
void StackPush(ST* ps, STDataType x)
{
//判空
assert(ps);
//判断容量是否已满
//top标识的是最后一个数据的下一个位置,如果想要指向最后一个数据,初始时top=-1
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//为空就开辟四个空间,不为空,就扩容至二倍
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
printf("malloc fail\n");
exit(-1);
}
//将新开辟的内存空间的首地址tmp赋值给a
ps->a = tmp;
//更新capacity
ps->capacity = newCapacity;
}
//入栈
ps->a[ps->top] = x;
//top指向栈顶元素的下一个位置
ps->top++;
}
在入栈之前,首先需要对顺序栈的空间容量进行检查,这里使用三目运算符进行判断:若当前容量capacity为空,则开辟4个数据的内存空间;若当前容量非空但已满,则将capacity的大小扩容至2*capacity。然后调用realloc函数开辟新的内存空间,并返回新的内存空间的起始地址tmp。接着将新开辟的内存空间的起始地址tmp赋值给a,让a指向这片新开辟的空间。最后,将x插入栈顶位置,并让top继续指向栈顶元素的下一个位置。
调试分析:
2.5.顺序栈的出栈
void StackPop(ST* ps)
{
//判空
assert(ps);
//判断栈是否为空
assert(!StackEmpty(ps));
//出栈
ps->top--;
}
在出栈之前,首先需要调用StackEmpty(ps)函数来判断栈是否为空,若不为空,则可以出栈。出栈操作就是将top减一,需要注意的是,出栈的数据还残留在内存中,只是逻辑上被删除了。
调试分析:
2.6.顺序栈的取栈顶元素
STDataType StackTop(ST* ps)
{
//判空
assert(ps);
//判断栈是否为空
assert(!StackEmpty(ps));
//取栈顶元素
return ps->a[ps->top - 1];
}
在取栈顶元素之前,首先需要调用函数StackEmpty(ps)判断栈是否为空,若栈不为空则可以取栈顶元素。因为top指向栈顶元素的下一个位置,所以top需要先进行减一,再取栈顶元素。
调试分析:
2.7.顺序栈的求栈的大小
int StackSize(ST* ps)
{
//判空
assert(ps);
return ps->top;
}
因为top指向栈顶元素的下一个位置,而top的下标又是从0开始的,所以top所在位置的下标就是所求栈的大小,也就是栈中元素个数。
调试分析:
2.8.顺序栈的销毁
void StackDestory(ST* ps)
{
//判空
assert(ps);
//realloc开辟的空间,需要调用free释放
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
对于栈的销毁,首先需要释放由realloc动态申请开辟的内存空间,这里主要搭配free函数进行释放。然后将top和capacity初始化为0。
调试分析:
2.9.完整程序
Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;//动态开辟数组
int top;//标识栈顶位置
int capacity;//栈可以容纳的数据个数
}ST;
//初始化
void StackInit(ST* ps);
//销毁
void StackDestory(ST* ps);
//入栈
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
//取栈顶元素
STDataType StackTop(ST* ps);
//判空
bool StackEmpty(ST* ps);
//栈大小
int StackSize(ST* ps);
Stack.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
//初始化
void StackInit(ST* ps)
{
//判空
assert(ps);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
//销毁
void StackDestory(ST* ps)
{
//判空
assert(ps);
//realloc开辟的空间,需要调用free释放
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
//入栈
void StackPush(ST* ps, STDataType x)
{
//判空
assert(ps);
//判断容量是否已满
//top标识的是最后一个数据的下一个位置,如果想要指向最后一个数据,初始时top=-1
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//为空就开辟四个空间,不为空,就扩容至二倍
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
printf("malloc fail\n");
exit(-1);
}
//将新开辟的内存空间的首地址tmp赋值给a
ps->a = tmp;
//更新capacity
ps->capacity = newCapacity;
}
//入栈
ps->a[ps->top] = x;
//top指向栈顶元素的下一个位置
ps->top++;
}
//出栈
void StackPop(ST* ps)
{
//判空
assert(ps);
//判断栈是否为空
assert(!StackEmpty(ps));
//出栈
ps->top--;
}
//取栈顶元素
STDataType StackTop(ST* ps)
{
//判空
assert(ps);
//判断栈是否为空
assert(!StackEmpty(ps));
//取栈顶元素
return ps->a[ps->top - 1];
}
//判空
bool StackEmpty(ST* ps)
{
//判空
assert(ps);
return ps->top == 0;
}
//栈大小
int StackSize(ST* ps)
{
//判空
assert(ps);
return ps->top;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
void TestStack()
{
ST st;
//初始化
StackInit(&st);
//入栈
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st, 5);
//出栈
while (!StackEmpty(&st))
{
//读取栈顶元素
printf("%d ",StackTop(&st));
//出栈
StackPop(&st);
}
printf("\n");
//销毁
StackDestory(&st);
}
int main()
{
TestStack();
return 0;
}
二.队列
1.队列的定义
队列是只允许在一端进行插入(入队),在另一端删除的线性表(出队)。
队列的特点:先进先出,即First In First Out(FIFO)。
几个重要术语:
- 空队列
- 队头:允许删除的一端
- 队尾:允许插入的一端
- 队头元素
- 队尾元素
2.链式队列的功能实现
2.1.链式队列的定义
typedef int QDataType;
//链式队列结点
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
//链式队列
typedef struct Queue
{
QNode* head;//队列的头
QNode* tail;//队列的尾
}Queue;
2.2.链式队列的初始化
void QueueInit(Queue* pq)
{
//判空
assert(pq);
//不带哨兵位
pq->head = pq->tail = NULL;
}
这里实现的是不带头结点的链式队列,初始时front和rear都指向NULL。
调试分析:
2.3.链式队列的判空
bool QueueEmpty(Queue* pq)
{
//判空
assert(pq);
//看队头元素是否为NULL
return pq->head == NULL;
}
判断队列是否为空,只需要看队头元素是否为NULL。
调试分析:
2.4.链式队列的入队
void QueuePush(Queue* pq, QDataType x)
{
//判空
assert(pq);
//创建新结点newnode
QNode* newnode = (QNode*)malloc(sizeof(QNode));
//判空
if (newnode == NULL)
{
perror("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
//链表为空
if (pq->tail == NULL)
{
//在空队列中插入第一个元素
//修改队头队尾指针
pq->head = pq->tail = newnode;
}
else
{
//链表不为空
pq->tail->next = newnode;//新结点插入到tail结点之后
pq->tail = newnode;//修改tail指针
}
}
在入队之前,需要调用malloc函数开辟一个新结点newnode。对于不带头结点的情况,第一个元素入队时要特殊处理。因为一开始这两个指针都是指向NULL的,所以插入第一个元素时对这两个指针都要进行修改。
调试分析:
2.5.链式队列的出队
void QueuePop(Queue* pq)
{
//判空
assert(pq);
//判断队列是否为空
assert(!QueueEmpty(pq));
//只含一个结点
if (pq->head->next == NULL)
{
free(pq->head);//释放最后一个结点
pq->head = pq->tail = NULL;//将队头与队尾指针都置空
}
else//含多个结点
{
QNode* next = pq->head->next;//next为队头结点的下一个结点
free(pq->head);//释放队头结点
pq->head = next;//修改头指针
}
}
在出队之前,首先需要判断队列是否为空,若队列为空,则无法进行出队操作。当只剩下最后一个元素未出队时需特殊处理。首先需要调用free函数释放首元素,然后将队头指针与队尾指针都置为NULL。当还剩多个元素时,首先找到队头结点的下一个结点,然后调用free函数释放掉队头结点,最后将队头指针head指向next,更新队头元素。
调试分析:
2.6.链式队列的取队头元素
QDataType QueueFront(Queue* pq)
{
//判空
assert(pq);
//判断队列是否为空
assert(!QueueEmpty(pq));
//取队头元素
return pq->head->data;
}
在取队头元素之前,首选需要调用函数QueueEmpty(pq)判断队列是否为空,若为空,则无法取队头元素。若队列不为空,则取队头元素。
调试分析:
2.7.链式队列的取队尾元素
QDataType QueueBack(Queue* pq)
{
assert(pq);
//判断队列是否为空
assert(!QueueEmpty(pq));
//取队尾元素
return pq->tail->data;
}
在取队尾元素之前,首选需要调用函数QueueEmpty(pq)判断队列是否为空,若为空,则无法取队尾元素。若队列不为空,则取队尾元素。
调试分析:
2.8.链式队列的求队列的大小
int QueueSize(Queue* pq)
{
//判空
assert(pq);
//设置指针变量cur,用于遍历队列
QNode* cur = pq->head;
int size = 0;
//遍历队列
while (cur)
{
++size;
cur = cur->next;
}
return size;
}
在求队列的元素个数时,首先设置指针变量cur,并使其指向队头元素,然后设置一个变量size,用于统计元素个数。让指针变量cur进入while循环遍历整个队列,每遍历一个元素,size就自增1,直到cur走到队尾,则跳出循环,并返回size大小。
调试分析:
2.9.链式队列的销毁
void QueueDestory(Queue* pq)
{
//判空
assert(pq);
//设置指针变量cur,用于遍历队列
QNode* cur = pq->head;
//遍历队列
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
//将队头指针,队尾指针均置为空
pq->head = pq->tail = NULL;
}
首先设置指针变量cur,并使其指向队头元素,然后让指针变量cur进入while循环遍历整个队列,每遍历一个元素,就将其前一个元素释放掉,直到cur走到队尾,则跳出循环。在销毁整个链表之后要将队头指针与队尾指针均置为NULL。
调试分析:
2.10.完整程序
Queue.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int QDataType;
//链式队列结点
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
//链式队列
typedef struct Queue
{
QNode* head;//队列的头
QNode* tail;//队列的尾
}Queue;
//初始化
void QueueInit(Queue* pq);
//销毁
void QueueDestory(Queue* pq);
//入队
void QueuePush(Queue* pq, QDataType x);
//出队
void QueuePop(Queue* pq);
//取队头元素
QDataType QueueFront(Queue* pq);
//取队尾元素
QDataType QueueBack(Queue* pq);
//判空
bool QueueEmpty(Queue* pq);
//总计元素个数
int QueueSize(Queue* pq);
Queue.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
//初始化
void QueueInit(Queue* pq)
{
//判空
assert(pq);
//不带哨兵位
pq->head = pq->tail = NULL;
}
//销毁
void QueueDestory(Queue* pq)
{
//判空
assert(pq);
//设置指针变量cur,用于遍历队列
QNode* cur = pq->head;
//遍历队列
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
//将队头指针,队尾指针均置为空
pq->head = pq->tail = NULL;
}
//入队
void QueuePush(Queue* pq, QDataType x)
{
//判空
assert(pq);
//创建新结点newnode
QNode* newnode = (QNode*)malloc(sizeof(QNode));
//判空
if (newnode == NULL)
{
perror("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
//链表为空
if (pq->tail == NULL)
{
//在空队列中插入第一个元素
//修改队头队尾指针
pq->head = pq->tail = newnode;
}
else
{
//链表不为空
pq->tail->next = newnode;//新结点插入到tail结点之后
pq->tail = newnode;//修改tail指针
}
}
//出队
void QueuePop(Queue* pq)
{
//判空
assert(pq);
//判断队列是否为空
assert(!QueueEmpty(pq));
//只含一个结点
if (pq->head->next == NULL)
{
free(pq->head);//释放最后一个结点
pq->head = pq->tail = NULL;//将队头与队尾指针都置空
}
else//含多个结点
{
QNode* next = pq->head->next;//next为队头结点的下一个结点
free(pq->head);//释放队头结点
pq->head = next;//修改头指针
}
}
//取队头元素
QDataType QueueFront(Queue* pq)
{
//判空
assert(pq);
//判断队列是否为空
assert(!QueueEmpty(pq));
//取队头元素
return pq->head->data;
}
//取队尾元素
QDataType QueueBack(Queue* pq)
{
assert(pq);
//判断队列是否为空
assert(!QueueEmpty(pq));
//取队尾元素
return pq->tail->data;
}
//判空
bool QueueEmpty(Queue* pq)
{
//判空
assert(pq);
//看队头元素是否为NULL
return pq->head == NULL;
}
//总计元素个数
int QueueSize(Queue* pq)
{
//判空
assert(pq);
//设置指针变量cur,用于遍历队列
QNode* cur = pq->head;
int size = 0;
//遍历队列
while (cur)
{
++size;
cur = cur->next;
}
return size;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
void TestQueue()
{
Queue q;
//初始化
QueueInit(&q);
//入队
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePush(&q, 5);
//出队
while (!QueueEmpty(&q))
{
//取对头元素
printf("%d ",QueueFront(&q));
//出队
QueuePop(&q);
}
printf("\n");
//销毁
QueueDestory(&q);
}
int main()
{
test();
return 0;
}
三.栈与队列刷题
题一:有效的括号
题目描述:
给定一个只包括'(',')','{','}','[',']'的字符串s,判断字符串是否有效
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合
- 左括号必须以正确的顺序闭合
分析:
我们可以把左括号依次压入栈中,越往后被压入的左括号越先被弹出栈进行匹配。每出现一个右括号,就“消耗”一个左括号进行匹配检查,这个过程对应出栈操作。扫描一连串括号的过程中若发现下列情况都说明括号序列不合法,终止操作。
- 弹出栈的左括号与刚刚遇到要检查的右括号不匹配;
- 扫描到右括号时发现栈空了(右括号单身);
- 处理完所有括号后,栈非空(右括号单身)。
实现:
bool isValid(char* s)
{
ST st;
//初始化
StackInit(&st);
while (*s)
{
if (*s == '(' || *s == '[' || *s == '{')//是左括号
{
//入栈
StackPush(&st, *s);
++s;
}
else//是右括号
{
//判断栈是否为空,可能出现字符串只含右括号的情况
if (StackEmpty(&st))
{
//销毁
StackDestory(&st);
return false;
}
//若栈不为空,则读取栈顶元素并出栈
STDataType top = StackTop(&st);// 读取栈顶元素
StackPop(&st);//出栈
//看栈顶元素与当前字符是否匹配
if ((top == '{' && *s == '}') || (top == '(' && *s == ')') || (top == '[' && *s == ']'))
{
//匹配
++s;
}
else
{
//不匹配则销毁
StackDestory(&st);
return false;
}
}
}
//匹配完之后,判断栈是否为空,可能出现字符串只含左括号的情况
bool ret = StackEmpty(&st);
//销毁
StackDestory(&st);
return ret;
}
题二:用队列实现栈
题目描述:
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push,top,pop和empty)。
实现MyStack类:
void push(int x):将元素x压入栈顶
void pop():移除并返回栈顶元素
int top():返回栈顶元素
boolean empty():如果栈是空的,返回true;否则,返回false
分析:
队列queue1保存原始输入数据,队列queue2作为临时队列缓存数据。当进行stack_pop操作时,先将queue1里除最后一个元素外全部出队,并将出队的数据保存在临时队列queue2里,然后保存queue1的最后一个元素,最后再将queue2里的全部元素出队,且出队的元素重新放进queue1里,返回保存的queue1最后的元素。
实现:
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int QDataType;
//链式队列结点
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
//链式队列
typedef struct Queue
{
QNode* head;//队列的头
QNode* tail;//队列的尾
}Queue;
//初始化
void QueueInit(Queue* pq);
//销毁
void QueueDestory(Queue* pq);
//入队
void QueuePush(Queue* pq, QDataType x);
//出队
void QueuePop(Queue* pq);
//取队头元素
QDataType QueueFront(Queue* pq);
//取队尾元素
QDataType QueueBack(Queue* pq);
//判空
bool QueueEmpty(Queue* pq);
//总计元素个数
int QueueSize(Queue* pq);
/**********用队列实现栈**********/
//构造一个包含两个队列的栈
typedef struct
{
Queue q1;
Queue q2;
}MyStack;
//初始化栈
MyStack* myStackCreate()
{
//调用malloc为栈开辟内存空间
MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
//初始化两个队列
QueueInit(&obj->q1);
QueueInit(&obj->q2);
return obj;
}
//入栈
void myStackPush(MyStack* obj, int x)
{
//判空
assert(obj);
//往不为空的队列插入元素,若两个队列均为空,插入其中一个即可
if (!QueueEmpty(&obj->q1))
{
QueuePush(&obj->q1, x);
}
else
{
QueuePush(&obj->q2, x);
}
}
//出栈
int myStackPop(MyStack* obj)
{
//判空
assert(obj);
//假设q1队列为空,q2队列不为空
Queue* emptyQ = &obj->q1;
Queue* nonEmptyQ = &obj->q2;
//若q1队列不为空,则将q2队列设为空,q1队列设为非空
if (!QueueEmpty(&obj->q1))
{
emptyQ = &obj->q2;
nonEmptyQ = &obj->q1;
}
//把非空队列的数据导入到空队列,也就是将非空队列的前n-1个数据导入至另一个空队列
while (QueueSize(nonEmptyQ) > 1)
{
QueuePush(emptyQ,QueueFront(nonEmptyQ));//取非空队列对头的数据插入到空队列中去
QueuePop(nonEmptyQ);//出队
}
int top = QueueFront(nonEmptyQ);//取非空队列的队头元素
QueuePop(nonEmptyQ);//出队
return top;
}
//取栈顶元素
int myStackTop(MyStack* obj)
{
//判空
assert(obj);
//若q1队列不为空,则取q1队尾元素
if (!QueueEmpty(&obj->q1))
{
return QueueBack(&obj->q1);
}
else
{
//若q2队列不为空,则取q2队尾元素
return QueueBack(&obj->q2);
}
}
//判断栈是否为空
bool myStackEmpty(MyStack* obj)
{
//判空
assert(obj);
//只有当两个队列均为空时,才表示栈为空
return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}
//销毁栈
void myStackFree(MyStack* obj)
{
//判空
assert(obj);
//销毁队列q1和q2
QueueDestory(&obj->q1);
QueueDestory(&obj->q2);
//释放栈
free(obj);
}
题三:用栈实现队列
题目描述:
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push,pop,peek,empty)
实现MyQueue类:
void push(int x):将元素x推到队列的末尾
int pop():从队列的开头移除并返回元素
int peek():返回队列开头的元素
boolean empty():如果队列为空,返回true;否则,返回false
分析:
用两个栈实现一个队列,设置其中一个栈pushst专门入数据,另一个栈popst专门出数据。若要入队,则进pushst栈;若要出队,首先看popst栈是否为空,如果为空,就先把pushst栈的数据转移过来,然后出队,如果不为空,则直接出popst栈的数据。
实现:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;//动态开辟数组
int top;//标识栈顶位置
int capacity;//栈可以容纳的数据个数
}ST;
//初始化
void StackInit(ST* ps);
//销毁
void StackDestory(ST* ps);
//入栈
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
//取栈顶元素
STDataType StackTop(ST* ps);
//判空
bool StackEmpty(ST* ps);
//栈大小
int StackSize(ST* ps);
/**********用栈实现队列**********/
//构造一个包含两个栈的队列
typedef struct
{
ST pushst;
ST popst;
}MyQueue;
//初始化队列
MyQueue* myQueueCreate()
{
//调用malloc为栈开辟内存空间
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
//初始化两个栈
StackInit(&obj->pushst);
StackInit(&obj->popst);
return obj;
}
//入队
void myQueuePush(MyQueue* obj, int x)
{
//判空
assert(obj);
//入栈
StackPush(&obj->pushst, x);
}
//出队
int myQueuePop(MyQueue* obj)
{
//判空
assert(obj);
//出队前首先要判断popst栈是否为空
if (StackEmpty(&obj->popst))
{
//如果popst栈为空,则把pushst栈的数据拷贝过来
while (!StackEmpty(&obj->pushst))
{
//将pushst栈的栈顶元素压入popst栈
StackPush(&obj->popst, StackTop(&obj->pushst));
//pushst栈的栈顶元素出栈
StackPop(&obj->pushst);
}
}
//读取popst栈的栈顶元素
int front = StackTop(&obj->popst);
//pushst栈的栈顶元素出栈
StackPop(&obj->popst);
return front;
}
//取队头元素
int myQueuePeek(MyQueue* obj)
{
//判空
assert(obj);
//当popst栈为空
if (StackEmpty(&obj->popst))
{
//如果popst栈为空,则把pushst栈的数据拷贝过来
while (!StackEmpty(&obj->pushst))
{
//将pushst栈的栈顶元素压入popst栈
StackPush(&obj->popst, StackTop(&obj->pushst));
//pushst栈的栈顶元素出栈
StackPop(&obj->pushst);
}
}
//读取popst栈的栈顶元素
return StackTop(&obj->popst);
}
//判空队列是否为空
bool myQueueEmpty(MyQueue* obj)
{
//判空
assert(obj);
//只有当两个栈均为空时,才表示队列为空
return StackEmpty(&obj->pushst) && StackEmpty(&obj->popst);
}
//销毁队列
void myQueueFree(MyQueue* obj)
{
//判空
assert(obj);
//销毁pushst栈和popst栈
StackDestory(&obj->pushst);
StackDestory(&obj->popst);
//释放队列
free(obj);
}
题四:设计循环队列
题目描述:
设计你的循环队列实现。循环队列是一种线性数据结构,其操作表现基于FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为"环形缓冲器"
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k):构造器,设置队列长度为k
Front:从队首获取元素。如果队列为空,返回-1
Rear:获取队尾元素。如果队列为空,返回-1
enQueue(value):向循环队列插入一个元素,如果成功插入则返回真
deQueue():从循环队列中删除一个元素。如果成功删除则返回真
isEmpty():检查循环队列是否为空
isFull():检查循环队列是否已满
分析:
1.采用数组或者链表都可以,但是数组缓存利用率更高,所以这里主要采用数组的方式。
2.用模运算将存储空间在逻辑上变成“环状”。当发现rear指针要指向MaxSize时,不应该让它指向MaxSize而是应该让它指向数组下标为0的位置。
3.队列判空:Q.rear==Q.front;队列判满:队尾指针的下一个位置是对头,即(Q.rear+1)%MaxSize==Q.front
实现:
//循环队列的定义
typedef struct
{
int* a;//动态开辟数组
int k;//当前有效元素个数
int head;//队头
int tail;//队尾
}MyCircularQueue;
//循环队列的初始化
MyCircularQueue* myCircularQueueCreate(int k)
{
//为队列开辟一块动态内存空间
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
//为数组开辟一个包含(k+1)个元素的内存空间
obj->a = (int*)malloc(sizeof(int) * (k + 1));
//队头,队尾起始都指向数组下标为0的位置
obj->head = obj->tail = 0;
//当前有效元素个数设置为k个
obj->k = k;
return obj;
}
//判断队列是否为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
//判空
assert(obj);
return obj->head == obj->tail;
}
//判断队列是否已满
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
//判空
assert(obj);
int next = obj->tail + 1;
//当tail指向数组最后一个下标的下一个位置时,则将tail指向数组下标为0的位置
if (next == obj->k + 1)
{
next = 0;
}
//队尾指针的下一个位置是对头,则表示队列已满
return next == obj->head;
}
//入队
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
//判空
assert(obj);
//检查队列是否已满
if (myCircularQueueIsFull(obj))
{
return false;
}
//未满则将value插入队尾
obj->a[obj->tail] = value;
obj->tail++;//队尾指针后移
//当tail指向数组最后一个下标的下一个位置时,则将tail指向数组下标为0的位置
if (obj->tail == obj->k + 1)
{
obj->tail = 0;
}
//obj->tail%=(k+1);
return true;
}
//出队
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
//判空
assert(obj);
//检查队列是否为空
if (MyCircularQueueIsEmpty(obj))
{
return false;
}
//若不空,则队头指针后移
++obj->head;
//当head指向数组最后一个下标的下一个位置时,则将head指向数组下标为0的位置
if (obj->head == obj->k + 1)
{
obj->head = 0;
}
return true;
}
//取队头元素
int myCircularQueueFront(MyCircularQueue* obj)
{
//判空
assert(obj);
//检查队列是否为空
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
//若不为空,则取队头元素
return obj->a[obj->head];
}
//取队尾元素
int myCircularQueueRear(MyCircularQueue* obj)
{
//判空
assert(obj);
//检查队列是否为空
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
//因为tail指向队尾元素的下一个位置,所以要取tail前一位置的下标
int pre = obj->tail - 1;
//若tail在数组起始位置,则前一位置的下标为数组的末尾位置
if (obj->tail == 0)
{
pre = obj->k;
}
//int pre = obj->tail - 1 + obj->k + 1;
//pre %= (obj->k+1);
//取队尾元素
return obj->a[pre];
}
//销毁
void myCircularQueueFree(MyCircularQueue* obj)
{
//判空
assert(obj);
//释放动态开辟的数组
free(obj->a);
//释放队列
free(obj);
}