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

news2024/11/25 7:03:27

目录

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.coloradmin.cn/o/1718115.html

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

相关文章

BioVendor—sHLA-G ELISA试剂盒

人类白细胞抗原-G (HLA-G)与其他MHC类基因的不同之处在于它的低多态性和产生七种HLA-G蛋白的选择性剪接&#xff0c;这些蛋白的组织分布局限于正常的胎儿和成人组织&#xff0c;这些组织对先天和后天免疫细胞都具有耐受性。可溶性HLA-G是一种免疫抑制分子&#xff0c;诱导活化的…

掌握这三点软文营销秘诀,快速实现品牌用户增长

在信息爆炸的时代&#xff0c;品牌用户如何快速实现增长已成为每个企业发展的必经之虑。而软文营销&#xff0c;作为品牌推广中不可或缺的一环&#xff0c;也正逐渐受到越来越多企业的青睐。它与传统的硬广告相比&#xff0c;不会直接的进行产品销售&#xff0c;软文更注重与读…

[初始计算机]——计算机网络的基本概念和发展史及OSI参考模型

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;网络通信基础TCP/IP专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年5月30日11点59分 &#x1f004;️文章质量&#xff1a;96分 ​ 目录 &#x1f310;计算机网络概述 &#x1f4af;…

星鸾云平台:高效配置Anaconda环境

星鸾云平台&#xff1a;高效配置Anaconda环境 多种算力资源和AI一体机在星鸾云平台上安装Anaconda并配置Python环境的步骤注册和登录体验GPU显卡服务选择服务器实例登陆GPU实例挂载数据盘配置CUDA环境安装Anaconda 星鸾云平台的优势 星鸾云平台以其卓越的产品功能和优势&#x…

c++ QT 实现QMediaPlayer播放音频显示音频级别指示器

文章目录 效果图概述代码总结 效果图 概述 QMediaPlayer就不介绍了&#xff0c;就提供了一个用于播放音频和视频的媒体播放器 QAudioProbe 它提供了一个探针&#xff0c;用于监控音频流。当音频流被捕获或播放时&#xff0c;QAudioProbe 可以接收到音频数据。这个类在需要访问…

计算机网络之快重传和快恢复以及TCP连接与释放的握手

快重传和快恢复 快重传可以让发送方尽早得知丢失消息&#xff0c; 当发送消息M1,M2&#xff0c;M3,M4,M5后,假如消息M2丢失&#xff0c;那么按照算法会发送对M2报文前一个报文M1的重复确认&#xff08;M1正常接受到&#xff0c;已经发送了确认),然后之后收到M4,M5,也会发送两…

Linux网络编程:传输层协议|UDP|TCP

知识引入&#xff1a; 端口号&#xff1a; 当应用层获得一个传输过来的报文时&#xff0c;这时数据包需要知道&#xff0c;自己应该送往哪一个应用层的服务&#xff0c;这时就引入了“端口号”&#xff0c;通过区分同一台主机不同应用程序的端口号&#xff0c;来保证数据传输…

el-date-picker选择开始日期的近半年

<el-date-pickerv-model"form[val.key]":type"val.datePickerType || daterange":clearable"val.clearable && true"range-separator"~"start-placeholder"开始日期"end-placeholder"结束日期"style&q…

DataRockMan洛克先锋OZON选品工具

随着全球电子商务的飞速发展&#xff0c;跨境电商平台已成为越来越多企业和个人追逐市场红利的重要战场。在众多跨境电商平台中&#xff0c;OZON以其独特的市场定位和强大的用户基础&#xff0c;吸引了无数卖家的目光。然而&#xff0c;如何在OZON平台上成功选品&#xff0c;成…

结构体(C保姆级讲解)

前言&#xff1a; 为什么会有结构体&#xff0c;结构体可以用来面熟一个复杂对象&#xff0c;我们知道C语言中有哪些数据类型&#xff0c;有整型&#xff0c;有浮点型&#xff0c;有字符型&#xff0c;但是在生活中&#xff0c;我们需要描述一些比较复杂的东西&#xff0c;比如…

java调用科大讯飞离线语音合成SDK --内附完整项目

