一、栈
1.栈的概念及结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。
数组栈
链式栈
对于第一种头作为栈顶,插入删除数据为头插和头删,这样很容易找到头进行操作,如果用头做栈顶,头插头删,就可以设计成单链表。
第二种尾作为栈顶,插入删除数据为尾插和尾删,这种结构设计成双向链表比较好,否则删除数据效率低。
其次数组栈缓存利用率高,因为数组是一段连续的空间,内存访问数组的第一个数的时候,后面的数字已经预缓存在cache中,而链式栈的空间不是连续的,是用链表将不连续的空间连接起来,在找到链表的第一个数字后,如果找不到第二个空间,假设不命中,一次加载20byte到缓存(具体加载多大取决硬件体系)。链式栈的缓存命中率低。
对于链式栈唯一的好处就是可以随时开辟空间,不浪费空间,数组栈只能在空间不够的时候开辟一定的空间,存在空间的浪费,但是链式栈仅限于这个好处。除非要在一个不能浪费空间的地方使用,比如嵌入式,我们用链式栈。
所以对于栈,两种都可以,非要选一种,我们最好用数组栈。2.栈的实现
对于各个函数的声明和头文件的包括,我们写在Stack.h中#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;//数组指针
int top;//栈顶
int capacity;//最大容量
}ST;
//初始化数组
void StackInit(ST ps);
//销毁数组
void StackDestroy(ST ps);
void StackPush(ST ps, STDataType x);
void STackPop(ST ps);
//找到栈顶元素
STDataType StackTop(ST ps);
//栈的大小
int StackSize(ST ps);
//判断栈是否为空
bool StackEmpty(ST* ps);//引用头文件
#### 栈的初始化
将指针置空,栈中最大个数为0,栈的下标为0或-1
```c
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;//指向栈顶的下一个,指向栈顶后,+1指向下一个
//ps->top=-1;//指向栈顶,先+1再指向栈顶
ps->capacity = 0;
}
插入数据
栈的插入数据只有一种,在栈顶插入,将数据插入栈顶,top下标也要增加一个,当插入到栈满的时候,我们需要扩容。
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = realloc(ps->a, sizeof(STDataType)*newcapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
销毁数据
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);//释放置空
ps->a = NULL;
ps->capacity = ps->top = 0;//置为0
}
删除数据
删除非常简单,只需要将top减减,但在test.c中测试,删除数据最后top会小于0,变成-1,-2...,所以我们直接断言一下,在top大于0时,才能删除
void STackPop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
取栈顶数据
因为前面初始化栈顶top为0,最后指向栈顶元素的下一个,所以返回top-1.当栈为空时,top-1为野指针,返回随机值,所以断言一下top>0
STDataType StackTop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
return(ps->a[ps->top-1]);
}
栈的大小
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
判断栈是否为空
bool StackEmpty(ST* ps)
{
/*if (ps->top == 0)//查看下标top是否为0
{
return true;
}
else
{
return false;
}*/
return ps->top == 0;//如果为0返回ture,其他情况返回false
}
栈的实现时非常简单的
那么如何实现栈的遍历打印呢
void TestStack2()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));
StackPop(&st);
}
StackDestroy(&st);
}
每次打印栈顶,并删除栈顶即可,最后栈为空跳出循环,打印结束。
3.OJ练习题--有效的括号
我们来做一道OJ题小试牛刀
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-parentheses
这道题我们运用上面写好的接口,因为用的是C语言来写,不像C++有STL库刷起来那么方便,需要自己实现接口,这是C刷题的一个弊端。
思路:当遇到左括号入栈,当遇到右括号左括号出栈跟右括号匹配示例5中,遇到左括号入栈,当遇到有括号的时候栈顶的左括号出栈与之匹配,匹配上了,指针向下走,这时栈顶的第一个左括号已经被删除,当遇到第二个右括号时,栈里的左括号与之匹配,匹配上了,指针向下走,当指针指向空时,返回真,其他情况返回false.
返回数据的时候一定要记得销毁栈,避免内存泄漏,因为这个栈是我们realloc出来的。
当遇到只有一个左括号的时候,左括号入栈,但是指针在向下走的时候,直接跳出循环,得到返回值,无法得到true结果,这时我们要判断是否是空,如果是空返回true,否则为false。
当只有一个右括号的时候,栈中没有元素,栈为空,取不到栈顶,我们在上面断言了栈不能为空,所以要判断一下栈是否为空,然后返回false,这个详情看下面代码。
typedef char STDataType;
typedef struct Stack
{
STDataType* a;//数组指针
int top;//栈顶
int capacity;//最大容量
}ST;
//初始化数组
void StackInit(ST* ps);
//销毁数组
void StackDestroy(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
//找到栈顶元素
STDataType StackTop(ST* ps);
//栈的大小
int StackSize(ST* ps);
//判断栈是否为空
bool StackEmpty(ST* ps);//引用头文件
void StackInit(ST* ps)
{
assert(ps);
ps->a=NULL;
ps->top=ps->capacity=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->top==ps->capacity)
{
int newcapacity=ps->capacity==0?4:ps->capacity*2;
STDataType* tmp=(STDataType*)realloc(ps->a,sizeof(STDataType)*newcapacity);
if(tmp==NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a=tmp;
ps->capacity=newcapacity;
}
ps->a[ps->top]=x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(ps->top>0);
ps->top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
assert(ps->top>0);
return ps->a[ps->top-1];
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
bool StackEmpty(ST* ps)
{
return ps->top==0;
}
bool isValid(char * s){
ST st;
StackInit(&st);
while(*s)
{
if(*s=='(' || *s=='[' || *s=='{')
{
//入栈
StackPush(&st,*s);
++s;
}
else
{
//遇到右括号,但是栈里没有数据,没有左括号了,返回false
if(StackEmpty(&st))
{
StackDestroy(&st);
return false;
}
STDataType top=StackTop(&st);
StackPop(&st);
if((*s==')' && top !='(')||
(*s==']' && top !='[')||
(*s=='}' && top !='{'))
{
StackDestroy(&st);
return false;
}
else
{
++s;
}
}
}
//如果栈不是空,栈中还有左括号未出,没有匹配,返回的时false
// bool ret=StackEmpty(&st);
// return ret;
if(StackEmpty(&st))
{
return true;
}
else
{
return false;
}
StackDestroy(&st);
}
二、队列
1.队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
2.队列的实现
队列里面我们定义一个指针
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QueueNode;
因为队列是先进先出,存放数据为尾插,删除数据为头删,所以为了方便,我们定义两个指针,一个指向头一个指向尾,然后放在一个结构体中,这样我们测试的时候可以不用二级指针,直接通过结构体来改变里面的数据。如果不定义这样的一个结构体,通过二级指针传入指针地址,然后通过一级指针找到数据来改变数据。
typedef struct QueueList
{
QueueNode* head;
QueueNode* tail;
}QList;
栈的初始化和销毁
void QueueInit(QList* ps)
{
assert(ps);
ps->head = NULL;
ps->tail = NULL;
}
void QueueDestroy(QList* ps)
{
assert(ps);
QueueNode* cur = ps->head;//定义指针遍历链表
QueueNode* next = cur->next;//存放当前指针的下一个
while (cur)
{
free(cur);
cur = next;
}
ps->head = ps->tail = NULL;
}
判断队列是否为空
bool QueueEmpty(QList* ps)
{
/*if (ps->head == NULL)
{
return true;
}
else
{
return false;
}*/
return ps->head == NULL;
}
尾插和头删
只实现尾插和头删是由于队列的性质写出的
**尾插**
void QueuePush(QList* ps, QDataType x)
{
assert(ps);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
newnode->data = x;
newnode->next = NULL;
if (ps->tail == NULL)
{
ps->tail = ps->head = newnode;
}
else
{
ps->tail->next = newnode;
ps->tail = newnode;
}
}
头删
在头删的同时,头删到NULL,tail指针并没有被释放,它还指向最后一个节点,head释放掉之后,tail成为野指针,返回随机值。所以head释放掉变为NULL的同时,也将tail置为空
void QueuePop(QList* ps)
{
assert(ps);//判断结构体不为空
assert(!QueueEmpty(ps));//判断队列不为空
QueueNode* next = ps->head->next;
free(ps->head);
ps->head = next;
if (ps->head == NULL)
{
ps->tail = NULL;
}
}
返回队尾和队头数据
QDataType QueueFront(QList* ps)
{
assert(ps);
assert(!QueueEmpty(ps));
return ps->head->data;
}
QDataType QueueBack(QList* ps)
{
assert(ps);
assert(!QueueEmpty(ps));
return ps->tail->data;
}
计算队列大小
int QueueSize(QList* ps)
{
assert(ps);
QueueNode* cur = ps->head;
int count = 0;
while (cur)
{
++count;
cur = cur->next;
}
return count;
}
测试一下
test.c
void TestQueue1()
{
QList q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
while (!QueueEmpty(&q))
{
QDataType front = QueueFront(&q);
printf("%d ", front);
QueuePop(&q);
}
printf("\n");
}
int main()
{
TestQueue1();
return 0;
}
队列OJ题
让我们来做几道OJ题
1.用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回false 。
OJ链接
思路:这道题用两个队列实现栈,将数据放进一个队列中,再将数据删除导入到另一个队列,直到删除到最后一个数字,直接将数字删除,实现后进先出
这道题用C语言,所以需要自己实现一些队列接口。前面我们写好直接用
这道题的函数接口中,将两个队列放在一个结构体中,myStackCreate中调用接口初始化,返回结构体指针,与我们上面写的有所区别,我们上面是直接在test.c中创建一个结构体,把结构体的指针传过去。
typedef struct {
} MyStack;
MyStack* myStackCreate() {
}
![图片.png](https://s2.51cto.com/images/20211105/1636111112941209.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
两个地方要区分开来。
但是在我们创建结构体返回指针时,我们不能这样创建
```c
MyStack* myStackCreate() {
Mystack st;
return &st;
}
这是我们在栈里面定义的局部变量,除了作用域就会被销毁,我可以定义全局变量,但是会出问题,所以我们用malloc在堆里面定义,这样出了作用域也不会被销毁。
typedef struct {
QList q1;
QList q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* st=(MyStack*)malloc(sizeof(MyStack));
QueueInit(&st->q1);
QueueInit(&st->q2);
return st;
}
在释放函数中
```c
void myStackFree(MyStack* obj) {
}
因为我们的结构是这样的
需要先释放两个队列,再释放队列的结构体
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
}
实现代码
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QueueNode;
typedef struct QueueList
{
QueueNode* head;
QueueNode* tail;
}QList;
void QueueInit(QList* ps);
void QueueDestroy(QList* ps);
void QueuePush(QList* ps, QDataType x);
void QueuePop(QList* ps);
QDataType QueueFront(QList* ps);
QDataType QueueBack(QList* ps);
int QueueSize(QList* ps);
bool QueueEmpty(QList* ps);
void QueueInit(QList* ps)
{
assert(ps);
ps->head = NULL;
ps->tail = NULL;
}
void QueueDestroy(QList* ps)
{
assert(ps);
QueueNode* cur = ps->head;
while (cur)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
ps->head = ps->tail = NULL;
}
void QueuePush(QList* ps, QDataType x)
{
assert(ps);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
newnode->data = x;
newnode->next = NULL;
if (ps->tail == NULL)
{
ps->tail = ps->head = newnode;
}
else
{
ps->tail->next = newnode;
ps->tail = newnode;
}
}
void QueuePop(QList* ps)
{
assert(ps);//判断结构体不为空
assert(!QueueEmpty(ps));//判断队列不为空
QueueNode* next = ps->head->next;
free(ps->head);
ps->head = next;
if (ps->head == NULL)
{
ps->tail = NULL;
}
}
QDataType QueueFront(QList* ps)
{
assert(ps);
assert(!QueueEmpty(ps));
return ps->head->data;
}
QDataType QueueBack(QList* ps)
{
assert(ps);
assert(!QueueEmpty(ps));
return ps->tail->data;
}
int QueueSize(QList* ps)
{
assert(ps);
QueueNode* cur = ps->head;
int count = 0;
while (cur)
{
++count;
cur = cur->next;
}
return count;
}
bool QueueEmpty(QList* ps)
{
assert(ps);
return ps->head == NULL;
}
typedef struct {
QList q1;
QList q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* st=(MyStack*)malloc(sizeof(MyStack));
QueueInit(&st->q1);
QueueInit(&st->q2);
return st;
}
//如果队列不为空,则放数据
void myStackPush(MyStack* obj, int x) {
if(!QueueEmpty(&obj->q1))
{
QueuePush(&obj->q1,x);
}
else
{
QueuePush(&obj->q2,x);
}
}
//当不为空的队列数据个数大于1,将数据导入空队列,并删除
int myStackPop(MyStack* obj) {
QList* emptyQ=&obj->q1;
QList* noneemptyQ=&obj->q2;
if(!QueueEmpty(&obj->q1))
{
emptyQ=&obj->q2;
noneemptyQ=&obj->q1;
}
while(QueueSize(noneemptyQ)>1)
{
QueuePush(emptyQ,QueueFront(noneemptyQ));//放入队列头的数据
QueuePop(noneemptyQ);
}
QDataType top=QueueFront(noneemptyQ);
QueuePop(noneemptyQ);
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);
}
3.用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
int StackSize(ST* ps);
bool StackEmpty(ST* ps);
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = realloc(ps->a, sizeof(STDataType)*newcapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
return(ps->a[ps->top-1]);
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
bool StackEmpty(ST* ps)
{
return ps->top == 0;
}
typedef struct {
ST pushST;
ST popST;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* q=(MyQueue*)malloc(sizeof(MyQueue));
StackInit(&q->pushST);
StackInit(&q->popST);
return q;
}
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->pushST,x);
}
//当popST不为空,如果pushST不为空,将数据导入popST再进行删除
int myQueuePop(MyQueue* obj) {
if(StackEmpty(&obj->popST))
{
while(!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST,StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
STDataType top=StackTop(&obj->popST);
StackPop(&obj->popST);
return top;
}
int myQueuePeek(MyQueue* obj) {
if(StackEmpty(&obj->popST))
{
while(!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST,StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
return StackTop(&obj->popST);
}
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->popST)&&StackEmpty(&obj->pushST);
}
void myQueueFree(MyQueue* obj) {
StackDestroy(&obj->pushST);
StackDestroy(&obj->popST);
free(obj);
}
3.设计循环队列
typedef struct {
int* a;
int front;
int tail;
int k;
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* cq=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
cq->a=(int*)malloc(sizeof(int)*(k+1));
cq->tail=cq->front=0;
cq->k=k;
return cq;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
//满了不能添加,返回flase
if(myCircularQueueIsFull(obj))
return false;
obj->a[obj->tail]=value;
++obj->tail;
obj->tail%=(obj->k+1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
//空了不能删除,返回false
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;
if(obj->tail%(obj->k+1)==0)
{
return obj->a[obj->k];
}
else
{
return obj->a[obj->tail-1];
}
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front==obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->tail+1)%(obj->k+1)==obj->front;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}