【初阶数据结构】栈和队列(附题目)

目录

1.栈

1.1栈的概念及结构

1.2栈的实现

1.2.2实现结构的选择

a.数组

b.链表

c.更优的选择

1.2.3实现结构

a.栈的结构体

b.栈的初始化

c.栈的销毁

d.入栈

e.出栈 

f.获取栈顶元素

g.获取栈中有效元素个数 

h.检测队列是否为空,如果为空返回非零结果,如果非空返回0 

2.队列

2.1队列的概念及结构

2.2队列的实现

2.2.1.实现结构的选择

2.2.2.实现结构的选择

a.单个队列节点的结构体

b.维护队列的结构体

c.队列的初始化

d.销毁队列 

e.队尾入队列 

f.队头出队列 

g.获取队列头部元素 

h.获取队列队尾元素

i.获取队列中有效元素个数

j.检测队列是否为空,如果为空返回非零结果,如果非空返回0 

3.栈和队列面试题

1. 括号匹配问题。OJ链接

2. 用队列实现栈。OJ链接

3. 用栈实现队列。OJ链接

4. 设计循环队列。OJ链接

4.概念选择题

4.1题目

4.2答案

5.附录源码:

5.1栈:

Stack.h

Stack.c

5.2队列

Queue.h

Queue.c


1.栈

1.1栈的概念及结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶

出栈:栈的删除操作叫做出栈。出数据也在栈顶。

栈在逻辑上仍然是线性的,但是与顺序表、链表不同,栈只能在栈顶入数据,在栈底出数据,不能在任意位置进行操作,栈顶与栈底是根据功能命名的,没有严格规定首部是栈顶,尾部是栈底。

1.2栈的实现

1.2.2实现结构的选择

在数据结构之前的学习中,我们已经学习了链式与数组这两种存储的结构,栈的实现一般都可以使用数组或者链表实现。相对而言数组的结构实现更优一些。因为数组在尾上插入数据的 代价比较小。

a.数组

对于数组而言,如果我们将头部作为栈底,那么压栈出栈就需要挪动许多数据,算法复杂度为较大,因此我们一般将数组头部作为栈顶,尾部作为栈底.

b.链表

与数组不同,对于单链表而言,如果我们将链表的尾部作为栈底,我们是无法直接访问链表尾部的,同时进行压栈、出栈时,我们还需要找到尾节点的前一个节点,这样使用链表构建栈就会相当麻烦,读者读到这里,可能会认为找前一个节点麻烦,这是由于单链表结构的固有缺陷导致的,那我们直接使用双向链表,不就好了吗?确实,找前一个节点不麻烦了,但是为了解决这个问题,使用了双向链表,那么我们每个节点就多出一个需要维护的节点,空间损耗就大了,而且找尾节点还是需要遍历的,总得来说,我们花费的代价就太大了。因此将链表的尾部作为栈底并不是一个明智的选择。因此我们还是使用单链表(不带头),只不过将单链表第一个有效节点处,作为栈顶。这样我们进行压栈、出栈就方便了。

c.更优的选择

数组与链表两种结构看上去各有千秋,不过,实际上有数组去实现栈会更优一些。有的读者可能会认为如果是动态开辟数组的话,不是会有扩容上的消耗吗?同时不是还有将数据迁移到新的空间上的消耗吗?确实,如果仅看这些,按需申请的链表似乎更优一些,但是扩容调用的是系统上的资源,运行很快,并且一次扩容都是按倍扩,扩容的次数并不会很多,更重要的是数组的CPU的高速缓存更好(涉及知识点较多,这里便不展开了),因此,这里笔者主要使用数组来实现栈。

1.2.3实现结构

a.栈的结构体

静态的结构在实践用到的地方并不多,因此我们实现动态增长的栈。栈的结构体中,_a指向动态开辟的数组空间,_top用来指向栈顶元素或者栈顶元素的下一个位置,_capacity用来表示栈的容量

// 支持动态增长的栈
typedef int STDataType;typedef struct Stack
{STDataType* _a;int _top;		// 栈顶int _capacity;  // 容量 
}Stack;
b.栈的初始化

栈的初始化中,需要注意的是_top的初始值初始化为-1与初始化为0是截然不同的。我们的惯性思考会认为没有数据就是0,但是_top此处并不是单纯的表示数据个数的,我们知道数组能访问到最小下表是0,如果_top是表示指向栈顶数据的,那么当_top初始化为0,表示没有数据,当数组内有一个数据时,为了表示有一个数据,我们就将_top+1吗?这样似乎浪费空间了,因此如果_top是表示指向栈顶数据,我们就将_top初始化为-1,如果_top指向栈顶数据的下一个位置,我们就将_top始化为 0,这时的_top就还可以表示表示数组元素的个数了。笔者接下来实现栈的结构,_top指向栈顶元素的下一个位置。