科大讯飞语音开放平台基础环境搭建 1.用户注册 注册科大讯飞开放平台账号 2.注册好后先创建一个自己的应用 创建完成后进入应用选择离线语音合成&#xff08;普通版&#xff09;可以看到我们开发需要的SDK,选择windows MSC点击下载。 3.选择你刚刚创建的应用&#xff0c;选择…

磁盘配额的具体操作

磁盘配额&#xff1a; linux的磁盘空间有两个方面&#xff1a;第一个是物理空间&#xff0c;也就是磁盘的容量 第二个inode号耗尽&#xff0c;也无法写入 linux根分区&#xff1a;根分区的空间完全耗尽&#xff0c;服务程序崩溃&#xff0c;系统也无法启动了。 为了防止有人…

Vue2 + Element UI 封装 Table 递归多层级列表头动态

1、在 components 中创建 HeaderTable 文件夹&#xff0c;在创建 ColumnItem.vue 和 index.vue。 如下&#xff1a; 2、index.vue 代码内容&#xff0c;如下&#xff1a; <template><div><el-table:data"dataTableData"style"width: 100%"…

神经网络与深度学习——第3章 线性模型

本文讨论的内容参考自《神经网络与深度学习》https://nndl.github.io/ 第3章 线性模型 线性模型 线性模型&#xff08;Linear Model&#xff09;是机器学习中应用最广泛的模型&#xff0c;指通过样本特征的线性组合来进行预测的模型&#xff0c;给定一个 D D D维样本 x [ x …

定时器与PWM的LED控制

目录 一、基础概念定时器定时器类型定时器特性 PWM定义占空比原理 二、实验1.LED周期性亮灭定时器TIM2配置GPIO引脚设置工程相关参数配置Keil编写程序 2.LED呼吸灯(PWM)呼吸灯原理Keil编写程序Keil虚拟示波器&#xff0c;观察 PWM输出波形设置点击setup&#xff0c;并设置观察引…

贪心算法拓展(反悔贪心)

相信大家对贪心算法已经见怪不怪了&#xff0c;但是一旦我们的决策条件会随着我们的步骤变化&#xff0c;我们该怎么办呢&#xff1f;有没有什么方法可以反悔呢&#xff1f; 今天就来讲可以后悔的贪心算法&#xff0c;反悔贪心。 https://www.luogu.com.cn/problem/CF865Dhttp…

[图的搜索]5.图解狄克斯特拉算法及其代码演示

狄克斯特拉算法 与前面提到的贝尔曼-福特算法类似&#xff0c;狄克斯特拉&#xff08;Dijkstra&#xff09;算法也是求解最短路径问题的算法&#xff0c;使用它可以求得从起点到终点的路径中权重总和最小的那条路径路径。 图解 01 这里我们设A为起点、G为终点&#xff0c;来讲…

“揭秘乐园通行证:Spring JWT的魔法之旅

嗨&#xff0c;我将带你深入了解如何利用JWT打造一个既安全又高效的网络乐园。从基础概念到实战技巧&#xff0c;再到安全策略&#xff0c;每一步都充满惊喜。你将学会如何为乐园设置无状态的门票系统&#xff0c;如何通过RBAC和ABAC确保游客安全&#xff0c;以及如何在微服务架…

统计信号处理-匹配滤波器实现与验证(matlab仿真)

什么是匹配滤波器 匹配滤波器是一种信号处理技术&#xff0c;它用于从噪声中提取信号&#xff0c;特别是在信号与噪声比率较低的情况下。匹配滤波器之所以存在&#xff0c;是因为它在信号检测和估计方面具有几个关键的优势&#xff1a; 最大化信噪比&#xff1a;匹配滤波器设计…

数字化校园建设让学习更加广阔

校园构建数字化校园的亮点是什么&#xff1f;校园以智能服务、才智办理、数字讲堂为中心内容的智慧校园建造&#xff0c;不只使师生作业和日子更高效&#xff0c;并且使他们有更多的时刻投入到智能教育和智能学习中去&#xff0c;进步教育质量&#xff0c;使学生走出校门时紧跟…