栈
今天再来看一种数据结构,叫做栈
1、概念及其结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
2、栈的实现
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构更优一点。因为数组在尾上插入数据的代价比较小
书归正传,我们先写一下栈的简单结构和它的功能
先写一个栈的初始化
当然少不了删除了
这里我们将top初始化为0,所以就是先插入数据然后top++
如果我们将top定义成了-1,那么在插入数据的时候就是top++,然后再插入数据
我们再写一个判断栈是否为空的功能
我们写了栈的插入那么就要写栈的删除
在写一个找出top位置的功能
好了,这下我们来看队列
队列
1、概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的特性
入队列:进行插入操作的的一端称为队尾
出队列:进行删除操作的一端称为队头
由于在队头出数据需要挪动,效率比较低,所以队列用链表来实现比较好一点,用最简单的单链表就行
先写好结构
2、队列的实现
由于队列是队尾入数据,队头出数据故我们需要两个指针分别指向队尾和队头
我们就用一个结构体来存储这两个指针
接下来就是老生常谈的初始化
还有删除整个队列
当然少不了插入数据
还有删除单个队列数据
接着是取队头数据和队尾数据
最后是判断是否为空和计算大小
栈和队列OJ
括号匹配问题
这道题就是让你查看给的括号到底能不能都匹配在一起,这道题我们用栈做最靠谱了。
思路是左括号入栈,右括号出栈,栈顶元素进行匹配
这里就是左括号入栈
然后是右括号出栈
也会有特殊例子,就像只有一个左括号,那么只入栈就结束了,这时我们就要加一个条件判断栈是否为空,如果栈为空,那么说明栈出完了,那就算正常结束,如果栈不为空那就是flase
还有一种情况,就是只给了一个右括号,那么就没有入栈,但是程序还会找栈的top,这时也会报错,那我们就要在写一个判断语句
最后这才算大功告成
typedef char STDataType;
typedef struct Stack
{
STDataType* a;
int capacity;
int top;//初始值为0,表示栈顶元素写一个位置的下标
}ST;
void StackInit(ST* ps);//初始化
void StackDestroy(ST* ps);//删除整个栈的数据
void StackPush(ST* ps,STDataType x);//插入数据
void StackPop(ST* ps);//删除栈的一个数据
STDataType StackTop(ST* ps);//找出top位置
bool StackEmpty(ST* ps);//判断栈是否为空
int StackSize(ST* ps);//栈的大小
//初始化
void StackInit(ST* ps)
{
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)
{
perror("malloc fail");
exit(-1);
}
ps->capacity = 4;
ps->top = 0;
}
//删除
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
//插入数据
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->capacity == ps->top)
{
STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * sizeof(STDataType) * 2);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity *= 2;
}
ps->a[ps->top] = x;
ps->top++;
}
//判断栈是否为空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == NULL;
}
//删除栈的一个数据
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
//找出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;
}
bool isValid(char * s){
ST st;
StackInit(&st);
while(*s)
{
if(*s=='['||*s=='{'||*s=='(')
{
StackPush(&st,*s);
++s;
}
else
{
if(StackEmpty(&st))
{
StackDestroy(&st);
return false;
}
char top=StackTop(&st);
StackPop(&st);
if((*s== ']' && top !='[')
||(*s=='}' && top !='{')
||(*s==')' && top !='('))
{
StackDestroy(&st);
return false;
}
else
{
++s;
}
}
}
bool ret = StackEmpty(&st);
return ret;
}
用队列实现栈
这道题是用队列实现栈,队列是先进先出,而栈是后进先出
这里push进来1234,如果是栈pop的话就是1 2 3 4依次出来,但队列恰好相反,是4 3 2 1
题目的条件是用两个队列来实现,我们就可以让有数据的那个队列先出数据,出到size剩了一个了就刚好了pop队尾的数据
大思路就是:保持一个队列存数据,一个队列为空,出数据时,倒一下
首先,先定义两个队列,来实现上面所说的功能,也可以定义成指针,不过后面就还需要用malloc
题目第一个要求的功能就是Creat函数,意思就是没有结构体,这就需要我们创建加初始化
这里我们定义一个obj来指向malloc
然后把q1和q2传给初始化函数,最后返回obj
下面就是push函数,按上面的思路,就是保持一个为空,一个不为空,哪个队列不为空就向那个队列插,或者都为空,那就随便插了
接着是pop函数,这里pop函数的要求不仅是删除一个数据,还要返回栈顶的元素,这里我们的思路是找到为空的队列和不为空的,然后将非空的队列里的数据转移到空的队列中,最后再返回栈顶元素
下一个函数是求栈顶元素的,倒也能用队列来回倒,但我们刚好有个求队尾元素的函数
我们直接调用,这个省了不少事
最后两个一个是判空,另一个是释放队列
值得一提的是,我们如果只释放obj,创造出来的两个q1和q2就并没有被删除,这时就造成了内存泄漏
用栈实现队列
思路:
我们来看看上一道题的兄弟题目,用栈实现队列
栈是后进先出,而队列是先进先出
删除的思路也是倒一下,把有数据的栈里的数据全部倒到空栈里,剩一个数据的时候就删除,这样就把栈底的数据删了
当我们入数据的时候,因为现在有数据的栈里是234,我们入一个5,顺序就乱了,要想按顺序入数据就还得倒到另一个栈里再入。
但是这样太麻烦了,我们换一种思路,将原先有数据的栈就叫做出栈,为空的栈就叫做入栈,这样也是按顺序存储,出栈时也是哪顺序出,直到右边为空,再想出的时候就再倒。
typedef int STDatatype;
typedef struct Stack
{
STDatatype* a;
int capacity;
int top; // 初始为0,表示栈顶位置下一个位置下标
}ST;
void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps, STDatatype x);
void StackPop(ST* ps);
STDatatype StackTop(ST* ps);
bool StackEmpty(ST* ps);
int StackSize(ST* ps);
void StackInit(ST* ps)
{
assert(ps);
//ps->a = NULL;
//ps->top = 0;
//ps->capacity = 0;
ps->a = (STDatatype*)malloc(sizeof(STDatatype)*4);
if (ps->a == NULL)
{
perror("malloc fail");
exit(-1);
}
ps->top = 0;
ps->capacity = 4;
}
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
void StackPush(ST* ps, STDatatype x)
{
assert(ps);
// 扩容
if (ps->top == ps->capacity)
{
STDatatype* tmp = (STDatatype*)realloc(ps->a, ps->capacity * 2 * sizeof(STDatatype));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity *= 2;
}
ps->a[ps->top] = x;
ps->top++;
}
// 20:20继续
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
STDatatype StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
typedef struct {
ST popset;
ST pushset;
} MyQueue;
bool myQueueEmpty(MyQueue* obj);
MyQueue* myQueueCreate() {
MyQueue*pq=(MyQueue*)malloc(sizeof(MyQueue));
StackInit(&pq->popset);
StackInit(&pq->pushset);
return pq;
}
void myQueuePush(MyQueue* obj, int x) {
assert(obj);
StackPush(&obj->pushset,x);
}
int myQueuePop(MyQueue* obj) {
assert(obj);
assert(!myQueueEmpty(obj));
int k=myQueuePeek(obj);
StackPop(&obj->popset);
return k;
}
int myQueuePeek(MyQueue* obj) {
assert(obj);
assert(!myQueueEmpty(obj));
if(StackEmpty(&obj->popset))
{
while(!StackEmpty(&obj->pushset))
{
StackPush(&obj->popset,StackTop(&obj->pushset));
StackPop(&obj->pushset);
}
}
return StackTop(&obj->popset);
}
bool myQueueEmpty(MyQueue* obj) {
assert(obj);
return StackEmpty(&obj->popset)&&StackEmpty(&obj->pushset);
}
void myQueueFree(MyQueue* obj) {
assert(obj);
StackDestroy(&obj->popset);
StackDestroy(&obj->pushset);
free(obj);
}
设计循环队列
来看看这个循环队列
这里我们经过权衡利弊,最终选择使用数组实现
至于怎么判空呢,当rear等于front的时候队列就是空的,判满则是用(rear+1)%(k+1)==front判断
这里我们先写一下前面的代码,先malloc出一个结构体来,在malloc出数组,注意这里要多开一个空间,方便判断
然后再顺便把判空和判满写了
下面来看看插入
我们首先进行一个判满,当不满时,rear先插入,再++,但是会有特殊情况
这种情况下rear++就会越界,我们想让他再从0开始,就可以写一个if语句,给他赋值为0。或者是%=一下k+1
然后是出数据,正常情况就是front++,等走到最后一个的时候就%=一下(k+1)
接着是取头,而且题目有规定,如果队列为空要返回-1
然后是取头,也是规定为空返回-1,这个也有特殊情况
最后就可以这样写
当然,还有一种更优思路,不过一般人不太能想到
如果画图,说不定就能想到
最后是释放,也是两层释放
typedef struct {
int*a;//数组
int front;//头
int rear;//尾
int k;//数组的大小
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
//多开一个解决判满的问题
obj->a=(int*)malloc(sizeof(int)*(k+1));
obj->front=obj->rear=0;
//这里的k是放满的时候能存储的最大数据个数,空间大小应该是k+1
obj->k=k;
return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
assert(obj);
return obj->rear==obj->front;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
assert(obj);
return ((obj->rear+1)%(obj->k+1))==obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
assert(obj);
if(myCircularQueueIsFull(obj))
return false;
obj->a[obj->rear++]=value;
obj->rear%=(obj->k+1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return false;
obj->front++;
obj->front%=(obj->k+1);
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj)){
return -1;
}
return obj->a[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->a[(obj->rear+obj->k)%(obj->k+1)];
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}
/**
* 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);
*/