// 初始化栈 
void StackInit(Stack* ps)
{assert(ps);ps->_a = NULL;// top指向栈顶数据的下一个位置ps->_top = 0;// top指向栈顶数据//ps->_top = -1;ps->_capacity = 0;
}
c.栈的销毁
// 销毁栈 
void StackDestroy(Stack* ps)
{assert(ps);free(ps->_a);ps->_a = NULL;ps->_capacity = ps->_top = 0;
}
d.入栈

入栈之前,我们需要先判断数组的空间是否足够,如果不足,我们再扩容,因为_top指向的是栈顶元素的下一个数据,一次插入时的_top当前指向的位置就是要插入数据的位置。之后_top再加一。

// 入栈 
void StackPush(Stack* ps, STDataType data)
{assert(ps);//扩容if(ps->_capacity == ps->_top){//对于第一次开辟空间,先初始化4个字节空间,之后每次扩容,扩大两倍int newcapacity = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;STDataType* newnode = (STDataType*)realloc(ps->_a, newcapacity * sizeof(STDataType));if (NULL == newnode){perror("StackInit:realloc");exit(1);}ps->_a = newnode;//更新容量的记录值ps->_capacity = newcapacity;}ps->_a[ps->_top] = data;ps->_top++;
}
e.出栈 
// 出栈 
void StackPop(Stack* ps)
{assert(ps);assert(ps->_top > 0);ps->_top--;}
f.获取栈顶元素
// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{assert(ps);assert(ps->_top > 0);return ps->_a[ps->_top - 1];
}
g.获取栈中有效元素个数 
// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{assert(ps);return ps->_top;
}
h.检测队列是否为空,如果为空返回非零结果,如果非空返回0 
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool StackEmpty(Stack* ps)
{assert(ps);return ps->_top == 0;
}

2.队列

2.1队列的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头

2.2队列的实现

2.2.1.实现结构的选择

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数 组头上出数据时,就需要挪动数据,效率会比较低。

2.2.2.实现结构的选择

另外扩展了解一下,实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型 时可以就会使用循环队列(涉及到许多其他知识,这里便不过多介绍)。环形队列可以使用数组实现,也可以使用循环链表实现。

a.单个队列节点的结构体
typedef int QDataType;// 链式结构:表示队列 
typedef struct QListNode
{struct QListNode* _next;QDataType _data;
}QNode;
b.维护队列的结构体

与栈不同,在实现队列时,以入队列为例,我们需要传尾指针,如果要改变尾节点还要传对应的二级指针,同时如果队列现在一个节点都没有,我们还需要传对应头指针的二级指针。总得来说,各种接口的使用似乎非常不便,针对这种问题,我们专门创建一个结构体去维护头指针与尾指针。调用接口时,我们只需要传入这个结构体的指针就可以了。这样我们就可以通过访问结构体来改变对应的头尾指针。此外加上_size记录数据个数

// 队列的结构 
typedef struct Queue
{QNode* _front;QNode* _rear;int _size;
}Queue;
c.队列的初始化

传入专门维护的结构体初始化。

// 初始化队列 
void QueueInit(Queue* q)
{q->_front = NULL;q->_rear = NULL;q->_size = 0;
}
d.销毁队列 

循环遍历节点,挨个释放

// 销毁队列 
void QueueDestroy(Queue* q)
{assert(q);QNode* cur = q->_front;while(cur){QNode* next = cur->_next;free(cur);cur = next;}q->_front = q->_rear = NULL;q->_size = 0;
}
e.队尾入队列 

如果队列内没有节点时,在入队列时,还需要该表头结点。

// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{assert(q);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (NULL == newnode){perror("QueuePush:malloc failed");exit(1);}newnode->_data = data;newnode->_next = NULL;if (NULL == q->_rear){q->_front = q->_rear = newnode;}else{q->_rear->_next = newnode;q->_rear = newnode;}q->_size++;
}
f.队头出队列 

出队列之前需要判断是否队列中是否存在数据,没有数据,无法出数据。如果队列队列中只有一个数据,出完数据,还需要改变头结点。

// 队头出队列 
void QueuePop(Queue* q)
{assert(q);assert(q->_size);QNode* next = q->_front->_next;free(q->_front);q->_front = next;if (q->_size == 1){q->_rear = NULL;}q->_size--;}
g.获取队列头部元素 

首先判断头指针是否为空,不为空才可以获取头部元素。

// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{assert(q);assert(q->_front);return q->_front->_data;
}
h.获取队列队尾元素

首先判断尾指针是否为空,不为空才可以获取尾部元素。

