目录
1.栈是什么
栈如同数据桶,先来的数据被埋在桶底,后来的数据浮搁其上,要想翻找到底部的数据,就得先将其上方的数据拾去。
其中,“桶底” 称作 栈底,
“桶顶” 称作 栈顶,
“向桶中埋数据” 称作 入栈/压栈,
“把最上方的数据拾去” 称作 出栈。
可见,栈只能在一端操纵数据出入,整体有着“先入后出”的特点
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDatatype;
typedef struct Stack
{
STDatatype* a;//栈底指针
int top;//栈顶元素下一个位置的下标
int capacity;//容量
}Stack;
void STInit(Stack* pst);//创建
void STDestroy(Stack* pst);//销毁
void STPush(Stack* pst, STDatatype x);//压栈
void STPop(Stack* pst);//出栈
STDatatype STTop(Stack* pst);//获取栈顶元素
bool STEmpty(Stack* pst);//判断是否为空
int STSize(Stack* pst);//获取大小
void STInit(Stack* pst)
{
assert(pst);
pst->a = NULL;
pst->top = 0;
pst->capacity = 0;
}
void STDestroy(Stack* pst)
{
assert(pst);
free(pst->a);
pst->top = 0;
pst->capacity = 0;
}
void STPush(Stack* pst, STDatatype x)
{
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDatatype* tmp = (STDatatype*)realloc(pst->a, newcapacity * sizeof(STDatatype));
assert(tmp);
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
void STPop(Stack* pst)
{
assert(pst);
assert(!STEmpty(pst));
pst->top--;
}
STDatatype STTop(Stack* pst)
{
assert(pst);
assert(!STEmpty(pst));
return pst->a[pst->top - 1];
}
bool STEmpty(Stack* pst)
{
assert(pst);
return pst->top == 0;
}
int STSize(Stack* pst)
{
assert(pst);
return pst->top;
}
2.队列是什么
队列如同数据管,先来的数据站在队头,后来的数据紧随其后,队头的数据可以离开,队尾的数据不能转身。
其中,“让队头数据离开” 称作 出队,
“把数据排在队尾” 称作 入队。
可见,队列可以在两端操纵数据,但出和入两权分立,整体有着“先入先出”的特点
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
typedef int QDatatype;
typedef struct QueueNode
{
QDatatype data;
struct QueueNode* next;
}QNode;//队列节点
typedef struct QNode
{
QNode* phead;
QNode* ptail;
int size;
}Queue;//队列
void QueueInit(Queue* pq);//初始化
void QueueDestory(Queue* pq);//销毁
void QueuePush(Queue* pq, QDatatype x);//入队
void QueuePop(Queue* pq);//出队
QDatatype QueueFront(Queue* pq);//获取队头元素
QDatatype QueueBack(Queue* pq);//获取对尾元素
int QueueSize(Queue* pq);//获取队列大小
bool QueueEmpty(Queue* pq);//判断是否为空
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
void QueueDestory(Queue* pq)
{
assert(pq);
QNode* cur = pq->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq, QDatatype x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
assert(newnode);
newnode->data = x;
newnode->next = NULL;
if (pq->phead == NULL)
{
assert(pq->ptail == NULL);
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = pq->ptail->next;
}
pq->size++;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
if (pq->phead->next == NULL)
{
free(pq->phead);
pq->phead = NULL;
pq->ptail = NULL;
}
else
{
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
QDatatype QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->phead->data;
}
QDatatype QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
3.栈和队列的联系
一致点:栈顶可以出数据,队头可以出数据
矛盾点:栈底不可入数据,队尾可以入数据
两个栈可以实现队列
思路:一个栈存数据,另一个栈辅助捯数据(此处以栈顶模拟队尾,栈底模拟队头为例)
“入队”1、2、3、4(实际操作:入数据栈1、2、3、4)
“出队”1(实际操作:把数据倒入辅助栈,辅助栈出栈,再倒回数据栈)
typedef struct {
Stack s1;
Stack s2;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
STInit(&obj->s1);
STInit(&obj->s2);
return obj;
}
void myQueuePush(MyQueue* obj, int x) {
STPush(&obj->s1, x);
}
int myQueuePop(MyQueue* obj) {
while(obj->s1.top != 0)
{
STPush(&obj->s2, obj->s1.a[obj->s1.top-1]);
STPop(&obj->s1);
}
int ret = STTop(&obj->s2);
STPop(&obj->s2);
while(obj->s2.top != 0)
{
STPush(&obj->s1, STTop(&obj->s2));
STPop(&obj->s2);
}
return ret;
}
int myQueuePeek(MyQueue* obj) {
while(obj->s1.top != 0)
{
STPush(&obj->s2, obj->s1.a[obj->s1.top-1]);
STPop(&obj->s1);
}
int ret = STTop(&obj->s2);
while(obj->s2.top != 0)
{
STPush(&obj->s1, STTop(&obj->s2));
STPop(&obj->s2);
}
return ret;
}
bool myQueueEmpty(MyQueue* obj) {
return STEmpty(&obj->s1);
}
void myQueueFree(MyQueue* obj) {
STDestroy(&obj->s1);
STDestroy(&obj->s2);
free(obj);
}
两个队列可以实现栈
思路:一个队列存数据,另一个队列辅助捯数据(此处以队头模拟栈底,队尾模拟栈顶为例)
“入栈”1、2、3(实际操作:入数据队列1、2、3)
“出栈”3(实际操作:数据队列流入辅助队列,剩一个出队,再流回数据队列)
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) {
QueuePush(&obj->q1,x);
}
int myStackPop(MyStack* obj) {
while(obj->q1.size != 1)
{
assert(obj->q1.phead);
QueuePush(&obj->q2, QueueFront(&obj->q1));
QueuePop(&obj->q1);
}
int ret = QueueFront(&obj->q1);
QueuePop(&obj->q1);
Queue tmp = obj->q1;
obj->q1 = obj->q2;
obj->q2 = tmp;
return ret;
}
int myStackTop(MyStack* obj) {
while(obj->q1.size != 0)
{
QueuePush(&obj->q2, QueueFront(&obj->q1));
QueuePop(&obj->q1);
}
int ret = QueueBack(&obj->q2);
Queue tmp = obj->q1;
obj->q1 = obj->q2;
obj->q2 = tmp;
return ret;
}
bool myStackEmpty(MyStack* obj) {
return obj->q1.size == 0;
}
void myStackFree(MyStack* obj) {
QueueDestory(&obj->q1);
QueueDestory(&obj->q2);
free(obj);
}
4.栈和队列的本质
为什么栈和队列可以相互实现?
正因为二者本质相同,都是线性表这一数据结构,都是对数据在逻辑上线性排列,在操作上增删查改,
因此通过特定的手法进行操作,便可以实现相同的功能。
只不过,栈和队列是分别对线性表在功能上进行了特化,栈特化了尾插和尾删,队列特化了尾插和头删,消去了冗余的功能。
其目的,是为了将各自功能突出,利于问题模块化,应用标准化。例如内存分区的栈区,底层采用的就是功能特化的栈,而非
功能全面的顺序表或链表。
至于线性表采用顺序表还是链表,要看谁能将其优点发挥极致,
顺序表缓存命中率高,有着访问和修改优势,且尾插尾删效率可观,栈就乐于采用顺序表。
链表任意位置插入删除时间复杂度O(1),有着插入和删除优势,队列就乐于用链表来实现。
至此,本篇文章来到了最后的时刻
浪水涂平白粒,海陆温风交转,平线的眼睑默然微睁,那日子还会远吗?
祝各位终能旅抵心中的目标