目录
一:括号匹配问题
🍑原题链接:
🤔题目详情:
🙋🏻♂️思路:
括号匹配问题为栈的经典例题。因为栈遵循的规则是后进先出,所以对于此道题我们可以创建一个栈,然后开始进行遍历字符串,如果字符为左括号“ ( ”、“ [ ”、“ { ”,则入栈;如果字符为右括号“ ) ”、“ ] ”、“ } ”,则栈中的栈顶元素(左括号)出栈与右括号进行匹配,如果不匹配则报错,匹配则继续遍历匹配,直到遍历完字符串。遍历完后我们还需要检查栈是否为空,如果为空则均匹配,不为空则未完成匹配。
✍🏻题解:
//创建栈 typedef int STDataType; typedef struct Stack { STDataType* a; //存储数据 int top; //记录栈顶的位置 int capacity; //记录栈的容量 }ST; //初始化栈 void StackInit(ST* ps); //销毁栈 void StackDestory(ST* ps); //压栈(栈规定只能在栈顶插入,所以只有一种插入方式) void StackPush(ST* ps, STDataType x); //出栈 void StackPop(ST* ps); //判断栈中的数据是否为空 bool StackEmpty(ST* ps); //访问栈顶元素 STDataType StackTop(ST* ps); //获取栈中有效元素个数 int StackSize(ST* ps); //初始化栈 void StackInit(ST* ps) { assert(ps); ps->a = NULL; ps->top = 0; ps->capacity = 0; } //销毁栈 void StackDestory(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; ps->a = (STDataType*)realloc(ps->a, newCapacity * sizeof(STDataType)); if (ps->a == NULL) { printf("realloc fail\n"); exit(-1); } ps->capacity = newCapacity; //扩容 } ps->a[ps->top] = x; //数据压栈 (ps->top)++; //栈顶更新 } //出栈 void StackPop(ST* ps) { assert(ps); assert(ps->top > 0);//如果要出栈,至少要保证栈中有元素 (ps->top)--; } //判断栈中的数据是否为空 bool StackEmpty(ST* ps) { /*法一 assert(ps); if (ps->top > 0) { return false; } else { return true; }*/ //法二 return ps->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 isValid(char* s) { ST st;//先创建一个栈 StackInit(&st);//初始化栈 while (*s) { if (*s == '[' || *s == '(' || *s == '{') { StackPush(&st, *s); //如果是左括号就入栈 ++s; } else { if (StackEmpty(&st)) { return false; //如果栈为空,说明栈中无左括号,必定不匹配,直接返回false } char top = StackTop(&st); //获取栈顶元素赋值给top StackPop(&st); //出栈进行匹配 if ((*s == ']' && top != '[') || (*s == ')' && top != '(') || (*s == '}' && top != '{')) { StackDestory(&st); //此种为不匹配情况,直接销毁栈,防止内存泄漏 return false; } else { //此种情况为匹配,继续遍历 ++s; } } } //栈为空,说明所有左括号都完成了匹配 bool ret = StackEmpty(&st); StackDestory(&st); //返回前先销毁,防止内存泄漏 return ret; }
二:用队列实现栈
🍑原题链接:
🤔题目详情:
🙋🏻♂️思路:
做这道题前我们必须知道栈与队列的最基本性质:
栈:后进先出 队列:先进先出
如果我们想要用队列实现栈,我们就需要创建两个队列。
入栈:push数据到不为空的队列。
出栈:把不为空的队列的前N-1个数据导入另外一个空队列,然后将剩下的一个数据删掉。
本质:保持一个队列存储数据,另外一个队列空着,要出栈时,空队列用来导数据。
✍🏻题解:
//创建队列 typedef int QDataType; typedef struct QueueNode { QDataType data; //储存数据 struct QueueNode* next; //储存下一个结点地址 }QNode; //记录队列头尾结点地址 typedef struct Queue { QNode* head; //储存头结点地址 QNode* tail; //储存尾结点地址 }Queue; //初始化队列 void QueueInit(Queue* pq); //销毁队列 void QueueDestory(Queue* pq); //入列 void QueuePush(Queue* pq, QDataType x); //出列 void QueuePop(Queue* pq); //判断队列是否为空 bool QueueEmpty(Queue* pq); //获取队列中元素个数 size_t QueueSize(Queue* pq); //获取队头元素 QDataType QueueFront(Queue* pq); //获取队尾元素 QDataType QueueBack(Queue* pq); //初始化队列 void QueueInit(Queue* pq) { assert(pq);//断言 pq->head = pq->tail = NULL;//置空 } //销毁队列 void QueueDestory(Queue* pq) { assert(pq); QNode* cur = pq->head; //从队头开始遍历销毁结点 while (cur) { QNode* next = cur->next;//销毁前保存下一个结点地址 free(cur); cur = next; } pq->head = pq->tail = NULL; } //入列 void QueuePush(Queue* pq, QDataType x) { assert(pq); //创建一个新的结点 QNode* newnode = (QNode*)malloc(sizeof(QNode)); assert(newnode); //新结点不能为空 //将所要入列的元素赋值给新创建的结点 newnode->data = x; newnode->next = NULL; //队列为空的情况 if (pq->tail == NULL) { assert(pq->tail == NULL);//队列为空,其头结点也必定为空 pq->head = pq->tail = newnode; } else { pq->tail->next = newnode; pq->tail = newnode;//刷新尾结点 } } //出列 void QueuePop(Queue* pq) { assert(pq); assert(pq->head && pq->tail);//出列必须保证队列不为空 //当出列到只剩下一个元素,tail为野指针,需单独判断 if (pq->head->next == NULL) { free(pq->head); pq->head = pq->tail = NULL; } else { QNode* next = pq->head->next; free(pq->head); pq->head = next; } } //判断队列是否为空 bool QueueEmpty(Queue* pq) { assert(pq); //return pq->head == NULL && pq->next==NULL; return pq->head == NULL; } //获取队列中元素个数 size_t QueueSize(Queue* pq) { assert(pq); QNode* cur = pq->head; size_t size = 0; while (cur) { size++; cur = cur->next; } return size; } //获取队头元素 QDataType QueueFront(Queue* pq) { assert(pq); assert(pq->head); return pq->head->data; } //获取队尾元素 QDataType QueueBack(Queue* pq) { assert(pq); assert(pq->head); return pq->tail->data; } //队列创建完毕,现在开始模拟实现栈 typedef struct { Queue q1; //队列q1 Queue q2; //队列q2 } MyStack; MyStack* myStackCreate() { MyStack* pst = (MyStack*)malloc(sizeof(MyStack)); //malloc一个模拟实现的栈 assert(pst); QueueInit(&pst->q1);//初始化队列q1 QueueInit(&pst->q2);//初始化队列q2 return pst; } void myStackPush(MyStack* obj, int x) { assert(obj); if (!QueueEmpty(&obj->q1)) { QueuePush(&obj->q1, x);//如果q1不为空,就往q1插入数据 } else { QueuePush(&obj->q2, x);//q1为空,数据直接插入p2 } } int myStackPop(MyStack* obj) { assert(obj); Queue* emptyQ = &obj->q1; //默认q1为空 Queue* nonEmtpyQ = &obj->q2;//默认q2不为空 if (!QueueEmpty(&obj->q1)) { emptyQ = &obj->q2; //若假设错误,则q2为空 nonEmtpyQ = &obj->q1;//q1不为空 } while (QueueSize(nonEmtpyQ) > 1) { QueuePush(emptyQ, QueueFront(nonEmtpyQ)); //把非空队列数据导到空的队列,直到只剩一个 QueuePop(nonEmtpyQ); //此时把非空的队头数据给删掉,方便后续导入数据 } int top = QueueFront(nonEmtpyQ); //记录栈顶数据 QueuePop(nonEmtpyQ); //删除栈顶数据,使该队列置空 return top; } int myStackTop(MyStack* obj) { assert(obj); if (!QueueEmpty(&obj->q1)) { return QueueBack(&obj->q1); } else { return QueueBack(&obj->q2); } } bool myStackEmpty(MyStack* obj) { assert(obj); //两个队列均为空,则栈为空 return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2); } void myStackFree(MyStack* obj) { assert(obj); QueueDestory(&obj->q1); //释放q1 QueueDestory(&obj->q2); //释放q2 free(obj); }
三:用栈实现队列
🍑原题链接:
🤔题目详情:
🙋🏻♂️思路:
和上道题相似的是首先我们需要创建两个栈,在上一道题中我们需要将数据在两个队列中来回导,从而实现栈的后进先出功能。而这一次我们无需来回导,我们创建两个栈pushST和popST,我们入队时将数据放入栈pushST中,假设我们入队元素为1 2 3 4,则将1 2 3 4放入栈pushST中,出队列时我们将pushST中元素导入popST中,因为栈遵循后进先出,所以存入popST中的元素顺序为4 3 2 1,此时popST元素出栈顺序为1 2 3 4,入栈1 2 3 4,出栈1 2 3 4,满足队列的先进先出性质。
✍🏻题解:
//创建栈 typedef int STDataType; typedef struct Stack { STDataType* a; //存储数据 int top; //记录栈顶的位置 int capacity; //记录栈的容量 }ST; //初始化栈 void StackInit(ST* ps); //销毁栈 void StackDestory(ST* ps); //压栈(栈规定只能在栈顶插入,所以只有一种插入方式) void StackPush(ST* ps, STDataType x); //出栈 void StackPop(ST* ps); //判断栈中的数据是否为空 bool StackEmpty(ST* ps); //访问栈顶元素 STDataType StackTop(ST* ps); //获取栈中有效元素个数 int StackSize(ST* ps); //初始化栈 void StackInit(ST* ps) { assert(ps); ps->a = NULL; ps->top = 0; ps->capacity = 0; } //销毁栈 void StackDestory(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; ps->a = (STDataType*)realloc(ps->a, newCapacity * sizeof(STDataType)); if (ps->a == NULL) { printf("realloc fail\n"); exit(-1); } ps->capacity = newCapacity; //扩容 } ps->a[ps->top] = x; //数据压栈 (ps->top)++; //栈顶更新 } //出栈 void StackPop(ST* ps) { assert(ps); assert(ps->top > 0);//如果要出栈,至少要保证栈中有元素 (ps->top)--; } //判断栈中的数据是否为空 bool StackEmpty(ST* ps) { /*法一 assert(ps); if (ps->top > 0) { return false; } else { return true; }*/ //法二 return ps->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; } //创建好栈,开始用栈实现队列 typedef struct { ST pushST;//保存数据的栈 ST popST; //删除数据的栈 } MyQueue; //创建队列 MyQueue* myQueueCreate() { MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue)); assert(obj); //初始化栈 StackInit(&obj->pushST); StackInit(&obj->popST); return obj; } //入队列 void myQueuePush(MyQueue* obj, int x) { assert(obj); StackPush(&obj->pushST, x);//将数据储存在栈pushST中 } //出队列 int myQueuePop(MyQueue* obj) { assert(obj); //如果栈popST中的数据为空,则需从pushST中导入数据才能删除 if (StackEmpty(&obj->popST)) { //将pushST中的数据全部导入popST中 while (!StackEmpty(&obj->pushST)) { StackPush(&obj->popST, StackTop(&obj->pushST));//把pushST栈顶数据导入popST中 StackPop(&obj->pushST);//pushST栈顶数据导入popST后将栈顶数据删除,方便后续数据的导入 } } int front = StackTop(&obj->popST);//记录popST栈顶元素 StackPop(&obj->popST);//删除popST栈顶数据 return front;//返回栈顶数据 } //取对头数据 int myQueuePeek(MyQueue* obj) { assert(obj); //如果栈popST中的数据为空,则需从pushST中导入数据 if (StackEmpty(&obj->popST)) { //将pushST中的数据全部导入popST中 while (!StackEmpty(&obj->pushST)) { StackPush(&obj->popST, StackTop(&obj->pushST));//把pushST栈顶数据导入popST中 StackPop(&obj->pushST);//pushST栈顶数据导入popST后将栈顶数据删除,方便后续数据的导入 } } return StackTop(&obj->popST);//返回栈顶元素 } //判断队列是否为空 bool myQueueEmpty(MyQueue* obj) { //队列为空则两个栈也为空 return StackEmpty(&obj->pushST) && StackEmpty(&obj->popST); } void myQueueFree(MyQueue* obj) { assert(obj); StackDestory(&obj->pushST); StackDestory(&obj->popST); free(obj); }
四:设置循环队列
🍑原题链接:
🤔题目详情:
🙋🏻♂️思路:
循环队列的实现可以用数组结构或者链表结构,用数组结构相对方便一些。
创建两个变量head和tail分别记录队头和队尾。刚开始head和tail都指向数组下标为0的元素,插入一个数据tail向后移动一位,tail始终为队列最后一个有效数据的下一位,而删除数据也只需要将head向后挪即可。当tail指定的数据为数组的最后一个下标,插入数据后tail重新指向0下标。
存在问题:
对于上面的方法还有一个弊端,那就是我们无法判断什么情况下数组为空,什么情况下数组已满。
当head=tail时有两种情况,一种情况是循环队列为空,另一种情况是队列已满。
解决方案:
我们可以在数组中空出来一个空间不存放数据来区别数组为空或满。比如我们开辟一个可以存放四个数据的数组,那么其有效存储数据个数为3。head=tail时为空,tail的下一个位置是head时为满。
✍🏻题解:
typedef struct { int* a; //创建数组模拟循环队列 int head; //记录队头 int tail; //记录队尾 int k; //表示数组一个循环所能储存的最大元素个数 } MyCircularQueue; //声明函数 bool myCircularQueueIsFull(MyCircularQueue* obj); bool myCircularQueueIsEmpty(MyCircularQueue* obj); //创建循环队列 MyCircularQueue* myCircularQueueCreate(int k) { MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue)); assert(obj); //初始化队列 obj->a = (int*)malloc(sizeof(int) * (k + 1));//多开一个空间,方便区分队空和队满 obj->head = obj->tail = 0; obj->k = k; //循环队列一个循环能存k个数据 return obj; } bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) { if (myCircularQueueIsFull(obj)) { return false;//队列已满,不能插入数据 } obj->a[obj->tail] = value;//插入数据 if (obj->tail == obj->k) { obj->tail = 0;//当tail走到尾端tail重新指向数组下标为0的位置 } else { (obj->tail)++; } return true; } bool myCircularQueueDeQueue(MyCircularQueue* obj) { if (myCircularQueueIsEmpty(obj)) { return false;//队列为空,不能删除 } if (obj->head == obj->k) { obj->head = 0; } else { (obj->head)++; } return true; } //取队头元素 int myCircularQueueFront(MyCircularQueue* obj) { if (myCircularQueueIsEmpty(obj)) { return -1;//队列不能为空 } return obj->a[obj->head];//返回队头元素 } //取队尾元素 int myCircularQueueRear(MyCircularQueue* obj) { if (myCircularQueueIsEmpty(obj)) { return -1;//队列不能为空 } if (obj->tail == 0) { return obj->a[obj->k];//tail为0,队尾在长度的最后一个位置 } else { return obj->a[obj->tail - 1]; } } bool myCircularQueueIsEmpty(MyCircularQueue* obj) { return obj->head == obj->tail;//当head==tail时为空 } bool myCircularQueueIsFull(MyCircularQueue* obj) { //head在头端,tali在尾端队列为满 if (obj->head == 0 && obj->tail == obj->k) { return true; } else { return obj->tail + 1 == obj->head;//一般情况下,当tail的下一个位置为head为满 } } void myCircularQueueFree(MyCircularQueue* obj) { free(obj->a); free(obj); }