// 获取队列队尾元素 
QDataType QueueBack(Queue* q)
{assert(q);assert(q->_rear);return q->_rear->_data;}
i.获取队列中有效元素个数
// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{assert(q);return q->_size;
}
j.检测队列是否为空,如果为空返回非零结果,如果非空返回0 
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q)
{assert(q);return q->_size == 0;
}

3.栈和队列面试题

1. 括号匹配问题。OJ链接

这一道题,要求左括号与对应的右括号匹配,当取出字符串中的右括号时,需要与当前右括号的前面第一个左括号括号比对,如果是对应的左括号,则匹配成功,取下一个右括号,并且取前面第一个左括号匹配(已经匹配的左括号不再参与匹配)。我们发现我们总是需要取离右括号最近的左括号(相对其他左括号顺序靠后的元素)。匹配后,已匹配的括号不参加,下次匹配据需向“左找”。

因此根据题意,我们这里可以创建栈,循环遍历字符数组,如果当前是左括号,则入栈,如果是右括号,那么这时,我们就取栈顶的元素(由于栈是后入先出栈,因此栈顶的元素就是左边离右括号最近的左括号),与右括号匹配,如果匹配则,将匹配的左括号出栈,继续循环,如果不匹配,则结束遍历。

#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
#include<stdio.h>// 支持动态增长的栈
typedef int STDataType;typedef struct Stack
{STDataType* _a;int _top;		// 栈顶int _capacity;  // 容量 
}Stack;// 初始化栈 
void StackInit(Stack* ps)
{ps->_a = NULL;ps->_capacity = ps->_top = 0;
}// 入栈 
void StackPush(Stack* ps, STDataType data)
{assert(ps);if(ps->_capacity == ps->_top){int newcapacity = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;STDataType* newnode = (STDataType*)realloc(ps->_a, newcapacity * sizeof(STDataType));if (NULL == newnode){perror("StackInit:realloc");exit(1);}ps->_a = newnode;ps->_capacity = newcapacity;}ps->_a[ps->_top] = data;ps->_top++;
}// 出栈 
void StackPop(Stack* ps)
{assert(ps);assert(ps->_top > 0);ps->_top--;}// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{assert(ps);assert(ps->_top > 0);return ps->_a[ps->_top - 1];
}// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{assert(ps);return ps->_top;
}// 检测栈是否为空,如果为空返回0,如果不为空返回非零结果
bool StackEmpty(Stack* ps)
{assert(ps);return ps->_top;
}// 销毁栈 
void StackDestroy(Stack* ps)
{assert(ps);free(ps->_a);ps->_a = NULL;ps->_capacity = ps->_top = 0;
}bool isValid(char* s)
{Stack p;StackInit(&p);while(*s){if(*s == '(' || *s == '{' || *s == '[')//左括号入栈{StackPush(&p,*s);}else{if(!StackEmpty(&p))//右括号入栈前不能没有左括号,否则一定会出现不匹配的情况{StackDestroy(&p);return false;}char top = 0;top = StackTop(&p);if(top == '(' && *s != ')'||top == '{' && *s != '}'||top == '[' && *s != ']')//括号不匹配返回false{StackDestroy(&p);return false;}StackPop(&p);//匹配成功,将匹配的左括号出栈}s++;//移动到下一个括号}int ret = !StackEmpty(&p);//判断栈内是否为空,为空说明存在未匹配的左括号StackDestroy(&p);return ret;
}

2. 用队列实现栈。OJ链接

这一题,我们需要根据队列先进先出的性质实现栈后进先出的性质,题目给了我们两个队列,因此这题就是通过将数据在两个列类之间转移来完成的,问题的关键就在于如何转移数据。

如果说队列中已经有数据,为了出4,这是我们就可以将1、2、3入队列入到q2中,这时我们就可以出q1中的4,达到后进先出的效果,之后加入我们入栈5,6呢?我们又将5,6入到哪个队列中呢?

假如们入到 q1,中,这时如果我们将5入到q2,再出6,似乎没什么问题,那么这是如果我们不出6,我们还需要继续导入数据呢?

这是我们发现,面对两个都不为空的队列,我们再想入数据,我们必须先遍历已经存储的数据进行,判断当数据较多时,我们就会发现复杂度以及程序的逻辑性都不会太好。因此,为了避免这一混乱的情况发生,我们必须保证将数据入到不为空的队列,这样出数据将数据导到空队列中,出完数量后,就又保持两队列一空一不为空了。

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include"stdbool.h"typedef int QDataType;// 链式结构:表示队列 
typedef struct QListNode
{struct QListNode* _next;QDataType _data;
}QNode;// 队列的结构 
typedef struct Queue
{QNode* _front;QNode* _rear;int _size;
}Queue;
void QueueInit(Queue* q)
{q->_front = NULL;q->_rear = NULL;q->_size = 0;
}// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{assert(q);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (NULL == newnode){perror("QueuePush:malloc failed");exit(1);}newnode->_data = data;newnode->_next = NULL;if (0 == q->_size){q->_front = q->_rear = newnode;}else{q->_rear->_next = newnode;q->_rear = q->_rear->_next;}q->_size++;
}// 队头出队列 
void QueuePop(Queue* q)
{assert(q);assert(q->_size);QNode* next = q->_front->_next;free(q->_front);q->_front = next;if (q->_size == 1){q->_rear = NULL;}q->_size--;}// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{assert(q);assert(q->_size);return q->_front->_data;
}// 获取队列队尾元素 
QDataType QueueBack(Queue* q)
{assert(q);assert(q->_size);return q->_rear->_data;}// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{assert(q);return q->_size;
}// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q)
{assert(q);return q->_size == 0;
}// 销毁队列 
void QueueDestroy(Queue* q)
{assert(q);QNode* cur = q->_front;while(cur){QNode* next = cur->_next;free(cur);cur = next;}q->_front = q->_rear = NULL;q->_size = 0;
}
typedef struct {Queue q1;Queue q2;
} MyStack;MyStack* myStackCreate() {MyStack*pst = (MyStack*)malloc(sizeof(MyStack));
//创建模拟的栈时,只能malloc动态创建,不能静态开辟,否则出了函数,开辟空间会被收回
//也不能使用static, 否则多次调用函数,值会一直保存,会出问题。if(NULL == pst){perror("malloc");exit(1);}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* empty = &(obj->q1);Queue* nonempty = &(obj->q2);if(!QueueEmpty(&(obj->q1))){nonempty = &(obj->q1);empty = &(obj->q2);}//不为空的队列前size-1导走,删除最后一个就是栈顶数据while(QueueSize(nonempty) > 1){QueuePush(empty,QueueFront(nonempty));QueuePop(nonempty);}int top = QueueFront(nonempty);QueuePop(nonempty);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);
}/*** Your MyStack struct will be instantiated and called as such:* MyStack* obj = myStackCreate();* myStackPush(obj, x);* int param_2 = myStackPop(obj);* int param_3 = myStackTop(obj);* bool param_4 = myStackEmpty(obj);* myStackFree(obj);
*/

