栈和队列及面试题

本篇目标

1、栈

2、队列

3、栈和队列的面试题

1、栈

1.1、栈的概念

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。 出栈:栈的删除操作叫做出栈。出数据也在栈顶。

    后进先出  :  栈就相当于一个容器,后存进去的先取出来。

                                 

存储数据又称为压栈,就是“堆在一起”,取出就只能从最上面开始。

栈的实现的底层逻辑可以是数组或者是链表,相比之下数组的结构要更优一些,因为数组插入数据的代价更小一些,而且数组支持下标访问以及随机访问。

在实际中静态的栈不实用不常见,所以我们下面来实现一下动态数组实现的栈。

//以下是实现栈基本功能的头文件的声明
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>


typedef int STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;		// 标识栈顶位置的
	int capacity;
}ST;
//栈的初始化
void STInit(ST* pst);
//栈的销毁
void STDestroy(ST* pst);

// 栈顶插入删除
void STPush(ST* pst, STDataType x);
//栈顶删除数据
void STPop(ST* pst);
//取出栈顶的元素
STDataType STTop(ST* pst);
//判断栈是否为空
bool STEmpty(ST* pst);
//得到栈中元素的数目
int STSize(ST* pst);
栈当中函数的具体实现

#include"Stack.h"

void STInit(ST* pst)

{

assert(pst);

//一定要检查传过来的指针是否合法,确保代码的稳健性

pst->a = NULL;

pst->capacity = 0;

// 表示top指向栈顶元素的下一个位置

pst->top = 0;

// 表示top指向栈顶元素

//pst->top = -1;

}

void STDestroy(ST* pst)

{

assert(pst);

free(pst->a);

pst->a = NULL;

pst->top = pst->capacity = 0;

}

// 栈顶插入删除

void STPush(ST* pst, STDataType x)

{

assert(pst);

if (pst->top == pst->capacity)

{

int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;

STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType) * newcapacity);

if (tmp == NULL)

{

perror("realloc fail");

return;

}

pst->a = tmp;

pst->capacity = newcapacity;

}

pst->a[pst->top] = x;

pst->top++;

}

void STPop(ST* pst)

{

assert(pst);

// 不为空

assert(pst->top > 0);

pst->top--;

}

STDataType STTop(ST* pst)

{

assert(pst);

// 不为空

assert(pst->top > 0);

return pst->a[pst->top - 1];

}

bool STEmpty(ST* pst)

{

assert(pst);

/*if (pst->top == 0)

{

return true;

}

else

{

return false;

}*/

return pst->top == 0;

}

int STSize(ST* pst)

{

assert(pst);

return pst->top;

}

2、队列

2.1、栈的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头

与栈相反,队列的结构与我们生活中排队买东西相似,先进先出,并且是从队尾进从队头出。

 队头:允许取出删除的一端,又称为队首。

队尾:允许插入的一端。

空队列:不包含任何元素的队列,队头和队尾指向空指针。

 实现队列常见功能函数的声明

#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>//断言非常重要
#include<stdlib.h>
// 链式结构:表示队列 
typedef int QDataType;
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* front;
	QNode* tail;
}Queue;

// 初始化队列 
void QueueInit(Queue* q);
// 队尾入队列 
void QueuePush(Queue* q, QDataType x);
// 队头出队列 
void QueuePop(Queue* q);
// 获取队列头部元素 
QDataType QueueFront(Queue* q);
// 获取队列队尾元素 
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数 
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q);
// 销毁队列 
void QueueDestroy(Queue* q);

初始化队列 

初始化队列时应将头尾指针都置为空

void QueueInit(Queue* q)

{

assert(q);

q->front = q->tail = NULL;

}

 插入元素

void QueuePush(Queue* pq, QDataType x)
{
    assert(pq);//确保传进来的指针有效

    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    if (newnode == NULL)
    {
        perror("malloc fail");
        return;
    }

    newnode->data = x;
    newnode->next = NULL;
//如果newnode是第一个节点则头尾指向头一个指针
    if (pq->tail == NULL)
    {
        pq->tail = pq->front = newnode;
    }
    else
    {
        pq->tail->next = newnode;
        pq->tail = newnode;
    }
}

 删除队头的元素

void QueuePop(Queue* q)
{assert(q);

assert(q->front);//确保队列的结点不为空

QNode* del = q->front;

q->front = q->front->next;

free(del);

del = NULL;

//如果只有一个元素则删除之后队列为空,队头和队尾应都置为空指针

if (q->front == NULL)

q->tail = NULL;

}

 

 队列的销毁等操作

 
 
 
 

void QueueDestroy(Queue* q)//队列的销毁

{

assert(q);

QNode* cur = q->front;

while (cur)

{

QNode* next = cur->next;

free(cur);

cur = next;

}

q->front = q->tail = NULL;

}

QDataType QueueFront(Queue* q)//得到对头的元素

{

assert(q);

assert(q->front);

return q->front->data;

}

