简述:栈:“后进先出” 队列:“先进先出”
两个栈能实现队列 两个队列能实现栈
目录
栈:
栈的概念:
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端
称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
栈的实现:
在栈的实现上 用数组和链表都是可以的 但是数组更具优势 在后续数据的存入中速度更快(CPU高速缓存命中率更高)
如果要使用单链表实现 就要把链表头当做栈顶 头插头删 否则就要使用双向链表
各接口实现:
这里我们创建数组栈的形式 因为之前有了顺序表各接口实现的基础 这里不再详述
注意的点都在注释
//初始化
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;//当top初始值为0的时候 top指向的是栈顶位置的下一位的
ps->capacity = 0;
}
//销毁
void StackDestroy(ST* ps)
{
assert(ps);
ps->capacity = 0;
ps->top = 0;
free(ps->a);
ps->a = NULL;
}
//压栈
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)//判断是否需要扩容
{
int newcapacity = ps->top == 0 ? 4 : ps->capacity * 2;//进行判断
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);
if (tmp == NULL)
{
printf("realloc fail");
exit(-1);
}
ps->a = tmp;//
ps->capacity = newcapacity;//容量也切换为新容量
}
//填充进去数据
ps->a[ps->top] = x;
ps->top++;
}
//出栈
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));//避免是个空栈
//一直减下去 top就会变成负值
ps->top--;
}
//提取栈顶的值
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));//避免是个空栈
return ps->a[(ps->top) - 1];//这里不要使用有副作用的--
}
//统计栈中数据的个数
int StackSize(ST* ps)
{
assert(ps);
return ps->top;//ps->top就把存储的数据个数记录下来了 直接返回就OK
}
//判断栈是否为空
bool StackEmpty(ST* ps)
{
assert(ps);
/*if (ps->top == 0)
{
return true;
}
else
{
return false;
}*/
return ps->top == 0;//这种表示更简便 但是上面思路更加清晰
}
队列:
队列的概念:
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头
队列的实现:
队列与堆不同,不能达到数组和链表形式均可以 出于其先入先出的性质,如果使用数组 效率太低
因此这里只能使用链表来实现队列
各接口实现:
同样,这里也不再详述 若有不了解 请阅读注释或私信笔者
//初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
//销毁
void QueueDestroy(Queue* pq)
{
assert(pq);
while (pq->head)
{
QueueNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->head = pq->tail= NULL;
}
//入列
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
printf("malloc ");
exit(-1);
}
newnode->next = NULL;
newnode->x = x;
if (pq->head==NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
//出列
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
QueueNode* tmp = pq->head;
pq->head = pq->head->next;
free(tmp);
tmp = NULL;
//这里值得注意的点是 当pq->head->next为NULL时 此时tail仍指向原位置 此时就会造成野指针的解引用
if (pq->head == NULL)
{
pq->tail = NULL;
}
}
//返回队头
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->x;
}
//返回末尾值
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));//防止为空队列
return pq->tail->x;
}
//计数
int QueueSize(Queue* pq)//这里如果结构体中设置了sz用来计数 O(1) 直接返回就OK了 如果没有就需要遍历一遍O(N)
{
assert(pq);
int sz = 0;
QueueNode* next = pq->head;
while (next)
{
next = next->next;
sz++;
}
return sz;
}
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
栈和队列:
两个栈实现队列:
栈的性质是后进先出 队列的性质是先进先出 要围绕着性质进行
怎样能把后入先出的两个栈实现
整体思路:
假设两个栈 一个栈负责Push 一个栈负责Pop
1.插入数据: 就是正常的压栈操作
2.删除数据: 要先判断popST中是否为空 如果为空 需要从pushST中搬运
根据题目要求 要创建变量保存一下数据 然后出栈
3.返回开头元素: 如果popST为空 就先进行搬运 如果有数据 根据先进先出原则 存在的栈顶就是队头
4.判空和内存销毁都比较简单 只需要注意是否全部释放内存就OK
typedef struct {
ST pushST;
ST popST ;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* st = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&st->pushST);
StackInit(&st->popST);
return st;
}
void myQueuePush(MyQueue* obj, int x) {
//向pushST后面压栈
StackPush(&obj->pushST,x);
}
int myQueuePop(MyQueue* obj) {
//出的时候要判断popST是否为空
if(StackEmpty(&obj->popST))//如果为空 需要从pushST向里面推
{
while(!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST,StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
//创建个变量保存队列开头元素
//此时位于popST的栈顶
int tmp = StackTop(&obj->popST);
StackPop(&obj->popST);
return tmp;
}
int myQueuePeek(MyQueue* obj) {
if(StackEmpty(&obj->popST))//如果为空 需要从pushST向里面推
{
while(!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST,StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
int tmp = StackTop(&obj->popST);
return tmp;
}
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->pushST)&&StackEmpty(&obj->popST);
}
void myQueueFree(MyQueue* obj) {
StackDestroy(&obj->pushST);
StackDestroy(&obj->popST);
free(obj);
}
两个队列实现栈:
也是围绕着队列和栈的性质进行
整体思路:
假设两个栈 一个存储数据 一个为空(在删除数据之后) 二者不断轮换
插入数据 : 向非空队列插入数据
删除数据 : 从非空队列向空队列搬运数据 搬运一个销毁一个 最后只剩下一个 将其pop掉
根据题目要求 pop之前创建变量 保存数据
返回栈顶的数据: 只有非空队列有数据 根据其性质 队尾位置即为栈顶数据
判空和销毁比较简单 仍然要主要free完全
typedef struct {
Queue s1;
Queue s2;
} MyStack;
MyStack* myStackCreate() {
MyStack* st = (MyStack*)malloc(sizeof(MyStack));//这里要注意 静态开辟的内存 是局部变量
//如果建立全局变量可能出问题 所以我们这里选择malloc
QueueInit(&st->s1);
QueueInit(&st->s2);
return st;
}
void myStackPush(MyStack* obj, int x) {
//向不是空的队列 插入
if(!QueueEmpty(&obj->s1))
{
//向里面插入
QueuePush(&obj->s1,x);
}
else
{
QueuePush(&obj->s2,x);
}
}
int myStackPop(MyStack* obj) {
//设立一个空队列
Queue* emptyqueue = &obj->s1;//定义空队列
Queue* nonemptyqueue = &obj->s2;
if(!QueueEmpty(&obj->s1))
{
emptyqueue = &obj->s2;
nonemptyqueue = &obj->s1;
}
while(QueueSize(nonemptyqueue)>1)
{
QueuePush(emptyqueue,QueueFront(nonemptyqueue));//将非空队列中的前面几个赶到空队列中
QueuePop(nonemptyqueue);
}
int end = QueueFront(nonemptyqueue);//保存栈顶元素
QueuePop(nonemptyqueue);//移除栈顶元素
return end;
}
int myStackTop(MyStack* obj) {
if(!QueueEmpty(&obj->s1))
{
return QueueBack(&obj->s1);
}
else
{
return QueueBack(&obj->s2);
}
}
bool myStackEmpty(MyStack* obj) {
return QueueEmpty(&obj->s1)&&QueueEmpty(&obj->s2);
}
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->s1);//除了obj 队列里面的也要free
QueueDestroy(&obj->s2);
free(obj);
}