需要注意的是最后销毁时,要先销毁队列对应的空间,在销毁存储两个队列指针的空间。

3. 用栈实现队列。OJ链接

对于这一题,如果我们沿用用队列实现栈的思路,似乎也可以,我们将先将其他数据入到另一个栈中,再留下要出栈的数据出栈。但是我们要入栈时,我们不能在入到非空的栈中,因为这样,出数据的时候,顺序就完全乱了,对此,我们可以将原数据再倒回原数据中,再向非空栈中入数据。这样就没问题了,但是这样就比较麻烦了。

根据观察,我们发现其实在将数据到到空栈是,原数据顺序会倒过来,这是我们再出栈,就直接实现了先进先出的效果。

因此我们将两个栈分成一个专门出数据,一个专门入数据,当出数据栈popst为空,我们就将入数据栈pushst数据全部导到popst中(必须一次性将所有数据全部导入,否则顺序就乱了。)入数据时,直接往对应栈入就行了。

#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
#include<stdio.h>// 支持动态增长的栈
typedef int STDataType;typedef struct Stack
{STDataType* _a;int _top;		// 栈顶int _capacity;  // 容量 
}Stack;
// 初始化栈 
void StackInit(Stack* ps)
{ps->_a = NULL;ps->_capacity = ps->_top = 0;
}// 入栈 
void StackPush(Stack* ps, STDataType data)
{assert(ps);if(ps->_capacity == ps->_top){int newcapacity = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;STDataType* newnode = (STDataType*)realloc(ps->_a, newcapacity * sizeof(STDataType));if (NULL == newnode){perror("StackInit:realloc");exit(1);}ps->_a = newnode;ps->_capacity = newcapacity;}ps->_a[ps->_top] = data;ps->_top++;
}// 出栈 
void StackPop(Stack* ps)
{assert(ps);assert(ps->_top > 0);ps->_top--;}// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{assert(ps);assert(ps->_top > 0);return ps->_a[ps->_top - 1];
}// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{assert(ps);return ps->_top;
}// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool StackEmpty(Stack* ps)
{assert(ps);return ps->_top == 0;
}// 销毁栈 
void StackDestroy(Stack* ps)
{assert(ps);free(ps->_a);ps->_a = NULL;ps->_capacity = ps->_top = 0;
}typedef struct {Stack pushst;Stack popst;
} MyQueue;MyQueue* myQueueCreate() {MyQueue* q1 = (MyQueue*)malloc(sizeof(MyQueue));StackInit(&q1->pushst);StackInit(&q1->popst);return q1;
}void myQueuePush(MyQueue* obj, int x) {
//实现入队列数据StackPush(&obj->pushst,x);
}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);
}int myQueuePop(MyQueue* obj) {
//出队列队头数据int front = myQueuePeek(obj);StackPop(&obj->popst);return front;}bool myQueueEmpty(MyQueue* obj) {
//两个栈都为空,队列才为空return StackEmpty(&obj->pushst)&&StackEmpty(&obj->popst);
}void myQueueFree(MyQueue* obj) {
//先释放对应栈空间,再释放队列对应空间StackDestroy(&obj->pushst);StackDestroy(&obj->popst);free(obj);
}/*** Your MyQueue struct will be instantiated and called as such:* MyQueue* obj = myQueueCreate();* myQueuePush(obj, x);* int param_2 = myQueuePop(obj);* int param_3 = myQueuePeek(obj);* bool param_4 = myQueueEmpty(obj);* myQueueFree(obj);
*/

