栈和队列的相互实现及环形队列

    本篇适用于学习了C语言基础栈和队列及其相关实现,想进一步提升运用的同学


目录

1.由两个队列实现栈

2.由两个栈实现队列

3.循环队列


 

1.由两个队列实现栈

225. 用队列实现栈 - 力扣(LeetCode)

“开幕雷击”

                             

这是个什么东西?

这便是大家在学习C语言结构体时很容易时忽视的一个知识点:匿名结构体

//匿名结构体类型
struct
{
 int a;
 char b;
 float c;
}x;

这样的结构体仅仅能使用一次,编译器不会把和他相同结构的结构体认为是同一自定义类型。 

具体思路:

              

比方我们先对栈1压入1 2 3 4,现在要top或者pop,我们需要取出4,由于队列的特性,我们只能先将1 2 3拿出来并压入另一个队列,再对剩下的4做top或者pop处理。

因此:

导数据即可。

实现push,紧接上文思路,再压入依然只能压入有数据的“另一个队列”,这样才能与上文的逻辑相吻合。

void myStackPush(MyStack* obj, int x) {
    //谁不为空就传谁
    if(!QueueEmpty(&obj->q1)){
        QueuePush(&obj->q1,x);
    }else{
        QueuePush(&obj->q2,x);
    }
}

实现pop,为了避免把重复的代码写两次,我们使用假设法。 

             

最终题解:

typedef struct {
    Queue q1;
    Queue q2;
} MyStack;


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)){
        QueuePush(&obj->q1,x);
    }else{
        QueuePush(&obj->q2,x);
    }
}

int myStackPop(MyStack* obj) {
    //先导数据,再删除
    //使用假设法
    Queue* pEmptyQueue=&obj->q1;
    Queue* pNonEmptyQueue=&obj->q2;
    if(!QueueEmpty(&obj->q1)){
        pEmptyQueue=&obj->q2;
        pNonEmptyQueue=&obj->q1;
    }
    //开导
    while(pNonEmptyQueue->size>1){
        QueuePush(pEmptyQueue,QueueFront(pNonEmptyQueue));
        QueuePop(pNonEmptyQueue);
    }
    int val=QueueFront(pNonEmptyQueue);
    QueuePop(pNonEmptyQueue);
    return val;
}

int myStackTop(MyStack* obj) {
    Queue* pEmptyQueue=&obj->q1;
    Queue* pNonEmptyQueue=&obj->q2;
    if(!QueueEmpty(&obj->q1)){
        pEmptyQueue=&obj->q2;
        pNonEmptyQueue=&obj->q1;
    }
    //开导
    while(pNonEmptyQueue->size>1){
        QueuePush(pEmptyQueue,QueueFront(pNonEmptyQueue));
        QueuePop(pNonEmptyQueue);
    }
    int val=QueueFront(pNonEmptyQueue);
    QueuePush(pEmptyQueue,QueueFront(pNonEmptyQueue));
    QueuePop(pNonEmptyQueue);
    return val;
}

bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}

void myStackFree(MyStack* obj) {
    free(obj);
}

      

2.由两个栈实现队列

可以模仿上一题依靠导数据求解,但是没必要刻舟求剑。


先压入1 2 3 4

第一次pop时,只能先将栈1的内容导入栈2

当第一次栈1数据被导入栈2之后,数据的顺序发生了变化,栈2后入先出的数据已经变成了栈1先入后出的数据 (有一种将栈1和栈2连接到一起的感觉)

因此,栈1取名叫pushst 栈2取名叫popst!!!

          所以,我们只需要在pop栈为空时进行导数据,其余时候可直接在push进,pop出。

                                                       


易错!

定义了指针,但是没有开辟指针指向的结构 

结构体里只放了两个指针,没有起到任何实质性的作用!!

typedef struct {
    Queue q1;
    Queue q2;
} MyStack;


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)){
        QueuePush(&obj->q1,x);
    }else{
        QueuePush(&obj->q2,x);
    }
}

int myStackPop(MyStack* obj) {
    //先导数据,再删除
    //使用假设法
    Queue* pEmptyQueue=&obj->q1;
    Queue* pNonEmptyQueue=&obj->q2;
    if(!QueueEmpty(&obj->q1)){
        pEmptyQueue=&obj->q2;
        pNonEmptyQueue=&obj->q1;
    }
    //开导
    while(pNonEmptyQueue->size>1){
        QueuePush(pEmptyQueue,QueueFront(pNonEmptyQueue));
        QueuePop(pNonEmptyQueue);
    }
    int val=QueueFront(pNonEmptyQueue);
    QueuePop(pNonEmptyQueue);
    return val;
}

