本篇目标
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);
}
至此本篇内容结束,下一篇我们一起来实现循环队列。