4. 设计循环队列。OJ链接

这道题目的意思就是队列的的空间大小是固定的,这些空间可以重复使用,看到这一题的循环结构,也许有的读者会想到链表,认为链表的结构完成这一题会比数组简单,但是实际上并不是这样的,笔者这里先以数组完成这题,链表的不便后文讲解。

首先假设要插入的空间K是4,这是如果我们开辟4个空间,head、tail都初始化为0,push一个数据,tail++,pop一个数据,head++,当tail、head值大于K,通过%K,实现回环,这样head == tail时,就代表数组为空的情况了,但是,我们发现如果数组满时也会出现head == tail,这就是我们说的假溢出的情况了。

为了解决这个问题,笔者有两种方法,一种是创建size专门记录数组元素个数;还有一种是多开一个空间。笔者接下来重点介绍第二种(对于这类问题一些官方解答更喜欢用第二种方法)。

如上图,其他不变,只是我们判断数组满到条件变成了(tail+1)%(k+1) == head;这是为什么呢?我们知道,head是指向队列的头,tail指向尾的下一位,因为我们多开了一个空间,并且是按序的空出空间,因此,当数组满时,尾的空间后面就是空空间,tail指向它,这时如果tail再向前走一步,就与head相等,不过这时,为了避免出现越界的情况,tail+1需要%(K+1),这就相当于tail+1>5时,tail+1-5,tail回到数组开头,同时数组越界的情况是相对与数组总长的,因此这里是%(k+1)不是%k.

typedef struct {//创建结构体存储需要用到的值方便维护int k;int head;int tail;int* a;
} MyCircularQueue;MyCircularQueue* myCircularQueueCreate(int k) {//初始化结构体MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));obj->a = (int*)malloc((k + 1) * sizeof(int));obj->head = 0;obj->tail = 0;obj->k = k;return obj;
}bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->head == obj->tail;//head == tail为空}bool myCircularQueueIsFull(MyCircularQueue* obj) {return (obj->tail + 1)%(obj->k + 1) == obj->head;//(tail + 1)%(k + 1) == head 为满}bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if(myCircularQueueIsFull(obj)){return false;}else{obj->a[obj->tail] = value;obj->tail = (obj->tail + 1) % (obj->k + 1);//防止越界return true;}
}bool myCircularQueueDeQueue(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return false;}obj->head = (obj->head + 1) % (obj->k + 1);//防止head越界return true;}int myCircularQueueFront(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj))//为空取不到队头的数据{return -1;}else{return obj->a[obj->head];}
}int myCircularQueueRear(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj))//为空取不到队尾的数据{return -1;}else{return obj->a[(obj->tail-1 + obj->k + 1) % (obj->k + 1)];}
}void myCircularQueueFree(MyCircularQueue* obj) {free(obj->a);free(obj);}/*** Your MyCircularQueue struct will be instantiated and called as such:* MyCircularQueue* obj = myCircularQueueCreate(k);* bool param_1 = myCircularQueueEnQueue(obj, value);* bool param_2 = myCircularQueueDeQueue(obj);* int param_3 = myCircularQueueFront(obj);* int param_4 = myCircularQueueRear(obj);* bool param_5 = myCircularQueueIsEmpty(obj);* bool param_6 = myCircularQueueIsFull(obj);* myCircularQueueFree(obj);
*/

