栈和队列应用实操
目录
有效的括号(力扣)
思路:
代码形成(栈的结构):
结果:
用队列实现栈
思路:
代码实现:
结果:
注意:
结论:销毁空队列是完全安全的,函数会正常执行(不会触发断言失败),最终只是将队列的指针和大小重置为初始空状态,没有内存泄漏风险。
用栈实现队列
思路:编辑
代码实现:
结果:
核心思想:
优势:
*设计循环队列
思路:
代码实现:
结果:
优点:
有效的括号(力扣)
https://leetcode.cn/problems/valid-parentheses/description/
思路:
代码形成(栈的结构):
//定义栈的结构
typedef int STDataType;
typedef struct Stack
{STDataType* arr;int top;//有效数据个数int capacity;//容量
}ST;
//初始化
void StackInit(ST* ps)
{assert(ps);ps->arr = NULL;ps->top = ps->capacity = 0;
}//入栈----栈顶
void StackPush(ST* ps, STDataType x)
{if (ps->top == ps->capacity){//增容int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc");exit(1);}//空间申请成功ps->arr = tmp;ps->capacity = newCapacity;}ps->arr[ps->top++] = x;
}//栈是否为空
bool STEmpty(ST* ps)
{assert(ps);return ps->top == 0;
}//出栈----栈顶
void StackPop(ST* ps)
{assert(!STEmpty(ps));--ps->top;
}//出栈顶数据,元素不会删除
STDataType StackTop(ST* ps)
{assert(!STEmpty(ps));return ps->arr[ps->top - 1];}//获取栈中有效元素个数
int StackSize(ST* ps)
{return ps->top;
}
//销毁
void StackDestory(ST* ps)
{if (ps->arr)free(ps->arr);ps->arr = NULL;ps->top = ps->capacity = 0;
}//---------以上是栈的结构----------
bool isValid(char* s) {ST st;StackInit(&st);char*pi=s;while(*pi!='\0'){//左括号入栈if(*pi=='('||*pi=='['||*pi=='{'){StackPush(&st,*pi);}else{//先判断栈为空情况if(STEmpty(&st)){StackDestory(&st);return false;}//右括号然后取栈顶与*pi匹配char top=StackTop(&st);if((top=='('&&*pi!=')')||(top=='['&&*pi!=']')||(top=='{'&&*pi!='}')){StackDestory(&st);return false;}StackPop(&st);}pi++;}bool ret=STEmpty(&st);StackDestory(&st);return ret;
}
结果:
- 若要 “销毁在判断之前”,只需交换两者的调用顺序,但结果无实际意义(永远返回
true
)。 - 实际开发中,几乎所有场景都需要 “先判断再销毁”,以获取有价值的状态信息,再安全释放资源。
用队列实现栈
https://leetcode.cn/problems/implement-stack-using-queues/description/
思路:
代码实现:
typedef int QDataType;
//队列结点的结构
typedef struct QueueNode
{QDataType data;struct QueueNode* next;
}QueueNode;
//队列的结构
typedef struct Queue
{QueueNode* phead;QueueNode* ptail;int size; //队列中有效数据个数
}Queue;
//初始化
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;pq->size = 0;//初始化不能遗漏
}//入队——队尾
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc fail");exit(1);}newnode->data = x;newnode->next = NULL;if (pq ->phead!= NULL)//队列非空{pq->ptail->next = newnode;pq->ptail = newnode;}else//队列为空{pq->phead = pq->ptail = newnode;}pq->size++;
}//队列判空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}//出队——队头
void QueuePop(Queue* pq)
{assert(!QueueEmpty(pq));if (pq->phead == pq->ptail)//只有一个节点,头尾都置为空{free(pq->phead);pq->phead=pq->ptail = NULL;}else{QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}pq->size--;
}//取队头数据
QDataType QueueFront(Queue* pq)
{assert(!QueueEmpty(pq));return pq->phead->data;
}//取队尾数据
QDataType QueueBack(Queue* pq)
{assert(!QueueEmpty(pq));return pq->ptail->data;
}
int QueueSize(Queue* pq)
{return pq->size;
}
//销毁队列
void QueueDestroy(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;while (pcur){QueueNode* next = pcur->next;free(pcur);pcur = next;}pq->phead = pq->ptail =NULL;pq->size =0;
}typedef struct {Queue q1;Queue q2;
} MyStack;MyStack* myStackCreate() {MyStack*pst=(MyStack*)malloc(sizeof(MyStack));QueueInit(&pst->q1);QueueInit(&pst->q2);return pst;
}void myStackPush(MyStack* obj, int x) {//往不为空的队列中插入数据if(!QueueEmpty(&obj->q1)){QueuePush(&obj->q1,x);}else{QueuePush(&obj->q2,x);}
}int myStackPop(MyStack* obj) {//将不为空队列前size-1个数据移到另一个队列//再将最后一个数据出队列Queue*emp=&obj->q1;Queue*noneEmp=&obj->q2;if(QueueEmpty(&obj->q2)){emp=&obj->q2;noneEmp=&obj->q1;}while(QueueSize(noneEmp)>1){QueuePush(emp,QueueFront(noneEmp));QueuePop(noneEmp);}int top=QueueFront(noneEmp);QueuePop(noneEmp);return top;
}
//取栈顶
int myStackTop(MyStack* obj) {//找不为空队列中队尾数据if(!QueueEmpty(&obj->q1)){return QueueBack(&obj->q1);}else{return QueueBack(&obj->q2);}
}bool myStackEmpty(MyStack* obj) {return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}
//销毁
void myStackFree(MyStack* obj) {QueueDestroy(&obj->q1);QueueDestroy(&obj->q2);free(obj);obj=NULL;
}/*** Your MyStack struct will be instantiated and called as such:* MyStack* obj = myStackCreate();* myStackPush(obj, x);* int param_2 = myStackPop(obj);* int param_3 = myStackTop(obj);* bool param_4 = myStackEmpty(obj);* myStackFree(obj);
*/
结果:
注意:
当队列本身为空(即 pq->phead == NULL
,队列中没有元素)时,调用 QueueDestroy
会怎样?
此时的执行流程如下:
assert(pq);
会通过(因为pq
是有效的指针,非NULL
)。判空一般使用在出数据或插入某位置时,然后assert(pq)即可满足QueueNode* pcur = pq->phead;
会让pcur
指向NULL
(因为队列为空时phead
是NULL
)。while (pcur)
循环条件不成立(pcur
是NULL
),循环体不会执行。- 执行
pq->phead = pq->ptail = NULL;
和pq->size = 0;
(相当于再次重置空队列的状态,虽然此时已经是默认空状态)。
结论:
销毁空队列是完全安全的,函数会正常执行(不会触发断言失败),最终只是将队列的指针和大小重置为初始空状态,没有内存泄漏风险。
这符合销毁操作的设计原则:即使容器为空,销毁操作也应该能安全执行,避免用户在调用销毁前还要额外判断容器是否为空。
用栈实现队列
https://leetcode.cn/problems/implement-queue-using-stacks/description/
思路:
代码实现:
//定义栈的结构
typedef int STDataType;
typedef struct Stack
{STDataType* arr;int top;//有效数据个数int capacity;//容量
}ST;
//初始化
void StackInit(ST* ps)
{assert(ps);ps->arr = NULL;ps->top = ps->capacity = 0;
}//入栈----栈顶
void StackPush(ST* ps, STDataType x)
{if (ps->top == ps->capacity){//增容int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc");exit(1);}//空间申请成功ps->arr = tmp;ps->capacity = newCapacity;}ps->arr[ps->top++] = x;
}//栈是否为空
bool STEmpty(ST* ps)
{assert(ps);return ps->top == 0;
}//出栈----栈顶
void StackPop(ST* ps)
{assert(!STEmpty(ps));--ps->top;
}//出栈顶数据,元素不会删除
STDataType StackTop(ST* ps)
{assert(!STEmpty(ps));return ps->arr[ps->top - 1];}//获取栈中有效元素个数
int StackSize(ST* ps)
{return ps->top;
}//销毁
void StackDestory(ST* ps)
{assert(ps);if (ps->arr)free(ps->arr);ps->arr = NULL;ps->top = ps->capacity = 0;
}//-------栈的结构---------
typedef struct {ST pushst;ST popst;
} MyQueue;MyQueue* myQueueCreate() {MyQueue*pq=(MyQueue*)malloc(sizeof(MyQueue));StackInit(&pq->pushst);StackInit(&pq->popst);return pq;
}void myQueuePush(MyQueue* obj, int x) {//直接将x插入pushst栈中StackPush(&obj->pushst,x);
}//检测popst是否为空,若不为空,直接出栈顶
//为空,将pushst中数据导入popst,再出栈顶
int myQueuePop(MyQueue* obj) {if(STEmpty(&obj->popst)){while(!STEmpty(&obj->pushst)){StackPush(&obj->popst,StackTop(&obj->pushst));StackPop(&obj->pushst);}}int top= StackTop(&obj->popst);StackPop(&obj->popst);return top;
}//取队头
int myQueuePeek(MyQueue* obj) {if(STEmpty(&obj->popst)){while(!STEmpty(&obj->pushst)){StackPush(&obj->popst,StackTop(&obj->pushst));StackPop(&obj->pushst);}}return StackTop(&obj->popst);
}bool myQueueEmpty(MyQueue* obj) {return STEmpty(&obj->pushst)&&STEmpty(&obj->popst);
}void myQueueFree(MyQueue* obj) {StackDestory(&obj->pushst);StackDestory(&obj->popst);free(obj);obj=NULL;
}/*** Your MyQueue struct will be instantiated and called as such:* MyQueue* obj = myQueueCreate();* myQueuePush(obj, x);* int param_2 = myQueuePop(obj);* int param_3 = myQueuePeek(obj);* bool param_4 = myQueueEmpty(obj);* myQueueFree(obj);
*/
结果:
核心思想:
- 分工明确:
pushst
栈专门用于处理入队操作(myQueuePush
)popst
栈专门用于处理出队(myQueuePop
)和取队头(myQueuePeek
)操作
- 数据迁移:当需要出队或取队头时,如果
popst
为空,就将pushst
中的所有元素依次弹出并压入popst
,此时popst
中元素的顺序与原队列顺序一致 - 空队列判断:当两个栈都为空时,队列才为空
优势:
- 高效性:每个元素最多被压栈和出栈各两次(从
pushst
到popst
),整体时间复杂度为 O (1) ) - 内存管理完善:
myQueueFree
函数正确销毁两个栈并释放队列结构体,避免内存泄漏 - 边界处理合理:通过
STEmpty
判断栈是否为空,确保操作的安全性
*设计循环队列
https://leetcode.cn/problems/design-circular-queue/description/
思路:
- 空间设计:申请
k+1
个空间(k
为队列最大容量),用一个位置作为 “空标记”,区分队列空和满的状态。 - 指针定义:
front
:指向队头元素(初始为 0)。rear
:指向队尾元素的下一个位置(初始为 0)。
- 空满判断:
- 空队列:
front == rear
。 - 满队列:
(rear + 1) % (capacity + 1) == front
(capacity
为k
)。
- 空队列:
代码实现:
typedef struct {int* arr;int front;//队头int rear;//队尾int capacity;//循环队列空间大小
} MyCircularQueue;MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue*pq=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));//申请k+1个空间pq->arr=(int*)malloc(sizeof(int)*(k+1));pq->front=pq->rear=0;pq->capacity=k;return pq;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->front==obj->rear;
}bool myCircularQueueIsFull(MyCircularQueue* obj) {return (obj->rear+1)%(obj->capacity+1)==obj->front;
}
//向循环队列插入一个元素
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if(myCircularQueueIsFull(obj)){return false;}//没有满obj->arr[obj->rear++]=value;obj->rear%=obj->capacity+1;return true;
}bool myCircularQueueDeQueue(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){
return false;}//非空++obj->front;obj->front%=obj->capacity+1;return true;
}int myCircularQueueFront(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return -1;}return obj->arr[obj->front];
}int myCircularQueueRear(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return -1;}int prev=obj->rear-1;if(obj->rear==0){prev=obj->capacity;}return obj->arr[prev];
}void myCircularQueueFree(MyCircularQueue* obj) {if(obj->arr)free(obj->arr);free(obj);obj=NULL;
}/*** Your MyCircularQueue struct will be instantiated and called as such:* MyCircularQueue* obj = myCircularQueueCreate(k);* bool param_1 = myCircularQueueEnQueue(obj, value);* bool param_2 = myCircularQueueDeQueue(obj);* int param_3 = myCircularQueueFront(obj);* int param_4 = myCircularQueueRear(obj);* bool param_5 = myCircularQueueIsEmpty(obj);* bool param_6 = myCircularQueueIsFull(obj);* myCircularQueueFree(obj);
*/
结果:
优点:
- 高效性:所有操作(入队、出队、取头尾)的时间复杂度均为 O (1)。
- 边界处理完善:空队列取元素返回
-1
,满队列入队返回false
,符合常规设计。 - 逻辑清晰:通过
front
和rear
的移动及取模运算,完美实现循环特性。