现在是个激动人心的时刻,因为我们来到了栈和队列的章节。
栈是一种特殊的线性表,只允许在一端进行插入和删除操作。进入数据插入和删除的一端叫作栈顶,另一端称为栈底。具有后进先出的特点。
压栈:数据的插入操作叫作进栈/入栈/压栈。
出栈:栈的删除操作叫作出栈,出栈也在栈顶。
栈的实现可以用数组或者线性表实现,相对而言,数组实现更优,因为数组在尾上插入的代价较小。
队列的表示和实现:
队列只允许在一端进行插入操作,在另一端进行删除操作。具有先进先出的特点。
入队列:进入插入操作的一端为队尾。
出队列:进行删除操作的一端为队头。
关于原地扩容和异地扩容:
异地扩容就是在新的地方开辟一个新的空间。
以上代码测试出如果是一样的,那么就是原地扩容,如果不一样,就是异地扩容。
此时地址是一样的。但是当我们把扩容的空间增大,地址不一样。
数据都在内存里,在堆上。
不会直接访问数据。在缓存,就叫做缓存命中。如果不在缓存,就叫作不命中,要把数据从内存加载到缓存,再访问。
顺序表和链表都可以用来实现栈,不过一般都使用顺序表,因为栈相当于是阉割版的顺序表,只用到了顺序表的尾插和尾删操作,顺序表的尾插和尾删不需要搬移元素效率非常高,故一般都是使用顺序表实现。
链式结构实现栈时,每次入栈相当于链表中头插一个节点,没有扩容一说。
实现静态的栈:
#define N 10
struct Stack
{
int a[N];
};
动态的栈:
Stack.h
#pragma once
#include<stdio.h>
#innclude<stdlib.h>
#include<stdbool.h>
typedef int STDataType;
struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST* pst);//pst指针都是在函数参数接收的,调用函数的时候传递&st就可以给pst指针赋值
void STDestory(ST* pst);
void STPush(ST* pst,STDataType x);
void STPop(ST* pst);
STDataType STTop(ST* pst);
bool STEmpty(ST* pst);
int STSize(ST* pst);
Stack.c
#include"Stack.h"
//初始化和销毁
void STInit(ST* pst)
{
assert(pst);
pst->a=NULL;
pst->top=0;// ps->top表示要插入位置的下标, 然后ps->top也表示当前栈里面有多少个元素
//0就表示当前插入的下标位置为0,栈里面的元素个数为0
// top指向栈顶数据//pst->top =-1;
pst->capcity=0;
}
void STDestory(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->capcity)
{
int newCapcity=pst->capcity==0?4:pst->capcity*2;
STDataType* tmp=realloc(pst->a,newCapcity*sizeof(STDataType));
//ptr是要调整的内存地址,size为调整后的大小。返回值为调整后内存的起始位置。
if(tmp==NULL)
{
perror("realloc fail");
return;
}
pst->a=tmp;
pst->capcity=newCapcity;
pst->a[pst->top]=x;
pst->top++;
}
void STPop(ST* pst)
{
assert(pst);
pst->top--;
}
STDataType STTop(ST* pst)//获取栈顶元素
{
assert(pst);
assert(pst->top>0);
return pst->a[pst->top-1];
}
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top==0;
}
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
int main()
{
ST s;
STInit(&s);
STPush(&s,1);
STPush(&s,2);
STPush(&s,3);
while(!STEmpty(&s))
{
printf("%d ",STTop(&s);
STPop(&s);
}
STDestory(&s);
}
我们应该知道,top应该指向栈顶,还是栈顶的下一个元素呢?top等于0是有数据还是没数据呢。所以当栈里面没有元素的时候,top应该指向-1。
那么我们的初始化可以改成这样:
void STInit(ST* pst)
{
assert(pst);
pst->a=NULL;
pst->top=0;//top指向栈顶数据的下一个位置
//top指向栈顶数据 pst->top=-1;
pst->capcity=0;
}
第一步:左括号入栈,第二步:出栈顶的左括号判断和右括号是否匹配,如果匹配,继续,如果不匹配,终止。
bool isValid(char* s) {
ST st;
STInit(&st);
while(*s)
{
if(*s=='('||*s=='['||*s=='{')//左括号入栈
{
STPush(st,*s);
}
else//右括号取栈顶的左括号尝试匹配
{
if(STEmpty(&st))
{
return false;
}
char top=STTop(&st);
STPop(&st);
if((top=='('&&*s!=')')
||(top=='{'&&*s!='}')
(top=='('&&*s!=')'))//不匹配
{
return false;
}
}
++s;
}
//如果栈不为空,说明左括号比右括号多,数量不匹配
bool ret=STEmpty(&st);
STDestory(&st);
return ret;
}