这里需要特别注意的是,当我们取队尾的数据时,由于tail是指向队尾数据的下一位,因此如果tail在数组头,tail-1取队尾就会出现负数的情况,因此与(tail+1)%(k+1)类似,我们要将(tail+1 +k+1)%(k+1),这样就可以避免出现,负数的情况,如果tail不在队头这样处理也不会有问题。

那么回到一开始的问题,为什么笔者说,使用链表不像看上去那样简单呢?首先,用链表形成循环队列,不像数组那样直接申请一块空间方便,其次由于tail是指向尾的下一个位置,因此我们如果想要找尾会很麻烦,对此可以使用双向链表、遍历寻找尾、专门创建变量记录tail前一个节点,总的来说,使用链表不光没有什么实质的好处,反而还会有不少麻烦。当然,如果读者想要用链表完成,也不无不可。

4.概念选择题

4.1题目

1.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )。

A 12345ABCDE

B EDCBA54321

C ABCDE12345

D 54321EDCBA

2.若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()

A 1,4,3,2

B 2,3,4,1

C 3,1,4,2

D 3,4,2,1

3.循环队列的存储空间为 Q(1:100) ,初始状态为 front=rear=100 。经过一系列正常的入队与退队操作 后, front=rear=99 ,则循环队列中的元素个数为( )

A 1

B 2

C 99

D 0或者100

4.以下( )不是队列的基本运算?

A 从队尾插入一个新元素

B 从队列中删除第i个元素

C 判断一个队列是否为空

D 读取队头元素的值

5.现有一循环队列,其队头指针为front,队尾指针为rear;循环队列长度为N。其队内有效长度为?(假设 队头不存放数据)

A (rear - front + N) % N + 1

B (rear - front + N) % N

C ear - front) % (N + 1)

D (rear - front + N) % (N - 1)

4.2答案

1.B  //依次入栈,入完再出栈,后入先出

2.C  //不可能出现2不出,就出到1

3.D  //属于没有多开一个空间的方法,相等时可能为空,可能满。

4.B

5.B

5.附录源码:

5.1栈:

Stack.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
#include<stdio.h>// 支持动态增长的栈
typedef int STDataType;typedef struct Stack
{STDataType* _a;int _top;		// 栈顶int _capacity;  // 容量 
}Stack;// 初始化栈 
void StackInit(Stack* ps);// 入栈 
void StackPush(Stack* ps, STDataType data);// 出栈 
void StackPop(Stack* ps);// 获取栈顶元素 
STDataType StackTop(Stack* ps);// 获取栈中有效元素个数 
int StackSize(Stack* ps);// 检测栈是否为空,如果为空返回0 ,如果不为空返回非零结果
bool StackEmpty(Stack* ps);// 销毁栈 
void StackDestroy(Stack* ps);

Stack.c

#include"Stack.h"// 初始化栈 
void StackInit(Stack* ps)
{assert(ps);ps->_a = NULL;// top指向栈顶数据的下一个位置ps->_top = 0;// top指向栈顶数据//ps->_top = -1;ps->_capacity = 0;
}// 入栈 
void StackPush(Stack* ps, STDataType data)
{assert(ps);//扩容if(ps->_capacity == ps->_top){int newcapacity = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;STDataType* newnode = (STDataType*)realloc(ps->_a, newcapacity * sizeof(STDataType));if (NULL == newnode){perror("StackInit:realloc");exit(1);}ps->_a = newnode;ps->_capacity = newcapacity;}ps->_a[ps->_top] = data;ps->_top++;
}// 出栈 
void StackPop(Stack* ps)
{assert(ps);assert(ps->_top > 0);ps->_top--;}// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{assert(ps);assert(ps->_top > 0);return ps->_a[ps->_top - 1];
}// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{assert(ps);return ps->_top;
}// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool StackEmpty(Stack* ps)
{assert(ps);return ps->_top == 0;
}// 销毁栈 
void StackDestroy(Stack* ps)
{assert(ps);free(ps->_a);ps->_a = NULL;ps->_capacity = ps->_top = 0;
}

5.2队列

Queue.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include"stdbool.h"typedef int QDataType;// 链式结构:表示队列 
typedef struct QListNode
{struct QListNode* _next;QDataType _data;
}QNode;// 队列的结构 
typedef struct Queue
{QNode* _front;QNode* _rear;int _size;
}Queue;// 初始化队列 
void QueueInit(Queue* q);// 队尾入队列 
void QueuePush(Queue* q, QDataType data);// 队头出队列 
void QueuePop(Queue* q);// 获取队列头部元素 
QDataType QueueFront(Queue* q);// 获取队列队尾元素 
QDataType QueueBack(Queue* q);// 获取队列中有效元素个数 
int QueueSize(Queue* q);// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q);// 销毁队列 
void QueueDestroy(Queue* q);

