目录
1.栈
1.1概念和结构
一种比较特殊的线性表,在固定一段进行插入和删除操作,这一端称为栈顶,另一端称为栈顶。比较形象的形容的话,就是一个杯子,我们平整的放入一些饼干,放进去和拿出来都是通过杯口,而栈遵循的是后进先出原则,在这个例子中,就是我们后放进去的饼干,如果要拿饼干,也是最先拿出来的饼干。
我们将杯口称为栈顶,杯底称为栈底。
压栈就是将数据放入栈中,在上面例子中,就是把饼干平整放进去。
出栈就是把数据从栈中拿出来(对于栈来说就是删除数据),也就是把饼干从杯子中拿出来。(注意,是从杯口拿出来)
在实现上,我们更推荐数组,因为数组在尾插尾删上效率很高,栈顶就可以是数组的末尾。
链表也可以,但是效率比不过数组
1.2栈的实现
1.2.1栈的头文件
#pragma once #include<assert.h> #include<stdio.h> #include<stdlib.h> #include<stdbool.h> typedef int STDataType; //方便改数据类型 typedef struct Stack { int* a; int top; int capacity; }ST; //栈的结构,采用数组实现,top是栈顶元素的下一个坐标 //capacity是扩容,本质跟顺序表的扩容一样 void STInit(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);
1.2.2栈的具体实现
1.2.2.1初始化
void STInit(ST* pst) { assert(pst); pst->a = NULL; pst->capacity = 0; pst->top = 0; } 断言空指针 数组指针置空 数组大小和top都置为0
1.2.2.2栈的销毁
void STDestory(ST* pst) { assert(pst); free(pst->a); pst->a = NULL; pst->top=pst->capacity = 0; } 断言空指针 free掉数组空间 数组指针置空 top和capacity都设置为0
1.2.2.3入栈
void STPush(ST* pst, STDataType x) { if (pst->top == pst->capacity) { int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2; STDataType* tmp = realloc(pst->a, sizeof(STDataType) * newcapacity); if (tmp == NULL) { perror("realloc fail"); return; } pst->a = tmp; pst->capacity = newcapacity; } pst->a[pst->top] = x; pst->top++; } 因为栈只有尾插,扩容操作就不额外做个函数了,直接放在这里 如果top==capacity,因为top是栈顶元素的下一个下标,capacity是数组大小, 那这样就说明此时数组数据已经满了,需要扩容 接下来就是常规的扩容操作,定一个newcapacity,如果数组大小为空就赋值4,非0就是扩容2倍 动态申请数组空间,判断开辟是否失败 将开辟的空间地址赋给栈的数组,空间大小记得更新 接下来赋值,top是栈顶元素的下一个下标,所以直接top位置赋值即可 top记得++
1.2.2.4出栈
void STPop(ST* pst) { assert(pst); assert(pst->top > 0); pst->top--; } 断言空指针 断言此时栈里是否还有数据 数组的删除只能通过(不理他)也就是直接top--,让这个位置跟别的存放随机值的空间一个待遇
1.2.2.5获取栈顶元素
STDataType STTop(ST* pst) { assert(pst); assert(pst->top > 0); return pst->a[pst->top - 1]; } 因为要返回具体的值,所以返回类型是SLDataType 断言空指针 断言空栈 top是栈顶元素的下一个下标,所以直接返回top-1位置元素即可
1.2.2.6判断空
bool STEmpty(ST* pst) { assert(pst); return pst->top == 0; } 断言空指针 判断是否为空,只要确认top是不是0就行, 因为是返回bool类型,返回一个关系表达式就可以
1.2.2.7获取栈数据数量
int STSize(ST* pst) { assert(pst); return pst->top; } top是栈顶元素的下一个下标,而数组是从0开始的 那么top正好就是数组有效数据的个数 返回top即可
2.队列
2.1概念和结构
队列可以想象成一个中空的管子,我们从一段(队尾)放东西进去,再从另一端(队头)出来
队列遵循先进先出,放数据(放入队尾)称为入队,拿数据(把队头数据删除)称为出队
结构上我们采用链表,因为需要头删和尾插,链表适合频繁删改的,综合上考虑还是选择链表。
2.2队列实现
2.2.1队列头文件
#pragma once #include<stdio.h> #include<assert.h> #include<stdlib.h> #include<stdbool.h> typedef int QDataType; //方便改数据类型 typedef struct QueueNode { QDataType val;//数据 struct QueueNode* next;//链表下一个地址 }QNode; //队列的一个节点 typedef struct Queue { QNode* phead;//指向队列链表结构的头结点,也就是队头 QNode* ptail;//指向队列链表结构的尾节点,也就是队尾 int size;//队列大小 }Queue; //队列 //方便直接引用头结点和尾节点 //入队 void QueuePush(Queue* pq, QDataType x); //出队 void QueuePop(Queue* pq); //初始化 void QueueInit(Queue* pq); //队列销毁 void QueueDestrory(Queue* pq); //取队头数据 QDataType QueueFront(Queue* pq); //取队尾数据 QDataType QueueBack(Queue* pq); //确认队列是否为空 bool QueueEmpty(Queue* pq); //获取队列数据数量 int QueueSize(Queue* pq);
2.2.2队列具体实现
2.2.2.1尾插
void QueuePush(Queue* pq, QDataType x) { assert(pq); QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { perror("malloc fail"); return; } newnode->val = x; newnode->next = NULL; if (pq->ptail == NULL) { pq->ptail = pq->phead = newnode; } else { pq->ptail->next = newnode; pq->ptail = newnode; } pq->size++; } 断言空指针 开辟一个队列节点,判断开辟是否失败 给节点赋值,指针置空 判断当前队列是否是空队列,是的话,两个指针都先指向这个新节点 否则的话,让ptail的next先指向新节点(尾插),再让ptail指向新的尾节点 size记得++
2.2.2.2出队
void QueuePop(Queue* pq) { assert(pq); assert(pq->phead); QNode* del = pq->phead; pq->phead = pq->phead->next; free(del); del = NULL; if (pq->phead == NULL) { pq->ptail = NULL; } pq->size--; } 断言空指针和空队列 把队头也就是phead指向的节点赋给del指针 让phead指向第二个节点,让第二个节点成为新的队头 free掉del指向的空间,del指针记得置空 如果此时只有一个节点,删了之后就是空队列,那么ptail也置空,以免引用野指针 size记得--
2.2.2.3队列初始化
void QueueInit(Queue* pq) { assert(pq); pq->phead = pq->ptail = NULL; pq->size = 0; } 队列初始化 断言空指针 两个指针都置空 size也置0
2.2.2.4队列销毁
void QueueDestrory(Queue* pq) { assert(pq); QNode* cur = pq->phead; while (cur) { QNode* next = cur->next; free(cur); cur = next; } pq->phead = pq->ptail = NULL; pq->size = 0; } 队列销毁 断言空指针 参考链表的销毁,逐级销毁 记得把phead和ptail也都置空,size变0
2.2.2.5获取队头数据
QDataType QueueFront(Queue* pq) { assert(pq); assert(pq->phead); return pq->phead->val; } 断言空指针和空队列 返回phead指向的队头节点的val即可
2.2.2.6获取队尾数据
QDataType QueueBack(Queue* pq) { assert(pq); assert(pq->ptail); return pq->ptail->val; } 断言空指针和空队列 返回ptail指向的队列尾节点的val即可
2.2.2.7判断空
bool QueueEmpty(Queue* pq) { assert(pq); return pq->phead == NULL; } 断言空指针 因为bool类型,返回一个关系表达式即可
2.2.2.8队列数据数量
int QueueSize(Queue* pq) { assert(pq); return pq->size; } 断言空指针 返回size即可
3.例题
3.1有效的括号
#include<assert.h> #include<stdio.h> #include<stdlib.h> #include<stdbool.h> typedef char STDataType; typedef struct Stack { STDataType* a; int top; int capacity; }ST; void STInit(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); void STInit(ST* pst) { assert(pst); pst->a = NULL; pst->capacity = 0; pst->top = 0; } void STDestory(ST* pst) { assert(pst); free(pst->a); pst->a = NULL; pst->top=pst->capacity = 0; } void STPush(ST* pst, STDataType x) { if (pst->top == pst->capacity) { int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2; STDataType* tmp = realloc(pst->a, sizeof(STDataType) * newcapacity); if (tmp == NULL) { perror("realloc fail"); return; } pst->a = tmp; pst->capacity = newcapacity; } pst->a[pst->top] = x; pst->top++; } void STPop(ST* pst) { assert(pst); assert(pst->top > 0); 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; } //这里上面都是手搓的栈,我直接cv了 bool isValid(char* s) { ST S; STInit(&S); //创建栈,初始化 while(*s) { if(*s=='('||*s=='{'||*s=='[') { STPush(&S,*s); } //这题我们可以把左括号都先入栈 else { if(STEmpty(&S)) { return false; } //考虑左括号少于右括号的情况 char top=STTop(&S); STPop(&S); if(top!='{' && *s=='}' || top!='[' && *s==']' || top!='(' && *s==')') { return false; } } //遇到非左括号,直接出栈,左括号和非左括号对比,如果 不是直接false s++; //s记得++ } bool ret=STEmpty(&S); //考虑右括号少于左括号的情况 STDestory(&S); return ret; }
3.2用队列实现栈
#include<stdio.h> #include<assert.h> #include<stdlib.h> #include<stdbool.h> typedef int QDataType; typedef struct QueueNode { QDataType val; struct QueueNode* next; }QNode; typedef struct Queue { QNode* phead; QNode* ptail; int size; }Queue; void QueuePush(Queue* pq, QDataType x); void QueuePop(Queue* pq); void QueueInit(Queue* pq); void QueueDestrory(Queue* pq); QDataType QueueFront(Queue* pq); QDataType QueueBack(Queue* pq); bool QueueEmpty(Queue* pq); int QueueSize(Queue* pq); #define _CRT_SECURE_NO_WARNINGS 1 void QueuePush(Queue* pq, QDataType x) { assert(pq); QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { perror("malloc fail"); return; } newnode->val = x; newnode->next = NULL; if (pq->ptail == NULL) { pq->ptail = pq->phead = newnode; } else { pq->ptail->next = newnode; pq->ptail = newnode; } pq->size++; } void QueuePop(Queue* pq) { assert(pq); assert(pq->phead); QNode* del = pq->phead; pq->phead = pq->phead->next; free(del); del = NULL; if (pq->phead == NULL) { pq->ptail = NULL; } pq->size--; } void QueueInit(Queue* pq) { assert(pq); pq->phead = pq->ptail = NULL; pq->size = 0; } void QueueDestrory(Queue* pq) { assert(pq); QNode* cur = pq->phead; while (cur) { QNode* next = cur->next; free(cur); cur = next; } pq->phead = pq->ptail = NULL; pq->size = 0; } QDataType QueueFront(Queue* pq) { assert(pq); assert(pq->phead); return pq->phead->val; } QDataType QueueBack(Queue* pq) { assert(pq); assert(pq->ptail); return pq->ptail->val; } bool QueueEmpty(Queue* pq) { assert(pq); return pq->phead == NULL; } int QueueSize(Queue* pq) { assert(pq); return pq->size; } //上面都是队列的接口和结构,cv即可 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) { Queue*emptyq=&obj->q1; Queue*nonemptyq=&obj->q2; if(!QueueEmpty(&obj->q1)) { emptyq=&obj->q2; nonemptyq=&obj->q1; } while(QueueSize(nonemptyq)>1) { QueuePush(emptyq,QueueFront(nonemptyq)); QueuePop(nonemptyq); } int top=QueueFront(nonemptyq); QueuePop(nonemptyq); 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) { QueueDestrory(&obj->q1); QueueDestrory(&obj->q2); free(obj); }
3.3用栈实现队列
#include<assert.h> #include<stdio.h> #include<stdlib.h> #include<stdbool.h> typedef int STDataType; typedef struct Stack { int* a; int top; int capacity; }ST; void STInit(ST* pst) { assert(pst); pst->a = NULL; pst->capacity = 0; pst->top = 0; } void STDestory(ST* pst) { assert(pst); free(pst->a); pst->a = NULL; pst->top=pst->capacity = 0; } void STPush(ST* pst, STDataType x) { if (pst->top == pst->capacity) { int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2; STDataType* tmp = realloc(pst->a, sizeof(STDataType) * newcapacity); if (tmp == NULL) { perror("realloc fail"); return; } pst->a = tmp; pst->capacity = newcapacity; } pst->a[pst->top] = x; pst->top++; } void STPop(ST* pst) { assert(pst); assert(pst->top > 0); 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; } //上面都是栈的接口,cv即可 typedef struct { ST q1; ST q2; } MyQueue; //队列采用两个栈 //创建队列 //开辟队列空间,初始化两个栈 MyQueue* myQueueCreate() { MyQueue*mq=(MyQueue*)malloc(sizeof(MyQueue)); STInit(&mq->q1); STInit(&mq->q2); return mq; } //队列插入,都入栈到q1栈上 void myQueuePush(MyQueue* obj, int x) { assert(obj); STPush(&obj->q1,x); } //返回队头元素 int myQueuePeek(MyQueue* obj) { if(STEmpty(&obj->q2)) { while(!STEmpty(&obj->q1)) { STPush(&obj->q2,STTop(&obj->q1)); STPop(&obj->q1); } } return STTop(&obj->q2); } //在q2栈空的情况下,先把q1的数据出栈,入栈到q2,这样q2栈顶元素就是队头元素 //移除队头元素并返回队头元素 int myQueuePop(MyQueue* obj) { assert(obj); int ret=myQueuePeek(obj); STPop(&obj->q2); return ret; } //断言空指针 //直接调用上面写的返回队头元素函数,然后把q2栈顶元素删除即可 //判断队列是否为空,引用两个判断栈空接口即可 bool myQueueEmpty(MyQueue* obj) { assert(obj); return (STEmpty(&obj->q1) &&STEmpty(&obj->q2)); } //销毁队列,调用两个栈的销毁接口即可。 void myQueueFree(MyQueue* obj) { assert(obj); STDestory(&obj->q1); STDestory(&obj->q2); free(obj); }
3.4设计循坏队列
typedef struct { int *a; int front; int back; int k; } MyCircularQueue; //采用数组形式构建循环队列 //front和back是用来定位的,k是用来标记队列长度的 MyCircularQueue* myCircularQueueCreate(int k) { MyCircularQueue*obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue)); obj->a=(int*)malloc(sizeof(int)*(k+1)); obj->front=0; obj->back=0; obj->k=k; return obj; } //构造函数,开辟队列空间,开辟数组空间,front和back都置0,k直接赋值 bool myCircularQueueIsFull(MyCircularQueue* obj) { return (obj->back+1)%(obj->k+1)==obj->front; } //怎么判断是否满,back是下一个准备被赋值的位置, //我们front和back中间是有要有一个空间不存数据, //做到back+1==front就是满状态,那么back+1%一个k+1,最终的值会是0-k, //具体可以自己画下图,假如back是7,k是6,说明1-6下标都已被赋值,此时应该是满的 //back+1%7,就等于1,正好是front的值,从而能够判断满 bool myCircularQueueIsEmpty(MyCircularQueue* obj) { return (obj->front==obj->back); } //空,说明此时back和front相等。 bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) { if(myCircularQueueIsFull(obj)) { return false; } obj->a[obj->back]=value; obj->back++; obj->back%=(obj->k+1); return true; } //入队,判断是否满,给back位置赋值,记得把back%k+1,保持back不越界 bool myCircularQueueDeQueue(MyCircularQueue* obj) { if(myCircularQueueIsEmpty(obj)) { return false; } ++obj->front; obj->front%=(obj->k+1); return true; } //判断是否空,然后让front++即可,记得%,以免越界。 int myCircularQueueFront(MyCircularQueue* obj) { if(myCircularQueueIsEmpty(obj)) { return -1; } return obj->a[obj->front]; } //判断队列是否为空,然后把front下标数据返回即可 int myCircularQueueRear(MyCircularQueue* obj) { if(myCircularQueueIsEmpty(obj)) { return -1; } if(obj->back==0) { return obj->a[obj->k]; } else { return obj->a[obj->back-1]; } //return (obj->back-1 +obj->k+1)%(obj->k+1); } //先判断是否为空 //如果back=0,说明,此时1-k下标的空间都已经被使用,下标k就是队尾数据 //否则的话,因为back是有效数据的下一个位置,所以back的前一个就是队尾数据 void myCircularQueueFree(MyCircularQueue* obj) { free(obj->a); free(obj); } //释放,先释放数组空间,再释放队列空间