int myStackTop(MyStack* obj) {
    Queue* pEmptyQueue=&obj->q1;
    Queue* pNonEmptyQueue=&obj->q2;
    if(!QueueEmpty(&obj->q1)){
        pEmptyQueue=&obj->q2;
        pNonEmptyQueue=&obj->q1;
    }
    //开导
    while(pNonEmptyQueue->size>1){
        QueuePush(pEmptyQueue,QueueFront(pNonEmptyQueue));
        QueuePop(pNonEmptyQueue);
    }
    int val=QueueFront(pNonEmptyQueue);
    QueuePush(pEmptyQueue,QueueFront(pNonEmptyQueue));
    QueuePop(pNonEmptyQueue);
    return val;
}

bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}

void myStackFree(MyStack* obj) {
    free(obj);
}

3.循环队列

 

一提及队列,我们的第一反应是用链表实现。

先根据需要,进行选型、分析:

       依照队列的习惯,我们使用front、rear两个指针分别指向头和尾

初始化时front和rear都指向第一个

进行一次push

rear还指向第一个元素。有一个元素和没有元素时两个指针的指向一样并且相等,这是我们不希望看到的。可以通过增加size变量来控制,但是这样变量更多,更难操作

那是否可以借助栈的思想,无元素时让rear指向空,有元素指向第一个(模仿栈的初始top=-1)?

加满之后(rear->next==front)貌似没有问题,紧接着我们考虑进行top。

每top一个元素,front就向前走一个(队列先进先删),那么当front和rear重合的时候(希望将环形队列删空),又需要单独对删除最后一个数据进行处理。

因此,我们采取“rear指向尾节点下一个”的思想(模仿栈的初始top=0)

为了防止队列为空和队列全满的时候无法区分(均满足条件front==rear,也就是常说的假溢出问题),我们多使用一个节点来存放rear.

再检查我们需要实现的功能,如何取尾?是遍历链表(效率低下)还是造轮子实现双向链表?

显然都不是最优解..........

尽管以上方法都能实现,但是为了减少需要注意的细节和特殊情况的处理,我们选择使用顺序表而非链表,rear指向尾的下一个,这样就能随机访问下标元素了。自然的,front和rear就由指针变成了链表。


小结:

a.链表

front  头

rear 尾下一个——————————————————————》无法区分头尾


b.链表

front 头

rear 尾——————————————————————》删除最后一个要单独操作,并且刚开始时需要将rear指向空。


c.注意了以上所有,依然需要实现双向链表(不是不能做,只是选型的目的就是找最优解),所以我们使用数组

具体实现时,开辟k+1个空间,注意让下标正常++时,每进入新的循环(比如front由k又+1到了0)

利用取模(%(k+1))操作,去掉每一个整周期即可。

所以,每push一次rear都要+1后取模以去掉整周期,每pop一次front都要+1后取模来去掉整周期。


(判断空时不用取模,为空时不管两个下标轮回了多少次都一定相等,毕竟之前该取的时候都取过了)

push操作:

因为每次加入数据之后rear都会取模,所以始终控制在较小的数据上(第一轮正周期之内)


之所以判断是否满的函数还要再单独取模,就是因为可能在rear+1那一下刚好发生如下临界的尴尬情况


取尾数据时的特殊情况:

                

为了让rear-1可以继续,我们给rear再加一轮(k+1),多走一个周期,保障所有情况rear都可以在变化之后被访问到

全部实现:

typedef struct {
    int* a;
    int front;
    int rear;
    int k;
} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k) {
     MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
     obj->k=k;
     obj->front=obj->rear=0;
     obj->a=(int*)malloc(sizeof(int)*(k+1));
     return obj;
}

bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueIsEmpty(MyCircularQueue* obj);

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj)){
        return false;
    }
    obj->a[obj->rear]=value;
    obj->rear=(obj->rear+1)%(obj->k+1);
    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj)){
        return false;
    }
    obj->front=(obj->front+1)%(obj->k+1);
    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    return -1;
    return obj->a[obj->front];
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    return -1;
    return obj->a[(obj->rear+obj->k)%(obj->k+1)];
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front==obj->rear;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->rear+1)%(obj->k+1)==obj->front;
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

最后也是顺利通过了:

  • 24
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值