目录
(1)方式1:队列的入队、出队、取队头数据、取队尾数据的过程
(2)方式2:队列的入队、出队、取队头数据、取队尾数据的过程
(1)对if(stackEmpty(&obj->popst))代码的解析
(2)对while(!stackEmpty(&obj->pushst))代码的解析
(3)对stackPush(&obj->popst,stackTop(&obj->pushst))代码进行解析
(4)对stackPop(&obj->pushst)代码进行解析
(5)对return stackTop(&obj->popst)代码进行解析
1.对assert(!myQueueEmpty(obj))代码进行解析
2.对int peek= myQueuePeek(obj)代码进行解析
3. 对stackPop(&obj->popst)代码进行解析
1.对return stackEmpty(&obj->pushst)&& stackEmpty(&obj->popst)代码进行解析
(1)对stackDestroy(&obj->pushst)代码进行解析
(2)对stackDestroy(&obj->popst)代码进行解析
用2个栈实现队列
一、解题分析
1.用两个栈实现队列一共有两种方式
(1)方式1:队列的入队、出队、取队头数据、取队尾数据的过程
注意:方式1的缺陷是出队操作要导数据两次才能完成删除一个队列队头数据。
图形解析:
①队列的入队思路
一开始还没有对队列进行入队操作时,队列中的两个栈都是空栈,则此时假设把n个元素进行入队操作时只需选两个栈中的其中一个空栈进行压栈即可,在压完栈后两个栈其中一个一定空栈,另一个一定是个非空栈。当下次队列进行入队操作时一定是把要入队的数据压入非空栈中。
②取队列的队头数据思路
当要取队头数据时只需把非空栈中的倒数n-1个数据导入到空栈中后会使得原来的空栈变成新的非空栈,而此时原来非空栈的栈顶元素就是我们要取的队头数据。
③队列的出队思路
当我们取完队头数据后由于此时队列中的两个栈都是非空栈,若队列要进行出队操作时,一定是把原来非空栈的栈顶元素进行出栈就完成删除队列队头数据则此时原来的非空栈就变成了空栈,但是完成删除队头数据后必须把新的非空栈中的所有数据导入到此时的空栈中,只有重新导回数据才算完成出队操作。在完成出队操作后两个栈一定一个是空栈而另一个是非空栈。
④取队列的队尾数据的思路
在取队列队尾数据时此时队列中的两个栈一定一个是空栈而另一个是非空栈,而两个栈中的非空栈的栈顶元素就是我们要取的队列队尾数据。
(2)方式2:队列的入队、出队、取队头数据、取队尾数据的过程
注意:方式2的优势是出队操作时要只需导数据1次就可以完成删除一个队列队头数据。
图形解析:
①队列的入队思路
由于栈pushst只用来做入队操作即当向队列队尾插入数据时把所有要入队的元素都压入栈pushst中以此来完成队列入队的操作。
②队列的出队思路
由于栈popst只用来做出队操作即在删除队头数据之前要把栈pushst中所有要出队的元素都压入栈popst中,当我们要进行出队操作时只需对栈popst的栈顶元素进行出栈就可以完成删除一个队列队头数据以此来完成出队操作。注意:只有当栈popst变成空栈时必须把栈pushst中的所有元素都导入到栈popst中才能让栈popst进行出队操作。若栈popst不是空栈则绝不能把栈pushst中的所有元素都导入到栈popst中。
③取队列的队头数据思路
非空栈popst的栈顶元素就是我们要取的队头数据。
④取队列的队尾数据的思路
非空栈pushst的栈顶元素就是我们要取的队尾数据。
注意:栈popst只用来做出队操作(即栈popst只用来出栈的),当栈popst为空栈时才会把栈pushst中的所有数据都导到栈popst中直到栈pushst中没有数据才会终止导入数据。只要栈popst中还有数据就可以继续对栈popst进行出队操作(即继续对栈popst进行出栈)。
(3)总结
由于方式2的出队操作比方式1的出队操作少导1遍数据,所以下面我们着重说明方式2如何实现。
二、方式2的代码分析
入队函数(插入数据) myQueuePush
1.用myQueuePush函数对队列实现入队操作的思路
由于栈pushst只用来做入队操作即当向队列队尾插入数据时把所有要入队的元素都压入栈pushst中以此来完成队列入队的操作。
注意:由于pushst栈是永远用来存放队列要入队的元素,所以只需用stackPush(&obj->pushst,x)函数对pushst栈进行压栈操作来完成对队列进行的入队操作。
代码实现:
//入队函数(插入数据)
void myQueuePush(MyQueue* obj, STDatatype x)
{
//判断指针obj是否是空指针
assert(obj);
//不管栈pushst是不是空栈都可以在栈pushst插入数据x以此来完成对队列q的入队操作。
StackPush(&obj->pushst, x);
}
取队头数据函数myQueuePeek
1.myQueuePeek函数取队列队头数据的思路
非空栈popst的栈顶元素就是我们要取的队头数据。
注意:由于pushst栈是永远存放入队元素的,而popst栈是永远用来存放出队元素的,当popst栈为空栈时此时popst栈中没有元素可以进行出栈操作以此来完成出队操作,所有必须把pushst栈中的所有数据都导入到popst栈后,我们才能用StackTop(&obj->popst)取非空栈popst的栈顶元素来代替队列队头数据以此来完成取队列队头数据的操作。
代码实现:
//取队头数据函数
STDatatype myQueuePeek(MyQueue* obj)
{
//判断指针obj是否是空指针
assert(obj);
//判断队列q是否是空队列,若队列是空队列则不需要取队头数据了。
assert(!myQueueEmpty(obj));
//当栈popst是空栈时则必须要把pushst栈中的所有元素压入popst栈中后才能执行队列q的出队操作。
if (StackEmpty(&obj->popst))
{
////写法1:
//while (1)
//{
// //把pushst栈中的元素压入popst栈中
// StackPush(&obj->popst, StackTop(&obj->pushst));
// StackPop(&obj->pushst);
// if (StackEmpty(&obj->pushst));
// break;
//}
//写法2:
while (!StackEmpty(&obj->pushst))//当栈popst是空栈且栈pushst不是空栈时要把栈pushst中的所有元素压入栈popst中直到栈pushst变成空栈为止。
{
//把pushst栈中的元素压入popst栈中
StackPush(&obj->popst, StackTop(&obj->pushst));
StackPop(&obj->pushst);
}
}
//栈popst的栈顶元素就是队列q的队头数据
return StackTop(&obj->popst);
}
2.代码分析
(1)对if(stackEmpty(&obj->popst))代码的解析
if(stackEmpty(&obj->popst))语句的作用是判断popst栈是否为空栈,若popst栈为空栈的话,则必须把pushst栈中的所有数据都导入popst栈中
(2)对while(!stackEmpty(&obj->pushst))代码的解析
while(!stackEmpty(&obj->pushst))循环中的循环条件判断表达式的作用是判断pushst栈中是否还有元素可以导入popst栈中。
(3)对stackPush(&obj->popst,stackTop(&obj->pushst))代码进行解析
利用stackTop(&obj->pushst)取pushst栈的1个栈顶元素后并用stackPush函数把这个pushst栈的栈顶元素导入到popst栈中。
(4)对stackPop(&obj->pushst)代码进行解析
利用stackPop(&obj->pushst)删除pushst栈当前的栈顶元素,这样做的目的是为了下一次利用StackTop(&obj->pushst)函数取pushst栈的下一个栈顶元素做准备。
(5)对return stackTop(&obj->popst)代码进行解析
利用stackTop(&obj->popst)取popst的栈顶元素来代替队列的队头数据,并用return stackTop(&obj->popst)返回队头数据。
入队函数(插入数据)myQueuePush
1.myQueuePop函数对队列实现出队操作的思路
由于栈popst只用来做出队操作即在删除队头数据之前要把所有要出队的元素都压入栈popst中,当我们要进行出队操作时只需对栈popst的栈顶元素进行出栈就可以完成删除一个队列队头数据以此来完成出队操作。注意:只有当栈popst变成空栈时必须把栈pushst中的所有元素都导入到栈popst中才能让栈popst进行出队操作。若栈popst不是空栈则绝不能把栈pushst中的所有元素都导入到栈popst中。
注意:一定要先实现myQueuePeek函数,再借用myQueuePeek函数函数实现myQueuePop函数。只有这样才能更容易实现myQueuePop函数。
代码实现:
//出队函数(删除数据)
STDatatype myQueuePop(MyQueue* obj)//myQueuePop函数的功能是删除队头数据并返回要删除队头数据的值
{
//判断指针obj是否是空指针
assert(obj);
//判断队列q是否是空队列以此来说明队列q中是否还有可以删除的元素
assert(!myQueueEmpty(obj));
//利用myQueuePeek函数取到队列q队头的数据给peek以便于myQueuePop函数返回要删除队头数据的值。
int peek = myQueuePeek(obj);
StackPop(&obj->popst);
return peek;
}
2.代码分析
1.对assert(!myQueueEmpty(obj))代码进行解析
当队列是空队列时导致此时pushst栈和popst栈都为空栈,所以利用assert(!myQueueEmpty(obj))来判断一下队列是否为空队列。
(注意:当pushst栈和popst栈都为空栈时是无法利用myQueuePop删除队头数据,因为pushst栈和popst栈中已经没有数据可以删除了)
2.对int peek= myQueuePeek(obj)代码进行解析
利用myQueuePeek(obj)函数取队列的队头数据并把这个队头数据用peek表示
3. 对stackPop(&obj->popst)代码进行解析
利用stackPop(&obj->popst)函数对popst栈进行出栈操作来删除栈popst的栈顶元素以此来完成myQueuePop函数对队列的出队操作。
取队尾数据函数myQueueTail
1.myQueueTail函数取队列队尾数据的思路
非空栈pushst的栈顶元素就是我们要取的队尾数据。
代码实现:
//取队尾数据函数
STDatatype myQueueTail(MyQueue* obj)
{
//判断指针obj是否是空指针
assert(obj);
//判断队列q是否是空队列,若队列是空队列则不需要取队尾数据了。
assert(!myQueueEmpty(obj));
//注意:若栈pushst为非空栈,则栈pushst的栈顶元素就是队列q的队尾元素;
//若栈pushst为空栈,则栈popst的栈底元素就是队列q的队尾元素
//情况1:若栈pushst为非空栈,则栈pushst的栈顶元素就是队列q的队尾元素。
if (!StackEmpty(&obj->pushst))
return StackTop(&obj->pushst);
else//情况2:若栈pushst为空栈,则栈popst的栈底元素就是队列q的队尾元素
return obj->popst.a[0];
}
判断队列是否是空队列函数myQueueEmpty
1.利用myQueueEmpty判读队列是否为空队列的思路
当队列中的两个栈pushst栈和popst栈都为空栈时,此时队列才是空队列。
代码实现:
//判断队列是否是空队列
bool myQueueEmpty(MyQueue* obj)
{
//判断指针obj是否是空指针
assert(obj);
//只有两个栈pushst、popst同时为空栈则队列q才是空队列。
return StackEmpty(&obj->pushst) && StackEmpty(&obj->popst);
}
2.代码分析
1.对return stackEmpty(&obj->pushst)&& stackEmpty(&obj->popst)代码进行解析
利用stackEmpty(&obj->pushst)函数判断pushst栈是否为空栈,再利用
stackEmpty(&obj->popst)函数判断popst栈是否为空栈。利用逻辑与表达式
stackEmpty(&obj->pushst)&& stackEmpty(&obj->popst)来判断队列是否为空队列。
销毁队列函数myQueueFree
1.利用myQueueFree函数删除栈的思路
由于栈是由两个队列组成,所以想要删除栈的话必须要释放掉栈的动态空间、pushst栈动态数组的空间、popst栈动态数组的空间。
代码实现:
//销毁队列函数
void myQueueFree(MyQueue* obj)
{
//判断指针obj是否是空指针
assert(obj);
//释放两个栈pushst、popst的动态空间
StackDestroy(&obj->pushst);
StackDestroy(&obj->popst);
//释放队列q的结构体动态空间
free(obj);
}
2.代码分析
(1)对stackDestroy(&obj->pushst)代码进行解析
利用stackDestroy(&obj->pushst)释放掉pushst栈动态数组的空间。
(2)对stackDestroy(&obj->popst)代码进行解析
利用stackDestroy(&obj->popst)释放掉popst栈动态数组的空间。
(3)对free(obj)代码进行解析
利用free(obj)函数释放掉指针obj指向的队列动态空间。
三、方式2的整个工程代码
1.stack.h
//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 capacity;//容量
int top;//统计栈中存放数据的个数。top表示栈顶元素的下一个元素的下标
}ST;
//初始化函数
void StackInit(ST* ps);
//销毁函数
void StackDestroy(ST* ps);
//压栈函数->尾插
void StackPush(ST* ps, STDatatype x);
//出栈函数->尾删
void StackPop(ST* ps);
//取栈顶元素
STDatatype StackTop(ST* ps);
//判断栈是否是空栈
bool StackEmpty(ST* ps);
//统计栈中存放数据的个数
int StackSize(ST* ps);
2.stack.c
//stack.c
#include "stack.h"
//初始化函数->把栈st初始化为空栈
void StackInit(ST* ps)
{
//判断指针ps是否是空指针
assert(ps);
//给栈st的成员变量指针a分配空间
ps->a = (STDatatype*)malloc(4 * sizeof(STDatatype));
if (ps->a == NULL)
{
perror("malloc fail");
exit(-1);
}
//对栈st的结构体成员进行初始化
ps->capacity = 4;
ps->top = 0;//top表示栈顶元素的下一个元素的下标。同时top也是元素压栈位置的下标。
}
//销毁函数
void StackDestroy(ST* ps)
{
//判断指针ps是否是空指针
assert(ps);
//释放动态空间
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
//压栈函数->尾插
void StackPush(ST* ps, STDatatype x)
{
//判断指针ps是否为空指针
assert(ps);
//判断插入位置的合法性
assert(ps->top >= 0);
//判断栈st的容量是否充足,若不足则要进行扩容。
if (ps->top == ps->capacity)
{
//扩容
STDatatype* tmp = (STDatatype*)realloc(ps->a, 2 * ps->capacity * sizeof(STDatatype));
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity *= 2;
}
ps->a[ps->top++] = x;
}
//出栈函数->尾删
void StackPop(ST* ps)
{
//判断指针ps是否为空指针
assert(ps);
//判断栈st是否是空栈
assert(!StackEmpty(ps));//或者写成assert(ps->top > 0);//判断栈st中是否还有可以删除的元素
ps->top--;
}
//取栈顶元素
STDatatype StackTop(ST* ps)
{
//判断指针ps是否为空指针
assert(ps);
//判断栈st是否是空栈
assert(!StackEmpty(ps));//或者写成assert(ps->top > 0);//判断栈st中是否还有可以删除的元素
return ps->a[ps->top - 1];
}
//判断栈是否是空栈
bool StackEmpty(ST* ps)
{
//判断指针ps是否为空指针
assert(ps);
////判断栈st是否是空栈的写法1:
//if (ps->top == 0)
// return true;
//else
// return false;
//判断栈st是否是空栈的写法2:
return ps->top == 0;
}
//统计栈中存放数据的个数
int StackSize(ST* ps)
{
//判断指针ps是否为空指针
assert(ps);
return ps->top;
}
3.MyQueue.h
//Myqueue.h
#pragma once
typedef struct
{
ST pushst;//栈pushst只用来做入队操作。做入队操作(即向队尾插入数据)时把所有要入队的元素都压入栈pushst中。(插入数据)
ST popst;//栈popst只用来做出队操作。做出队操作(即删除队头数据)时把所有要出队的元素在栈popst中出栈(删除数据)
} MyQueue;
//创建队列
MyQueue* myQueueCreate();//由于myQueueCreate函数有初始化功能,所以我们不用另外写一个初始化函数myQueueInit。
//入队函数->插入数据
void myQueuePush(MyQueue* obj, STDatatype x);
//出队函数->删除数据并取队头数据
STDatatype myQueuePop(MyQueue* obj);
//取队头数据函数
STDatatype myQueuePeek(MyQueue* obj);
//取队尾数据函数
STDatatype myQueueTail(MyQueue* obj);
//判断队列是否为空队列
bool myQueueEmpty(MyQueue* obj);
//销毁队列
void myQueueFree(MyQueue* obj);
//统计队列存储数据的个数
int myQueueSize(MyQueue* obj);
//队列打印函数
void myQueuePrint(MyQueue* obj);
4.MyQueue.c
//MyQueue.c
#include "stack.h"
#include "MyQueue.h"
MyQueue* myQueueCreate()
{
//创建队列q的结构体
MyQueue* pq = (MyQueue*)malloc(sizeof(MyQueue));
if (NULL == pq)
{
perror("malloc fail");
exit(-1);
}
//对队列q的结构体成员栈popst、栈pushst进行初始化
StackInit(&pq->popst);
StackInit(&pq->pushst);
return pq;//返回队列q的地址给主调函数
}
//入队函数(插入数据)
void myQueuePush(MyQueue* obj, STDatatype x)
{
//判断指针obj是否是空指针
assert(obj);
//不管栈pushst是不是空栈都可以在栈pushst插入数据x以此来完成对队列q的入队操作。
StackPush(&obj->pushst, x);
}
//出队函数(删除数据)
STDatatype myQueuePop(MyQueue* obj)//myQueuePop函数的功能是删除队头数据并返回要删除队头数据的值
{
//判断指针obj是否是空指针
assert(obj);
//判断队列q是否是空队列以此来说明队列q中是否还有可以删除的元素
assert(!myQueueEmpty(obj));
//利用myQueuePeek函数取到队列q队头的数据给peek以便于myQueuePop函数返回要删除队头数据的值。
int peek = myQueuePeek(obj);
StackPop(&obj->popst);
return peek;
}
//取队头数据函数
STDatatype myQueuePeek(MyQueue* obj)
{
//判断指针obj是否是空指针
assert(obj);
//判断队列q是否是空队列,若队列是空队列则不需要取队头数据了。
assert(!myQueueEmpty(obj));
//当栈popst是空栈时则必须要把pushst栈中的所有元素压入popst栈中后才能执行队列q的出队操作。
if (StackEmpty(&obj->popst))
{
////写法1:
//while (1)
//{
// //把pushst栈中的元素压入popst栈中
// StackPush(&obj->popst, StackTop(&obj->pushst));
// StackPop(&obj->pushst);
// if (StackEmpty(&obj->pushst));
// break;
//}
//写法2:
while (!StackEmpty(&obj->pushst))//当栈popst是空栈且栈pushst不是空栈时要把栈pushst中的所有元素压入栈popst中直到栈pushst变成空栈为止。
{
//把pushst栈中的元素压入popst栈中
StackPush(&obj->popst, StackTop(&obj->pushst));
StackPop(&obj->pushst);
}
}
//栈popst的栈顶元素就是队列q的队头数据
return StackTop(&obj->popst);
}
//取队尾数据函数
STDatatype myQueueTail(MyQueue* obj)
{
//判断指针obj是否是空指针
assert(obj);
//判断队列q是否是空队列,若队列是空队列则不需要取队尾数据了。
assert(!myQueueEmpty(obj));
//注意:若栈pushst为非空栈,则栈pushst的栈顶元素就是队列q的队尾元素;
//若栈pushst为空栈,则栈popst的栈底元素就是队列q的队尾元素
//情况1:若栈pushst为非空栈,则栈pushst的栈顶元素就是队列q的队尾元素。
if (!StackEmpty(&obj->pushst))
return StackTop(&obj->pushst);
else//情况2:若栈pushst为空栈,则栈popst的栈底元素就是队列q的队尾元素
return obj->popst.a[0];
}
//判断队列是否是空队列
bool myQueueEmpty(MyQueue* obj)
{
//判断指针obj是否是空指针
assert(obj);
//只有两个栈pushst、popst同时为空栈则队列q才是空队列。
return StackEmpty(&obj->pushst) && StackEmpty(&obj->popst);
}
//销毁队列函数
void myQueueFree(MyQueue* obj)
{
//判断指针obj是否是空指针
assert(obj);
//释放两个栈pushst、popst的动态空间
StackDestroy(&obj->pushst);
StackDestroy(&obj->popst);
//释放队列q的结构体动态空间
free(obj);
}
//统计队列存储数据的个数
int myQueueSize(MyQueue* obj)
{
//判断指针obj是否是空指针
assert(obj);
//注意:栈pushst 和 栈popst中存放数据个数的总和才是队列q中存放数据的个数。
int size = 0;
if (!StackEmpty(&obj->pushst))
size += StackSize(&obj->pushst);
if (!StackEmpty(&obj->popst))
size += StackSize(&obj->popst);
return size;
}
//打印函数
void StackPrint1(ST* ps)
{
int i = 0;
for (i = 0; i < ps->top; i++)
printf("%d ", ps->a[i]);
//printf("\n");//注意:由于队列中的所有数据是两个栈的总和,所以我们在打印队列的所有元素时最好不要换行打印栈pushst 和 栈popst的元素。
}
void StackPrint2(ST* ps)
{
int i = 0;
for (i = ps->top - 1; i >= 0; i--)
printf("%d ", ps->a[i]);
//printf("\n");//注意:由于队列中的所有数据是两个栈的总和,所以我们在打印队列的所有元素时最好不要换行打印栈pushst 和 栈popst的元素。
}
//队列打印函数
void myQueuePrint(MyQueue* obj)
{
//判断指针obj是否是空指针
assert(obj);
//注意:队列为空也是可以打印的
//注意:由于队列打印函数要展示队列先进先出的特性,所以我们在打印队列的元素时必须先打印完栈popst中的元素后,再打印栈pushst中的元素。
if (!StackEmpty(&obj->popst))
StackPrint2(&obj->popst);
if (!StackEmpty(&obj->pushst))
StackPrint1(&obj->pushst);
printf("\n");
}
5.test.c
#include "stack.h"
#include "MyQueue.h"
void TestQueue1()
{
//创建队列q的结构体
MyQueue*pq = myQueueCreate();
//入队操作
myQueuePush(pq, 1);
myQueuePush(pq, 2);
myQueuePush(pq, 3);
myQueuePush(pq, 4);
myQueuePush(pq, 5);
//打印
myQueuePrint(pq);
//统计队列q中存放数据的个数
printf("size:%d\n", myQueueSize(pq));
printf("队头数据:%d\n", myQueuePeek(pq));
printf("队尾数据:%d\n", myQueueTail(pq));
printf("\n\n");
//出队操作
//1.
printf("一.\n");
printf("要删除的队头数据:%d\n", myQueuePop(pq));
myQueuePrint(pq);
printf("size:%d\n", myQueueSize(pq));
printf("队头数据:%d\n", myQueuePeek(pq));
printf("队尾数据:%d\n", myQueueTail(pq));
printf("\n\n");
//2.
printf("二.\n");
printf("要删除的队头数据:%d\n", myQueuePop(pq));
myQueuePrint(pq);
printf("size:%d\n", myQueueSize(pq));
printf("队头数据:%d\n", myQueuePeek(pq));
printf("队尾数据:%d\n", myQueueTail(pq));
printf("\n\n");
//3.
printf("三.\n");
printf("要删除的队头数据:%d\n", myQueuePop(pq));
myQueuePrint(pq);
printf("size:%d\n", myQueueSize(pq));
printf("队头数据:%d\n", myQueuePeek(pq));
printf("队尾数据:%d\n", myQueueTail(pq));
printf("\n\n");
//4.
printf("四.\n");
printf("要删除的队头数据:%d\n", myQueuePop(pq));
myQueuePrint(pq);
printf("size:%d\n", myQueueSize(pq));
printf("队头数据:%d\n", myQueuePeek(pq));
printf("队尾数据:%d\n", myQueueTail(pq));
printf("\n\n");
//5.
printf("五.\n");
printf("要删除的队头数据:%d\n", myQueuePop(pq));
myQueuePrint(pq);
printf("size:%d\n", myQueueSize(pq));
printf("队头数据:%d\n", myQueuePeek(pq));
//printf("队尾数据:%d\n", myQueueTail(pq));
printf("\n\n");
//6.
//myQueuePop(pq);
//销毁队列q
myQueueFree(pq);
}
int main()
{
//测试函数
TestQueue1();
return 0;
}