菜鸡大学生的数据结构——刷题篇5
磕磕绊绊终于到了栈和队列,菜鸡大学生在水完一篇文章之后心情极佳鸡血上头,决定对栈和队列的题目进行一波制裁。
结果痛心地发现栈和队列这些数据结构c语言是没有现成的库的,它们的代码还得自己手敲一遍,遂,cv。
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
经常看我博客的好兄弟们肯定知道,菜鸡大学生的做题习惯是先给出大概思路之后再写题。
这题该这么盘呢?
我们康康示例,发现右括号一定要与最近的左括号匹配起来。
那么我们可以把左括号存到栈里面,遇到右括号就进行匹配,成功就出栈,失败就返回false。
好——
特殊情况时间:
解决方案:
栈的代码可以去之前博客自取——
bool isValid(char * s){
ST st;
StackInit(&st);
char* cur=s;
while(*cur)
{
if((*cur=='(')||(*cur=='[')||(*cur=='{'))
{
StackPush(&st,*cur);
++cur;
}
else
{
if(StackEmpty(&st))
return false;
char top=StackTop(&st);
if((*cur==')'&&top=='(')||(*cur==']'&&top=='[')||(*cur=='}'&&top=='{'))
{
StackPop(&st);
++cur;
}
else
{
StackDestory(&st);
return false;
}
}
}
if(StackEmpty(&st))
{
StackDestory(&st);
return true;
}
else
{
StackDestory(&st);
return false;
}
}
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
在写这道题的时候我们先把这个队列准备好:
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;
}
接着是push和pop。
这俩玩意怎么写呢?
push的话我们只要把数据放到pushST里面,pop的时候如果popST为空,就把pushST里面的数据全部放到popST里面。
此时popST里面的数据正好是颠倒的,只要向后依次出数据就好了。
void myQueuePush(MyQueue* obj, int x) {
assert(obj);
StackPush(&obj->pushST,x);
}
int myQueuePop(MyQueue* obj) {
assert(obj);
if(StackEmpty(&obj->popST))
{
while(!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST,StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
int front=StackTop(&obj->popST);
StackPop(&obj->popST);
return front;
}
peek就更简单了。
就是pop的简化版本。
复制粘贴大法好。
int myQueuePeek(MyQueue* obj) {
assert(obj);
if(StackEmpty(&obj->popST))
{
while(!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST,StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
int front=StackTop(&obj->popST);
return front;
}
empty呢?
两个栈都是空就表示队列是空。
bool myQueueEmpty(MyQueue* obj) {
assert(obj);
return StackEmpty(&obj->popST)&&StackEmpty(&obj->pushST);
}
最后的free函数。
先free两个栈,最后free掉队列。
void myQueueFree(MyQueue* obj) {
assert(obj);
StackDestory(&obj->popST);
StackDestory(&obj->pushST);
free(obj);
}
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
是差不多的题目呢。
那么,先建栈吧。
typedef struct {
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* obj=(MyStack*)malloc(sizeof(MyStack));
assert(obj);
QueueInit(&obj->q1);
QueueInit(&obj->q2);
return obj;
}
push到空队列里面去,原因在pop里面讲。
void myStackPush(MyStack* obj, int x) {
assert(obj);
if(!QueueEmpty(&obj->q1))
{
QueuePush(&obj->q1,x);
}
else
{
QueuePush(&obj->q2,x);
}
}
开始pop:
我们要先把非空的队列里面的内容放到空队列里面去,直到剩下最后一个。
然后直接把最后一个push出去就好。
看图更好理解一些:>
int myStackPop(MyStack* obj) {
assert(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 ret=QueueFront(nonemptyq);
QueuePop(nonemptyq);
return ret;
}
栈顶直接返回非空队列的队尾就好。
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);
QueueDestory(&obj->q2);
free(obj);
}
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
为了能使tail==front能够区分是空队列还是满队列,我们一般会开辟k+1个空间。
此时
画图真的能看的很清楚,我感觉都不需要讲什么了hh。
有了之前的思路我们就可以写代码了。
我们一个函数一个函数写。
循环队列本体以及初始化,
很多都是之前写过的~
typedef struct {
int* arr;
int front;
int tail;
int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->arr=(int*)malloc(sizeof(int)*(k+1));
obj->k=k;
obj->front=obj->tail=0;
return obj;
}
插入和修改:
和图片描述的差不多,但是要注意数组这玩意不是循环的,
当tail为k时,增加数据要手动把tail改成0,front同理。
也不要忘了判断一下队列是不是空/满。
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj))
return false;
obj->arr[obj->tail]=value;
if(obj->tail==obj->k)
{
obj->tail=0;
}
else
{
obj->tail++;
}
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return false;
if(obj->front==obj->k)
{
obj->front=0;
}
else
{
obj->front++;
}
return true;
}
队头队尾元素,插入删除的青春版。
对头元素直接返回。
队尾元素返回tail的前一个,
如果tail在第一个位置说明队尾元素在最后一个。
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
return obj->arr[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
if(obj->tail==0)
{
return obj->arr[obj->k];
}
else
{
return obj->arr[obj->tail-1];
}
}
空和满分析有讲。
不谈。
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front==obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
if(obj->tail==obj->k&&obj->front==0)
{
return true;
}
else
{
return obj->front==obj->tail+1;
}
}
销毁。
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->arr);
free(obj);
}
大体思路和数组差不多,但是比数组复杂一些。
本体以及初始化:
复杂了许多。
由于链表之间是不连续的,所以我们要存下链表的头和尾位置。
typedef struct SListNode{
int data;
struct SListNode* next;
struct SListNode* prev;
}SListNode;
typedef struct {
SListNode * SList;
SListNode * front;
SListNode * tail;
SListNode * SLfront;
SListNode * SLtail;
} MyCircularQueue;
初始化我们要建好一整个完整的双链表,该连的都要连上。
一开始SLfront,front和tail都在第一个节点。
SLtail在最后一个节点。
为了方便里面的值随便赋个0吧。
//建立新节点
SListNode* BuySListNode(int x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
newnode->data = x;
newnode->next = newnode;
newnode->prev = newnode;
return newnode;
}
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* newnode=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
SListNode* head=BuySListNode(0);
newnode->SList=head;
newnode->front=head;
newnode->tail=head;
newnode->SLfront=head;
SListNode* cur=head;
for(int i=0;i<k;i++)
{
SListNode* node=BuySListNode(0);
cur->next=node;
node->prev=cur;
cur=node;
}
cur->next=head;
head->prev=cur;
newnode->SLtail=cur;
return newnode;
}
插入删除队头队尾空满函数和数组版本是一个道理,不细讲。
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj))
return false;
obj->tail->data=value;
if(obj->tail->next==obj->SLfront)
{
obj->tail=obj->SLfront;
}
else
{
obj->tail=obj->tail->next;
}
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return false;
if(obj->front->next==obj->SLfront)
{
obj->front=obj->SLfront;;
}
else
{
obj->front=obj->front->next;
}
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
return obj->front->data;
}
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
if(obj->tail==obj->SLfront)
{
return obj->SLtail->data;
}
else
{
return obj->tail->prev->data;
}
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front==obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
if(obj->tail==obj->SLtail&&obj->front==obj->SLfront)
{
return true;
}
else
{
return obj->front==obj->tail->next;
}
}
销毁函数,由于是链表还得专门写个链表销毁函数…
啊——为什么不用数组呢,好麻烦。
void ListDestory(SListNode* phead)
{
assert(phead);
SListNode* cur = phead->next;
SListNode* next = cur->next;
while (next!=phead)
{
free(cur);
cur = next;
next = next->next;
}
free(cur);
free(phead);
phead=NULL;
}
void myCircularQueueFree(MyCircularQueue* obj) {
ListDestory(obj->SList);
free(obj);
}
栈的特点是后进先出,怎么入的就倒着出,选B。
A选项,1入栈,1出栈,2,3,4入栈,4,3,2出栈,可以。
B选项,1,2入栈,2出栈,3入栈,3出栈,4入栈,4出栈,1出栈,可以。
C选项,1,2,3入栈,3出栈,由于2还没有出栈,1不可能出栈,错误。
D选项,1,2,3入栈,3出栈,4入栈,4出栈,2,1出栈,可以。
front=rear,有可能是满队列也可能是空队列,选D。
这道题不会的建议看一下队列的概念,选B。
遇到这种题目怎么办?举个栗子吧!
我们可能遇见这两种情况: rear在前或者front在前。
先带几个数字试试看,再来推一下公式:(rear - front + N) % N
选B。
这周的画。
画图工作量明显少了,挺高兴的。
队列终于结束辣,下面就是二叉树惹!
其实不是,先出十大排序。