Queue.c

#include"queue.h"// 初始化队列 
void QueueInit(Queue* q)
{q->_front = NULL;q->_rear = NULL;q->_size = 0;
}// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{assert(q);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (NULL == newnode){perror("QueuePush:malloc failed");exit(1);}newnode->_data = data;newnode->_next = NULL;if (NULL == q->_rear){q->_front = q->_rear = newnode;}else{q->_rear->_next = newnode;q->_rear = newnode;}q->_size++;
}// 队头出队列 
void QueuePop(Queue* q)
{assert(q);assert(q->_size);QNode* next = q->_front->_next;free(q->_front);q->_front = next;if (q->_size == 1){q->_rear = NULL;}q->_size--;}// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{assert(q);assert(q->_front);return q->_front->_data;
}// 获取队列队尾元素 
QDataType QueueBack(Queue* q)
{assert(q);assert(q->_rear);return q->_rear->_data;}// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{assert(q);return q->_size;
}// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q)
{assert(q);return q->_size == 0;
}// 销毁队列 
void QueueDestroy(Queue* q)
{assert(q);QNode* cur = q->_front;while(cur){QNode* next = cur->_next;free(cur);cur = next;}q->_front = q->_rear = NULL;q->_size = 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/848726.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

m1系列芯片aarch64架构使用docker-compose安装seata

之前看到 DockerHub 上发布了 m1 芯片 aarch64 架构的 seata 镜像, 所以就尝试的安装了下, 亲测可用: 使用该命令查看正在运行的 seata 容器 docker ps | grep seata 一. docker-compose.yml 命令编写 volumes 命令所指定的宿主机映射地址, 需要根据自己的电脑环境更换 环…

MySQL条件查询

018条件查询之或者or or表示或者&#xff0c;还有另一种写法&#xff1a;|| 案例&#xff1a;找出工作岗位是MANAGER和SALESMAN的员工姓名、工作岗位 注意字符串一定要带单引号 select ename, job from emp where jobmanager or jobsalesman;任务&#xff1a;查询20和30部门的…

西湖大学最新AI工具:识别虚假新闻和辨别AI生成内容,准确率达99%

你好&#xff0c;我是郭震 随着人工智能技术的发展&#xff0c;生成式AI在文本生成领域展示了惊人的潜力。然而&#xff0c;随之而来的虚假新闻和AI生成的文章让人们难以分辨。 近日&#xff0c;西湖大学团队发布了一款名为Fast-DetectGPT的新工具&#xff0c;为识别虚假新闻和…

Linuxftp服务001匿名登入

在Linux系统中搭建FTP&#xff08;File Transfer Protocol&#xff09;服务&#xff0c;可以让用户通过网络在服务器与其他客户端之间传输文件。它有几种登入模式&#xff0c;今天我们讲一下匿名登入。 操作系统 CentOS Stream9 操作步骤 首先我们先下载ftp [rootlocalhost…

数字智能数字人直播带货软件系统 实现真人形象的1:1克隆 前后端分离 带完整的安装代码包以及搭建教程

系统概述 数字智能数字人直播带货小程序源码系统是一套集人工智能、3D建模、云计算等技术于一体的综合性解决方案。该系统通过深度学习算法&#xff0c;能够实现对真人形象的精准捕捉和1:1克隆&#xff0c;使数字人在直播过程中呈现出与真人无异的表现力。同时&#xff0c;系统…

OpenCv之简单的人脸识别项目(动态处理页面)

人脸识别 准备九、动态处理页面1.导入所需的包2.设置窗口2.1定义窗口外观和大小2.2设置窗口背景2.2.1设置背景图片2.2.2创建label控件 3.定义视频处理脚本4.定义相机抓取脚本5.定义关闭窗口的函数6.按钮设计6.1视频处理按钮6.2相机抓取按钮6.3返回按钮 7.定义关键函数8.动态处理…

el-input添加clearable属性 输入内容时会直接撑开

<el-inputclearablev-if"item.type number || item.type text":type"item.type":placeholder"item.placeholder":prefix-icon"item.icon || "v-model.trim"searchform[item.prop]"></el-input>解决方案 添加c…

安全专业的硬件远控方案 设备无网也能远程运维

在很多行业中&#xff0c;企业的运维工作不仅仅局限在可以联网的IT设备&#xff0c;不能连接外网的特种设备也需要专业的远程运维手段。 这种特种设备在能源、医疗等行业尤其常见&#xff0c;那么我们究竟如何通过远程控制&#xff0c;对这些无网设备实施远程运维&#xff0c;…

【简报】VITA 74 (VNX)C

VNX 模块标准 12.5 mm Module • 母板 MiniPCIe • 200 针连接器 • 75mm &#xff08;长&#xff09; X 89mm &#xff08;宽&#xff09; X 12.5mm &#xff08;高&#xff09; •应用 1 个 SBC 2 I/O 载波 3 个 GPS / IMU / SAASM 4 存储和内存 19 mm Module • 母板 …

鸿蒙轻内核M核源码分析系列九 互斥锁Mutex

多任务环境下会存在多个任务访问同一公共资源的场景&#xff0c;而有些公共资源是非共享的临界资源&#xff0c;只能被独占使用。鸿蒙轻内核使用互斥锁来避免这种冲突&#xff0c;互斥锁是一种特殊的二值性信号量&#xff0c;用于实现对临界资源的独占式处理。另外&#xff0c;…

60V降压12V0.3A稳压芯片 48V降压5V0.3A电源IC-惠海H6246

惠海H6246降压开关控制器芯片是一款降压恒压的电源管理芯片&#xff0c;适用于高压输入、低压输出的应用。以下是对该产品的详细分析&#xff1a; 首先&#xff0c;H6246降压恒压芯片它内置60V耐压MOS&#xff0c;能够在48V的输入电压下稳定工作&#xff0c;并且具有宽压8V-48V…

vue开发网站--对文章详情页的接口内容进行处理

一、需求 接口返回的数据中既包含文字也包含图片&#xff0c;并且需要对图片进行处理&#xff08;设置最大宽度为100%并拼接域名&#xff09; 可以按照以下步骤进行操作&#xff1a; 二、代码 <template><div class"details"><div class"infos…

docker registry-harbor私有镜像仓库安装

本博文将引导您安装和配置Harbor私有镜像仓库。安装前&#xff0c;请确保您已安装Docker和Docker Compose。 前置环境 需要安装docker和docker-compose 下载Harbor Harbor的最新版本可以从GitHub下载。这里以2.9.4版本为例&#xff1a; 下载地址&#xff1a;https://github…

SmartEDA:革新电路设计,体验前所未有的创新乐趣!

在数字化时代的浪潮中&#xff0c;电路设计作为科技领域的重要一环&#xff0c;也在经历着前所未有的变革。今天&#xff0c;就让我们一同走进SmartEDA的世界&#xff0c;感受这款革新性工具带来的电路设计乐趣&#xff0c;开启一段全新的创新之旅&#xff01; SmartEDA&#x…

数据结构复习

基本概念和术语&#xff1a; 数据&#xff1a;是描述客观事物的符号&#xff0c;是计算机中可以操作的对象&#xff0c;是能被计算机识别&#xff0c;并输入给计算机处理的符号集合。 数据元素&#xff1a;是组成数据的&#xff0c;具有一定意义的基本单位&#xff0c;在计算机…

Shopee本土店成本利润如何核算?EasyBoss ERP帮您精准掌控

这几年做跨境电商的老板们都在说东南亚市场广阔&#xff0c;在东南亚开本土店流量大&#xff0c;为了赚钱兴冲冲跑去东南亚开本土店&#xff0c;每天看着店铺不停出单。 心里乐呵呵&#xff1a;“本土店是真赚钱&#xff0c;马上要走上人生巅峰了&#xff01;” 但每月实际一对…

[XYCTF新生赛]-Reverse:ez_rand解析(爆破时间戳,汇编结合反汇编)

无壳 查看ida 这里是利用time64获取种子&#xff0c;但是time64不是标准的函数&#xff0c;这里是伪随机数&#xff0c;简单地来说就是它不是通过时间来确定种子&#xff0c;所以我们没办法在脚本里直接调用它得到种子&#xff0c;那就意味着我们不知道种子是多少&#xff0c;…

jmeter -n -t 使用非GUI模式运行脚本说明

命令模式下执行jmx文件 jmeter -n -t fatie.jmx -l results\t4.jtl -e -o results\h1 表示以命令行模式运行当前目录下的脚本fatie.jmx,将结果存入当前目录下的results\t1.jtl,并且生成html格式的报告&#xff0c;写入文件夹results\h1。 说明&#xff1a;生成结果的文件夹r…

STL:list

文章目录 标准库中的listlist的构造list的迭代器list的容量list的访问list的修改 list的迭代器失效list的反向迭代器list 与 vector的对比 标准库中的list list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是双…

电子抄表系统:当代能源管理的创新

1.界定与基本原理 电子抄表系统是一种利用通讯网技术&#xff0c;如wifi网络、物联网技术或有线连接&#xff0c;全自动收集并解决电磁能、水、气等公共事业表计数据信息解决方案。它取代了传统人工抄水表方法&#xff0c;提升了数据可靠性和质量。 2.功能和优点 -实时监控系…