数据结构栈和队列OJ练习(栈和队列相互实现+循环队列实现)
Posted 风继续吹TT
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构栈和队列OJ练习(栈和队列相互实现+循环队列实现)相关的知识,希望对你有一定的参考价值。
目录
前言
前面在学习了栈和队列的实现之后,相信大家对栈和队列的结构和使用方式都有了一些理解。
下面我们就来进行一些练习,这这章的练习相对于原来在难度上有了一些提升。
原来的题只需要实现一个接口,而今天的练习题需要实现多个接口。
1.用队列实现栈
225. 用队列实现栈 - 力扣(LeetCode) (leetcode-cn.com)
栈:后进先出;队列:先进先出
方法:
创建两个队列,用来来回导数据。
入栈:将数据放入不为空的队列。
出栈:将不为空的队列中的数据以出队入栈的方式放入空队列,直到该队列中只剩一个数据。
这个数据就是要出栈的数据,直接将他pop掉即可。
栈顶:返回队尾。
检测栈是否为空:两个队列都为空,则栈为空。
口说无凭,请看图:
如图:
队列1中有4个数,以1,2,3,4顺序入队,所以1为队头,4为队尾。
根据队列先进先出规则,1应该先出队。而这里我们要实现栈,即后进先出,即应该先出队尾数据。
所以我们将除队尾数据全部先导入队列2中,这样队列1中就只剩下队尾数据。
注意:队列1中的数据导入队列2中后,数据的相对位置是不变的。
即:
最后出栈,就可得到栈顶数据。
代码如下:
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QueueNode;
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
}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);
bool QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);
//初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
}
//销毁
void QueueDestroy(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->head;
while (cur)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
}
//入队
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
//出队
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
QueueNode* next = pq->head->next;
free(pq->head);
pq->head = next;
if (pq->head == NULL)
{
pq->tail = NULL;
}
}
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);
return pq->head == NULL;
}
int QueueSize(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->head;
int size = 0;
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
//因为C语言中没有直接可以调用的栈和队列,所以需要将自己实现的队列拷贝到前面
typedef struct {//在栈中创建两个队列
Queue q1;
Queue q2;
} MyStack;
bool myStackEmpty(MyStack* obj);
MyStack* myStackCreate() {
MyStack*obj = (MyStack*)malloc(sizeof(MyStack));//动态开辟存放两个队列结构体
QueueInit(&obj->q1);//初始化两个队列
QueueInit(&obj->q2);
return obj;//需返回该结构体地址
}
void myStackPush(MyStack* obj, int x) {
if(!QueueEmpty(&obj->q1))//入栈时向不为空的队列插入数据,都为空这里默认向队列2中插入
{
QueuePush(&obj->q1,x);
}
else
{
QueuePush(&obj->q2,x);
}
}
int myStackPop(MyStack* obj) {
Queue*empty = &obj->q1;//出栈时需在不为空的队列里出,所以先找不为空的队列
Queue*noempty = &obj->q2;//默认队列1不为空
if(!QueueEmpty(&obj->q1))//如果队列1不为空,则交换两队列
{
empty = &obj->q2;
noempty = &obj->q1;
}
while(QueueSize(noempty)>1)//将不为空队列中除队尾数据全部放入空队列中
{
QueuePush(empty,QueueFront(noempty));
QueuePop(noempty);
}
int pop = QueueFront(noempty);//剩下的最后一个数据
QueuePop(noempty);//将数据Pop掉
return pop;
}
int myStackTop(MyStack* obj) {//返回不为空的队列的队尾
if(!QueueEmpty(&obj->q1))
{
return QueueBack(&obj->q1);
}
else
{
return QueueBack(&obj->q2);
}
}
bool myStackEmpty(MyStack* obj) {//两个队列都为空则栈为空
return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);//不要忘记销毁两个队列
QueueDestroy(&obj->q2);
free(obj);
}
2.用栈实现队列
232. 用栈实现队列 - 力扣(LeetCode) (leetcode-cn.com)
方法:
与上题类似,创建两个栈,将数据入栈与入队相同。栈1用来入数据,栈2用来出数据。
不过出队时,队头的数据位于栈底,所以还是要将除栈底的数据全部导入另一个栈中。
但不同的是,队列实现栈时,导过去后数据的顺序不变,所以需要来回导;而这里倒过去后数据顺序会颠倒,所以不需要再导回去,可以直接出队,直到该栈中没有数据。
口说无凭,请看图:
这样下一次再需要出队时,只需将栈2的栈顶Pop即可,直到栈2中没有数据,然后再从栈1中导数据。
代码如下:
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}Stack;
//初始化
void StackInit(Stack*ps);
//销毁
void StackDestroy(Stack* ps);
//压栈
void StackPush(Stack* ps, STDataType x);
//出栈
void StackPop(Stack* ps);
//栈顶
STDataType StackTop(Stack* ps);
//判断栈是否为空
bool StackEmpty(Stack* ps);
//栈中元素个数
int StackSize(Stack* ps);
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
void StackPush(Stack* ps, STDataType x)
{
assert(ps);
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)
{
perror("realloc");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
//同样需拷贝自己实现的栈
typedef struct {//创建两个栈,s1用来入数据,s2用来出数据
Stack s1;
Stack s2;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue*obj = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&obj->s1);
StackInit(&obj->s2);
return obj;
}
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->s1,x);//直接将数据Push到s1中
}
int myQueuePop(MyQueue* obj) {
if(StackEmpty(&obj->s2))//pop数据之前先检查s2中是否有数据,没有数据需从s1中导入
{
while(!StackEmpty(&obj->s1))
{
StackPush(&obj->s2,StackTop(&obj->s1));
StackPop(&obj->s1);
}
}
int pop = StackTop(&obj->s2);//直接pop栈顶数据即可
StackPop(&obj->s2);
return pop;
}
int myQueuePeek(MyQueue* obj) {//返回队尾数据(即s2栈顶数据)
if(StackEmpty(&obj->s2))//返回数据之前先检查s2中是否有数据,没有数据需从s1中导入
{
while(!StackEmpty(&obj->s1))
{
StackPush(&obj->s2,StackTop(&obj->s1));
StackPop(&obj->s1);
}
}
return StackTop(&obj->s2);
}
bool myQueueEmpty(MyQueue* obj) {//两个栈都为空,队列才为空
return StackEmpty(&obj->s1)&&StackEmpty(&obj->s2);
}
void myQueueFree(MyQueue* obj) {
StackDestroy(&obj->s1);
StackDestroy(&obj->s2);
free(obj);
}
3.循环队列
622. 设计循环队列 - 力扣(LeetCode) (leetcode-cn.com)
这道题的意思是:
设计一个队列,这个队列的大小是固定的,且队列头尾相连, 然后该队列能够实现题目中的操作。
那么是使用数组实现,还是用链表实现呢?我们接着往下看。
例如:用k表示数组大小
图中我们用数组实现。用head和tail表示队头队尾下标,每插入一个数据,在数组tail位置插入,然后再将tail++。
但是有个问题如果将head==tail认为是队列为空的条件,那么怎么判断队列满了呢?
面对这个问题我们这样解决:将数组的空间大小增加1,即数组比队列大1个空间。
这样:当 head = tail 时队列为空,(tail+1)%(k+1) = head 时队列满了。
且以链表实现时也会多申请一个空间。
代码如下:数组实现。
typedef struct {
int*arr;
int head;//队头
int tail;//队尾
int capacity;//队列容量
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue*obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->arr = (int*)malloc(sizeof(int)*(k+1));//申请数组空间
obj->head = 0;
obj->tail = 0;
obj->capacity = k;//队列大小
return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
//插入数据
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj))//检查容量
{
return false;
}
else
{
obj->arr[obj->tail] = value;//在队尾位置插入
obj->tail++;//队尾向后移动
obj->tail %= obj->capacity+1;//当队尾移动到数组最后一个元素后面的下标时,队尾回到数组下标为0的位置
return true;
}
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))//检测队列是否为空
{
return false;
}
else
{
obj->head++;
obj->head %= obj->capacity+1; 当队头移动到数组最后一个元素后面的下标时,队尾回到数组下标为0的位置
return true;
}
}
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return -1;//队列为空返回-1
}
else
{
return obj->arr[obj->head];//返回队头数据
}
}
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return -1;//队列为空返回-1
}
else
{
return obj->arr[(obj->tail+obj->capacity)%(obj->capacity+1)];//返回下标为tail位置前面一个位置数据
}
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->head == obj->tail;//head==tail队列为空
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->tail+1)%(obj->capacity+1) == obj->head;//为防止数组越界,不能直接将tail+1与head比较
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->arr);
free(obj);
}
链表实现与数组实现类似,只是判断队列满的条件的不一样。
链表判满的条件时tail->next = head;
代码如下:
typedef struct ListNode1 ListNode1;
struct ListNode1{
int data;
ListNode1* next;
};
typedef struct {
ListNode1* front;//创建头尾指针指向队列头和尾
ListNode1* tail;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->front = NULL;
ListNode1* cur = NULL;
while (k > -1)//创建k+1个结点
{
if (obj->front == NULL)
{
obj->front = (ListNode1*)malloc(sizeof(ListNode1));
obj->front->next = NULL;
cur = obj->front;
}
else
{
ListNode1* next = (ListNode1*)malloc(sizeof(ListNode1));
cur->next = next;
cur = next;
}
k--;
}
cur->next = obj->front;//将链表成环
obj->tail = obj->front;//头尾指针复位
return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if (myCircularQueueIsFull(obj))
{
return false;
}
else
{
obj->tail->data = value;//插入数据将数据放入tail结点
obj->tail = obj->tail->next;//tail结点向后移动
return true;
}
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return false;
}
else
{
obj->front = obj->front->next;//删除数据时直接将头指针向后移动,不用将该位置的数据删除
return true;
}
}
int myCircularQueueFront(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
else
{
return obj->front->data;//返回头指针结点处的数据
}
}
int myCircularQueueRear(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
else
{
ListNode1* cur = obj->front;//因为不知到队尾位置,只知道队尾的下一个位置,所以需要遍历链表寻找队尾。
while (cur->next != obj->tail)
{
cur = cur->next;
}
return cur->data;
}
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->tail;//head==tail队列为空
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return obj->tail->next == obj->front;//tail->next == head 队列满了
}
void myCircularQueueFree(MyCircularQueue* obj) {
ListNode1* cur = obj->front->next;
while (cur != obj->front)//释放链表结点
{
ListNode1* next = cur->next;
free(cur);
cur = next;
}
free(cur);
free(obj);
}
如有错误,或更好的方法,可以在评论区一起交流哟~
你的点赞支持就是我创作的动力!
以上是关于数据结构栈和队列OJ练习(栈和队列相互实现+循环队列实现)的主要内容,如果未能解决你的问题,请参考以下文章