目录
一、队列实现栈
LeetCode题目链接:用队列实现栈
题目描述:
声明:下面再用队列实现栈的过程中,使用的实现队列功能的函数,在之前的文章中有详细记录
文章链接:数据结构—队列的C语言实现
1、定义用队列实现栈的结构体
typedef struct {
Queue q1;
Queue q2;
} MyStack;
2、创建栈
动态开辟一个用队列实现栈的结构体空间,然后再调用队列的初始化函数,初始化两个队列
MyStack* myStackCreate() {
MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
assert(pst);
QueueInit(&pst->q1);
QueueInit(&pst->q2);
return pst;
}
3、压栈
因为是用两个队列实现,最开始两个队列都是空的可以随便将元素入到其中一个队列,之后就需要判断队列是否为空,将元素入到不为空的队列中。这里调用了实现队列中的入队列函数和判断队列是否为空函数
void myStackPush(MyStack* obj, int x) {
assert(obj);
if(QueueEmpty(&obj->q1))
{
QueuePush(&obj->q2,x);
}
else
{
QueuePush(&obj->q1,x);
}
}
4、 移除并返回栈顶元素
因为栈是满足后入先出的,而队列是先入先出,也就是从队列头出元素,所以出栈时就将非空队列中的元素依次从队列头出再入到另一个空队列中,最后剩一个非空队列队尾元素就相当于栈顶元素出栈,再移除栈顶元素
这里定义了两个队列指针,用来指向非空队列和空队列,避免了相同的压栈算法写两遍
int myStackPop(MyStack* obj) {
assert(obj);
Queue* emptyQ = &obj->q1;
Queue* notemptyQ = &obj->q2;
if(!QueueEmpty(emptyQ))
{
emptyQ = &obj->q2;
notemptyQ = &obj->q1;
}
while(QueueSize(notemptyQ) > 1)
{
int front = QueueFront(notemptyQ);
QueuePop(notemptyQ);
QueuePush(emptyQ,front);
}
int top = QueueFront(notemptyQ);
QueuePop(notemptyQ);
return top;
}
5、返回栈顶元素
队列可以直接获取队列的尾元素,所以要获取栈顶元素,就直接返回非空队列的队尾元素即可
int myStackTop(MyStack* obj) {
assert(obj);
if(QueueEmpty(&obj->q1))
{
return QueueBack(&obj->q2);
}
else
{
return QueueBack(&obj->q1);
}
}
6、判断栈是否为空
bool myStackEmpty(MyStack* obj) {
assert(obj);
return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}
7、销毁栈
调用队列销毁函数销毁两个队列,最后再释放掉动态开辟的用队列实现栈的结构体
void myStackFree(MyStack* obj) {
assert(obj);
QueueDestory(&obj->q2);
QueueDestory(&obj->q2);
free(obj);
}
二、用栈实现队列
LeetCode题目链接:用栈实现队列
题目描述:
声明:下面再用栈实现队列的过程中,使用的实现栈功能的函数,在之前的文章中有详细记录
文章链接:数据结构—栈的C语言实现
1、用栈实现队列的结构体
结构体内定义一个栈PushSt用来入队列,定义一个栈PopSt用来出队列
typedef struct {
ST PushSt;
ST PopSt;
} MyQueue;
2、创建队列
动态开辟一个用栈实现队列的结构体空间,然后再调用初始化栈的函数,初始化两个栈
MyQueue* myQueueCreate() {
MyQueue* pQ = (MyQueue*)malloc(sizeof(MyQueue));
assert(pQ);
StackInit(&pQ->PushSt);
StackInit(&pQ->PopSt);
return pQ;
}
3、入队列
调用压栈函数直接将元素压栈到PushSt栈中,实现入队列
void myQueuePush(MyQueue* obj, int x) {
assert(obj);
StackPush(&obj->PushSt,x);
}
4、 从队列的开头移除并返回元素
返回队列头元素,栈PopSt是用来出队列的,所以首先要先判断栈PopSt中是否有元素,也就是判断PopSt是否为空,如果栈PopSt为空就需要取PushSt中的栈顶元素,压栈到PopSt中,每一次判断了PopSt为空之后就需要将PushSt中的全部元素依次出栈再压栈到PopSt中;然后再取PopSt的栈顶元素就相当于队列头元素,取到了之后再删除栈顶元素;如果PopSt不为空,则说明栈PopSt中还有元素,就直接取PopSt的栈顶元素返回,再删除栈顶元素
int myQueuePop(MyQueue* obj) {
assert(obj);
if(StackEmpty(&obj->PopSt))
{
while(!StackEmpty(&obj->PushSt))
{
StackPush(&obj->PopSt,StackTop(&obj->PushSt));
StackPop(&obj->PushSt);
}
}
STDataType top = StackTop(&obj->PopSt);
StackPop(&obj->PopSt);
return top;
}
5、返回队列开头的元素
这个与上面实现“从队列的开头移除并返回元素”相似,只是不用移除队列头元素,也就是不用删除PopSt栈顶元素
int myQueuePeek(MyQueue* obj) {
assert(obj);
if(StackEmpty(&obj->PopSt))
{
while(!StackEmpty(&obj->PushSt))
{
StackPush(&obj->PopSt,StackTop(&obj->PushSt));
StackPop(&obj->PushSt);
}
}
return StackTop(&obj->PopSt);
}
6、判断队列是否为空
实现队列的两个栈都为空则队列为空
bool myQueueEmpty(MyQueue* obj) {
assert(obj);
return (StackEmpty(&obj->PushSt) && StackEmpty(&obj->PopSt));
}
7、销毁队列
void myQueueFree(MyQueue* obj) {
assert(obj);
StackDestory(&obj->PushSt);
StackDestory(&obj->PopSt);
free(obj);
}
三、队列与栈的相互比较
1、队列先进先出,栈先进后出。
2、栈是一种线性结构,任何线性表都可以用来实现栈,只是实现方式有所差异。栈后进先出,只能在栈顶进行插入和删除操作。
3、顺序表的结构相比链表简单,不影响效率的情况下会优先使用顺序表。所以栈一般用顺序表实现,队列一般用链表实现。栈只能在一端插入和删除数据。
4、对插入和删除操作的"限定":栈是限定只能在表的一端进行插入和删除操作的线性表; 队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。
5、从"数据结构"的角度看,它们都是线性结构,即数据元素之间的关系相同。但它们是完全不同的数据类型。除了它们各自的基本操作集不同外,主要区别是对插入和删除操作的"限定"。
6、栈和队列是在程序设计中被广泛使用的两种线性数据结构,它们的特点在于基本操作的特殊性,栈必须按"后进先出"的规则进行操作,而队列必须按"先进先出"的规则进行操作。和线性表相比,它们的插入和删除操作受更多的约束和限定,故又称为限定性的线性表结构。
7、遍历数据速度不同,栈只能从头部取数据:
也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性队列怎不同,他基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影像数据结构,速度要快的多
8、栈(Stack)是限定只能在表的一端进行插入和删除操作的线性表;队列(Queue)是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。
9、从"数据结构"的角度看,它们都是线性结构,即数据元素之间的关系相同。但它们是完全不同的数据类型。除了它们各自的基本操作集不同外,主要区别是对插入和删除操作的"限定"。