🔥个人主页:大白的编程日记
🔥专栏:数据结构
哈喽,各位小伙伴大家好!今天咱们就正式开始学习数据结构了。我们今天要学习的数据结构分别是栈和队列。话不多说,咱们进入正题!向大厂冲锋!
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
栈是一种遵循后进先出的结构。类似生活中我们生活中的弹夹,羽毛球桶等等。
入栈
入栈就是往栈顶增添数据
出栈
出栈就是在栈顶删除元素
我们可以用数组实现栈用下标控制栈顶元素的入栈和出栈
那这三个我们改选择那里一个呢?
首先我们可以先排除双向链表,因为它单链表还多了一个指针,多浪费了空间而且还要多维护一个指针。那单链表和数组我们选哪一个呢?其实都差不多。顺序表有扩容的问题。但是顺序表的缓存利用率高(文章有解释)。所以我们就选择数组吧。
typedef int STDataType; typedef struct Stack { STDataType* a; int top; int capacity; }ST;
void STInit(ST* pst) { assert(pst); pst->a = NULL; pst->capacity = pst->top = 0;//指向栈顶元素的下一个 }
一种是初始化为-1,代表top指向栈顶元素。为什么要给-1呢?
因为如果给0的话,当栈为空时,0既能表示栈为空也能代表栈有一个元素,下标为0。所以初始化要给-1。
第二种就是初始化给0,代表top指向栈顶元素的下一个位置。
栈的销毁我们先free销毁数组,然后再给数组指针给空。
top和capacity都给0表示栈为空。
void STDestroy(ST* pst) { assert(pst); free(pst->a); pst->a = NULL; pst->capacity = pst->top = 0; }
void STPush(ST* pst, STDataType x) { assert(pst); if (pst->capacity == pst->top)//栈满了 { int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;//未开空间就给4个空间,否则就在原来的空间扩容两倍 STDataType* tmp = (STDataType*)realloc(pst->a,sizeof(STDataType)*newcapacity); if (tmp == NULL) { perror("realloc fail~"); return; } pst->a = tmp; pst->capacity = newcapacity; } pst->a[pst->top++] = x;//赋值 top++ }
出栈我们只需要控制top,让top–即可。
void STPop(ST* pst) { assert(pst); assert(pst->top > 0); pst->top--; }
因为我们top是指向栈顶元素的下一个,所以我们返回下标为top-1的元素
STDataType STTop(ST* pst) { assert(pst); assert(pst->top > 0); return pst->a[pst->top-1]; }
top等于0时就是空。
bool STEmpty(ST* pst) { assert(pst); return 0 == pst->top; }
int STSize(ST* pst) { assert(pst); return pst->top; }
int main() { ST s; STInit(&s); STPush(&s, 1); STPush(&s, 2); STPush(&s, 3); STPush(&s, 4); while (!STEmpty(&s)) { printf("%d ", STTop(&s)); STPop(&s); } STDestroy(&s); return 0; }
注意栈有可能边入边出,这时的输出结果顺序就不是与与输入顺序相反了
int main() { ST s; STInit(&s); STPush(&s, 1); STPush(&s, 2); printf("%d ", STTop(&s)); STPop(&s); STPush(&s, 3); STPush(&s, 4); while (!STEmpty(&s)) { printf("%d ", STTop(&s)); STPop(&s); } STDestroy(&s); return 0; }
-题目
有效的括号
思路分析
让左括号入栈,右括号与左括号匹配。
代码实现
typedef char STDataType; typedef struct Stack { STDataType* a; int top; int capacity; }ST; void STInit(ST* pst) { assert(pst); pst->a = NULL; pst->capacity = pst->top = 0; } void STDestroy(ST* pst) { assert(pst); free(pst->a); pst->a = NULL; pst->capacity = pst->top = 0; } void STPush(ST* pst, STDataType x) { assert(pst); if (pst->capacity == pst->top) { int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity; STDataType* tmp = (STDataType*)realloc(pst->a,sizeof(STDataType)*newcapacity); if (tmp == NULL) { perror("realloc fail~"); return; } pst->a = tmp; pst->capacity = newcapacity; } pst->a[pst->top++] = x; } 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 0 == pst->top; } int STSize(ST* pst) { assert(pst); return pst->top; } bool isValid(char* s) { ST t; STInit(&t); while(*s) { if(*s=='('||*s=='['||*s=='{') { STPush(&t,*s);//左括号入栈 } else { if(STEmpty(&t))//没有左括号匹配 { return false; } char tmp=STTop(&t);//获取栈顶元素匹配 STPop(&t); if(*s==')'&&tmp!='(' ||*s=='}'&&tmp!='{' ||*s==']'&&tmp!='[')//匹配 { return false; } } s++; } bool ret=STEmpty(&t);//判空 STDestroy(&t); return ret; }
typedef int STDataType; typedef struct Stack { STDataType* a; int top; int capacity; }ST; void STInit(ST* pst) { assert(pst); pst->a = NULL; pst->capacity = pst->top = 0; } void STDestroy(ST* pst) { assert(pst); free(pst->a); pst->a = NULL; pst->capacity = pst->top = 0; } void STPush(ST* pst, STDataType x) { assert(pst); if (pst->capacity == pst->top) { int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity; STDataType* tmp = (STDataType*)realloc(pst->a,sizeof(STDataType)*newcapacity); if (tmp == NULL) { perror("realloc fail~"); return; } pst->a = tmp; pst->capacity = newcapacity; } pst->a[pst->top++] = x; } 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 0 == pst->top; } int STSize(ST* pst) { assert(pst); return pst->top; } typedef struct { ST pushst; ST popst; } MyQueue; MyQueue* myQueueCreate() { MyQueue* pq=malloc(sizeof(MyQueue)); STInit(&pq->pushst); STInit(&pq->popst); return pq; } void myQueuePush(MyQueue* obj, int x) { STPush(&obj->pushst,x); } int myQueuePeek(MyQueue* obj) { if(STEmpty(&obj->popst)) { while(!STEmpty(&obj->pushst)) { int top=STTop(&obj->pushst); STPush(&obj->popst,top); STPop(&obj->pushst); } } int top=STTop(&obj->popst); return top; } int myQueuePop(MyQueue* obj) { int ret=myQueuePeek(obj); STPop(&obj->popst); return ret; } bool myQueueEmpty(MyQueue* obj) { return STEmpty(&obj->pushst)&&STEmpty(&obj->popst); } void myQueueFree(MyQueue* obj) { STDestroy(&obj->pushst); STDestroy(&obj->popst); free(obj); obj=NULL; }
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
与栈相反,队列遵循先进先出的原则。只能在队尾入数据,队头出数据。
顺序表
顺序表不好实现队列。因为队列是队头出数据,顺序表头删需要挪动数据。
双向链表
双向链表其实实现啥都好。但是双向链表多开一个指针,浪费内存。还要多维护一个指针。
单链表
单链表实现队列非常合适。因为队列在队尾入数据,队头出数据。单链表头删和尾删都不需要上一个节点。
所以我们用单链表实现。
typedef struct QueueNode { struct QueueNode* next; QDataType val; }QNode; typedef struct Queue { QNode* phead; QNode* ptail; int size; }Queue;
void QueueInit(Queue* pq) { assert(pq); pq->phead = pq->ptail = NULL; pq->size = 0; }
我们创建一个cur指针指向头节点,然后遍历销毁即可。注意要先保存下一个节点在销毁当前节点,然后移动cur即可。最后让头指针尾指针指向空。size为0即可。
void QueueDestroy(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; }
void QueuePush(Queue* pq, QDataType x) { assert(pq); QNode* node = (QNode*)malloc(sizeof(QNode)); if (node == NULL) { perror("malloc fail"); return; } node->next = NULL; node->val = x; if (pq->phead == NULL)//没有节点 { pq->phead = pq->ptail = node; } else//至少有一个节点 { pq->ptail->next = node; pq->ptail = node; } pq->size++; }
-获取队头元素
我们先断言一下判断队列是否为空,然后返回队头节点元素的值。
QDataType QueueFron(Queue* pq) { assert(pq); assert(pq->size != 0); return pq->phead->val; }
QDataType QueueBack(Queue* pq) { assert(pq); assert(pq->size != 0); return pq->ptail->val; }
void QueuePop(Queue* pq) { assert(pq); assert(pq->size != 0); if (pq->phead == pq->ptail)//只有一个节点 { free(pq->phead); pq->phead = pq->ptail = NULL; pq->size--; } else { QNode* next = pq->phead->next; free(pq->phead); pq->phead = next; pq->size--; } }
判断size是否为0即可
bool QueueEmpty(Queue* pq) { assert(pq); return pq->size == 0; }
因为我们前面用size记录了个数,直接返回size即可。
防止遍历找.实现O(1).
int QueueSize(Queue* pq) { assert(pq); return pq->size; }
队列的遍历就是获取队头元素,然后删除队头元素直到队列为空。
int main() { Queue q; QueueInit(&q); QueuePush(&q, 1); QueuePush(&q, 2); QueuePush(&q, 3); QueuePush(&q, 4); while (q.size) { printf("%d ", QueueFron(&q)); QueuePop(&q); } QueueDestroy(&q); return 0; }
注意不论是否边入边出。队列输出的顺序都与入队列顺序一致。
int main() { Queue q; QueueInit(&q); QueuePush(&q, 1); QueuePush(&q, 2); printf("%d ", QueueFron(&q)); QueuePop(&q); printf("%d ", QueueFron(&q)); QueuePop(&q); QueuePush(&q, 3); QueuePush(&q, 4); while (q.size) { printf("%d ", QueueFron(&q)); QueuePop(&q); } QueueDestroy(&q); return 0; }
题目
用队列实现栈
思路分析
我们保持一个队列有数据,一个队列没数据。
出栈时,往空队列导入数据即可拿到栈顶元素。
代码实现
typedef int QDataType; typedef struct QueueNode { struct QueueNode* next; QDataType val; }QNode; typedef struct Queue { QNode* phead; QNode* ptail; int size; }Queue; void QueueInit(Queue* pq) { assert(pq); pq->phead = pq->ptail = NULL; pq->size = 0; } void QueueDestroy(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; } void QueuePush(Queue* pq, QDataType x) { assert(pq); QNode* node = (QNode*)malloc(sizeof(QNode)); if (node == NULL) { perror("malloc fail"); return; } node->next = NULL; node->val = x; if (pq->phead == NULL) { pq->phead = pq->ptail = node; } else { pq->ptail->next = node; pq->ptail = node; } pq->size++; } QDataType QueueFron(Queue* pq) { assert(pq); assert(pq->size != 0); return pq->phead->val; } QDataType QueueBack(Queue* pq) { assert(pq); assert(pq->size != 0); return pq->ptail->val; } bool QueueEmpty(Queue* pq) { assert(pq); return pq->size == 0; } void QueuePop(Queue* pq) { assert(pq); assert(pq->size != 0); if (pq->phead == pq->ptail) { free(pq->phead); pq->phead = pq->ptail = NULL; pq->size--; } else { QNode* next = pq->phead->next; free(pq->phead); pq->phead = next; pq->size--; } } //统计个数 int QueueSize(Queue* pq) { assert(pq); return pq->size; } typedef struct { Queue q1; Queue q2; } MyStack; MyStack* myStackCreate() { MyStack* q=(MyStack*)malloc(sizeof(MyStack)); QueueInit(&(q ->q1)); QueueInit(&(q->q2)); return q; } void myStackPush(MyStack* obj, int x) { if(!QueueEmpty(&obj->q1))//往不为空的队列入数据 { QueuePush(&obj->q1,x); } else { QueuePush(&obj->q2,x); } } int myStackPop(MyStack* obj) { Queue* empty=&obj->q1; Queue* noempty=&obj->q2; if(!QueueEmpty(&obj->q1)) { noempty=&obj->q1; empty=&obj->q2; }//假设法 while(QueueSize(noempty)>1)//数据入队列直到剩下一个数据 { QueuePush(empty,QueueFron(noempty)); QueuePop(noempty); } int top=QueueFron(noempty); QueuePop(noempty); 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);//销毁队列1 QueueDestroy(&obj->q2);//销毁队列2 free(obj);//销毁结构体 obj=NULL; }
设计循环队列
主要就是回绕的问题和假溢出的问题
typedef struct { int* a; int head; int tail; int k; } MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) { MyCircularQueue* pq=(MyCircularQueue*)malloc(sizeof(MyCircularQueue)); pq->a=(int*)malloc(sizeof(int)*(k+1)); pq->head=0; pq->tail=0; pq->k=k; return pq; }
bool myCircularQueueIsEmpty(MyCircularQueue* obj) { return obj->head==obj->tail; }
当(tail+1)%(k+1)==head时为满。
%(k+1)是为了解决回绕的问题。
bool myCircularQueueIsFull(MyCircularQueue* obj) { return (obj->tail+1)%(obj->k+1)==obj->head; }
先判断队列是否为满。如果不满就直接在tail的位置插入,然后tail+1继续%k+1解决回绕。
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) { if (!myCircularQueueIsFull(obj)) { obj->a[obj->tail] = value; obj->tail = (obj->tail + 1) % (obj->k + 1); return true; } return false; }
先判空。队列的删除直接让head+1%k+1解决回绕即可。
bool myCircularQueueDeQueue(MyCircularQueue* obj) { if (!myCircularQueueIsEmpty(obj)) { obj->head = (obj->head + 1) % (obj->k + 1); return true; } return false; }
先判空。不为空直接返回下标为head的值
int myCircularQueueFront(MyCircularQueue* obj) { if(myCircularQueueIsEmpty(obj)) { return -1; } return obj->a[obj->head]; }
int myCircularQueueRear(MyCircularQueue* obj) { if(myCircularQueueIsEmpty(obj)) { return -1; } return obj->a[(obj->tail-1+obj->k+1)%(obj->k+1)]; }
正常情况我们可以直接返回下标为tail-1的值。可是会有越界的情况
先把队列数组销毁,然后销毁队列结构体即可。
void myCircularQueueFree(MyCircularQueue* obj) { free(obj->a); obj->a=NULL; free(obj); obj=NULL; }
这就是栈和队列的内容。栈和队列都是很重要的数据结构。大家一定要多加掌握和熟练。今天就分享到这里。感谢大家的耐心垂阅,咱们下期见!拜拜~