QDataType QueueBack(Queue* q)

{

assert(q);

assert(q->tail);

return q->tail->data;

}

int QueueSize(Queue* q)//得到队列元素个数

{

int size = 0;

QNode* p1 = q->front, * p2 = q->tail;

while (p1 != p2)

{

size++;

p1 = p1->next;

}

return size + 1;

}

int QueueEmpty(Queue* q)//判断是否为空

{

assert(q);

return q->front == NULL;

3、栈和队列在 面试中的问题

以上就是基础队列和栈的实现,在面试中我们可能有遇到以下的问题

1. 用队列实现栈。

2. 用栈实现队列。

 3. 设计循环队列。

那么这篇文章我们先来实现前两个面试问题,下一篇文章解决设计循环队列的问题。

1、用队列实现栈

用队列实现栈后进先出的功能需要两个队列

然后我们直接CTRL+c+v上面我们实现过的队列的函数

1.1、栈的初始化

 1.2、元素入栈

void myStackPush(MyStack* obj, int x) {

    if(!QueueEmpty(&obj->p1))

    {

        QueuePush(&obj->p1,x);

    }

    else

    QueuePush(&obj->p2,x);

}

1.3、删除栈顶元素

 确保有一个队列为空,便于我们从一个队列中‘’翻捡‘’出栈底的元素,所以将新插入元素插入到非空的队列中,当我们需要得到栈底的元素时,假设元素个数为N,就把前N-1个元素放到空队列中然后在对最后一个元素进行操作即可。

nt myStackPop(MyStack* obj) {

    Queue* emptyq=&obj->p1;

    Queue*nonemptyq=&obj->p2;

if(!QueueEmpty(&obj->p1))//确保Empty指针指向空队列

{

    emptyq=&obj->p2;

    nonemptyq=&obj->p1;

}

while(QueueSize(nonemptyq)>1)

{//把非空队列的元素移到另一个队列中,只保留一个队头即栈底的元素

    QueuePush(emptyq,QueueFront(nonemptyq));

QueuePop(nonemptyq);

}

int top=QueueFront(nonemptyq);

QueuePop(nonemptyq);

 return top;

}

1.4、得到栈顶元素

得到栈顶的元素只需取出队头(非空队列)的元素即可 

int myStackTop(MyStack* obj) {

    if(!QueueEmpty(&obj->p1))

    {

        return QueueBack(&obj->p1);

    }

    else

    return QueueBack(&obj->p2);

}

1.5、栈的销毁和判空

bool myStackEmpty(MyStack* obj) {

    return QueueEmpty(&obj->p1)&&QueueEmpty(&obj->p2);

}

//切记栈的销毁不能直接释放栈的结点,因为栈的结构中包含两个队列类型指针如果直接释放,没有先将两个指针释放并置为空,会出现野指针

void myStackFree(MyStack* obj) {

    QueueDestroy(&obj->p1);

    QueueDestroy(&obj->p2);

    free(obj);

}

2、用栈实现队列

用栈实现队列我们需要两个栈,一个为输出栈(inq),一个为输入栈(outq)

2.1、栈的初始化

typedef struct {

    ST inq;

    ST outq;

} MyQueue;

MyQueue* myQueueCreate() {

    MyQueue* obj=(MyQueue *)malloc(sizeof(MyQueue));

    STInit(&obj->inq);//初始化输入栈

    STInit(&obj->outq);//初始化输出栈

    return obj;

}

2.2、元素入队尾

只需向输入栈顶插入即可

void myQueuePush(MyQueue* obj, int x) {

STPush(&obj->inq,x);

}

2.3、删除队头元素并返回该元素

如果输出栈中有元素则直接返回并删除该元素即可,如果没有则先把输入栈中的元素移到输出栈中并返回最后一个元素。

int myQueuePop(MyQueue* obj) {

    if(obj->outq.top>0)

    {STDataType x= STTop(&obj->outq);

        STPop(&obj->outq);

        return x;

    }

    else

    {

        while(!STEmpty(&obj->inq))

        {

            STPush(&obj->outq,STTop(&obj->inq));

STPop(&obj->inq);

        }

    STDataType x= STTop(&obj->outq);

        STPop(&obj->outq);

        return x;

    }

}

//返回对头元素

int myQueuePeek(MyQueue* obj) {

    if(STEmpty(&obj->outq))

    {

               while(!STEmpty(&obj->inq))

        {

            STPush(&obj->outq,STTop(&obj->inq));

STPop(&obj->inq);

        }

    }

                return STTop(&obj->outq);

}

 2.4、队列的判空以及销毁

bool myQueueEmpty(MyQueue* obj) {

    if(STEmpty(&obj->inq)&&STEmpty(&obj->outq))//两个栈都为空则队列为空

    return true;

    else

    return false;

}

void myQueueFree(MyQueue* obj) {

    STDestroy(&obj->inq);

    STDestroy(&obj->outq);

}

至此本篇内容结束,下一篇我们一起来实现循环队列。

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值