【数据结构】线性表——栈与队列

写在前面

栈和队列的关系链表和顺序表的关系差不多,不存在谁替代谁,只有双剑合璧才能破敌万千~~😎😎


文章目录

  • 写在前面
  • 一、栈
    • 1.1栈的概念及结构
    • 1.2、栈的实现
      • 1.2.1、栈的结构体定义
      • 1.2.2、栈的初始化栈
      • 1.2.3、入栈
      • 1.2.4、出栈
      • 1.2.5、 获取栈顶元素
      • 1.2.6、 获取栈中有效元素个数
      • 1.2.7、检测栈是否为空,如果为空返回非零结果,如果不为空返回0
      • 1.2.8、销毁栈
  • 二、队列
    • 2.1队列的概念及结构
    • 2.2、队列的实现
      • 2.2.1、队列的结构体定义
      • 2.2.2、初始化队列
      • 2.2.3、队尾入队列
      • 2.2.4、队头出队列
      • 2.2.5、检测队列是否为空,如果为空返回非零结果,如果非空返回0
      • 2.2.6、获取队列头部元素
      • 2.2.7、获取队列队尾元素
      • 2.2.8、获取队列中有效元素个数
      • 2.2.9、销毁队列
  • 三、栈与队列的相互实现
    • 3.1、栈模拟实现队列
    • 3.2、队列模拟实现栈
  • 四、循环队列
    • 4.1、循环队列的结构体定义
    • 4.2、循环队列的初始化
    • 4.3、循环队列的判断是否为空
    • 4.4、循环队列的判断是否满了
    • 4.5、循环队列的入队列
    • 4.6、循环队列的出队列
    • 4.7、循环队列获取头队列元素
    • 4.7、循环队列获取尾队列元素
    • 4.7、循环队列的销毁


一、栈

1.1栈的概念及结构

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

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶

在这里插入图片描述ps:图片来源51CTO

1.2、栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。

  • 栈的实现逻辑与顺序表的大差不差,只是需要规定规定入队列与出队列需要遵守遵守后进先出即可

1.2.1、栈的结构体定义

typedef int stackData;
typedef struct stack
{
	stackData* val;
	int size;
	int cakacity;
}stack;
  • typedef int stackData;把顺序表结构的类型重命名为:stackData。若将来如果要改变数据栈内容的结构类型。可以极为方便的改变。
  • 在结构体中定义了。栈的总体大小和当前容量。以便确认是否需要扩容。
  • 定义的stackData*用于接收动态开辟的内存。
  • size是栈空间的大小。
  • capacity是栈空间当前的元素数量。

1.2.2、栈的初始化栈

void SKinit(stack* head) {
	head->val = (stackData*)malloc(sizeof(stackData) * 4);
	assert(head->val);
	head->size = 4;//栈存储空间的大小
	head->top = 0;//top是栈顶元素的下一个位置
}
  • 因为栈的结构体是用户自己开辟没有进行栈存储空间内存划分的。所以需要把栈的表存储空间进行初始化。
  • 默认开辟两个空间。
  • 在成功开辟后。把size设置为2。top 设置为0(此时栈空间内没有存储有效数据)。

1.2.3、入栈

void SKpush(stack* pHead, stackData x) {
	assert(pHead);
	stack* head = pHead;
	if (head->top == head->size) {//判断是否需要扩容
		stackData* p1 = (stackData*)realloc(head->val,sizeof(stackData) * head->size * 2);
		assert(p1);
		head->val = p1;
		head->size *= 2;
	}
	head->val[head->top] = x;
	head->top++;

}
  • 与顺序表尾插增添并无二样。

1.2.4、出栈

stackData SKPop(stack* pHead) {
	assert(pHead);

	stack* head = pHead;
	stackData date = head->val[head->top - 1];
	head->top--;
	return date;
}
  • 只要把当前数据。进行减减。无需把栈中的内容进行删除。因为再次入栈数据,就会把之前的数据覆盖。

1.2.5、 获取栈顶元素

stackData StackTop(stack* ps) {
	assert(ps);

	return ps->val[ps->top - 1];
}
  • 访问数组而已。
  • 因为top是栈顶元素的下一个元素,所以top-1就是栈顶当前元素的下标。

1.2.6、 获取栈中有效元素个数

int SKsize(stack* pHead) {
	return pHead->top;
}
  • 只需要返回top就知道栈空间有多少元素了

1.2.7、检测栈是否为空,如果为空返回非零结果,如果不为空返回0

int SKEmpty(stack* pHead) {
	return pHead->top == 0;
}
  • top也代表栈空间有多少元素,只需要把top0比较即可。

1.2.8、销毁栈

void SKdestory(stack* pHead) {
	while (pHead->top) {
		SKPop(pHead);
	}
}
  • 遍历出栈即可完成销毁。

二、队列

2.1队列的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列是先进先出FIFO(First In First Out)的原则。

  • 入队列:进行插入操作的一端称为队尾
  • 出队列:进行删除操作的一端称为队头

在这里插入图片描述

2.2、队列的实现

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

在这里插入图片描述
即:在单链表中只能使用尾插和头删进行数据的更改

2.2.1、队列的结构体定义

typedef int queData;
typedef struct QueNode {
	queData data;
	struct qnode* next;
}qnode;
typedef struct Queue {
	qnode* head;
	qnode* tail;
}que;
  • typedef int queData;链表结构的类型重命名为:queData。若将来如果要改变链表内容的结构类型,就可以极为方便的改变。
  • 在结构体QueNode中定义了链表存储的内容存储下个节点的指针
  • 为了更方便使用链表结构体,把链表结构体重命名为qnode
  • 在定义完成链表的结构体后,我们需要定义队列的结构体,用来确保每次访问队列只能访问到链表的头节点尾节点
  • 队列结构体struct Queue是用来管理链表的,所以成员变量类型都是qnode*head代表头节点,tail代表尾节点。

2.2.2、初始化队列

void QueInit(que** queue) {
	que* p1 = (que*)malloc(sizeof(que));
	assert(p1);
	p1->head = NULL;
	p1->tail = NULL;
	*queue = p1;
}
  • 动态开辟一个空队列。先把队列设置为空,把前后指针设置为NULL

2.2.3、队尾入队列

void QuePush(que* queue, queData x) {
	assert(queue);
	qnode* p1 = (qnode*)malloc(sizeof(qnode));
	assert(p1);
	p1->next = NULL;
	p1->data = x;
	if (queue->head == NULL) {
		queue->head = p1;
		queue->tail = p1;
	}
	else {
		queue->tail->next = p1;
		queue->tail = p1;
	}
	
}
  • 首先动态开辟一个链表节点。
  • 把入队节点的next赋为空值,并且把需要插入的x值传递给新开辟的链表节点p1->data。完成入队数据的处理。
  • 在入队列之前,我们需要判断队列是否为NULL,如NULL说明队列的链表里面目前没有一个节点,那就需要把p1赋值head作为队列的队头
  • 判断了队列不为NULL,则直接把队尾的next指向p1,之后更新队尾完成入队。

2.2.4、队头出队列

qnode* QuePop(que* queue) {
	assert(queue);
	if (queue->head == NULL) {
		return NULL;
	}
	qnode* p1 = queue->head;
	queue->head = queue->head->next;
	if (queue->head == NULL) {
		queue->tail = NULL;
	}
	return p1;
}
  • 在出队列之前,我们需要判断队列是否为NULL,如NULL说明队列的链表里面目前没有一个节点,那就需返回NULL即可。
  • 判断了队列不为NULL,用一个指针p1记录原对头,之后把队头的head指针指向next节点更新新队头后返回指针p1 完成出队。
  • 如果把队列的节点全部出完后,就需要把headtail赋为NULL

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

bool QueEmpty(que* queue) {
	assert(queue);
	if (queue->head == NULL && queue->tail == NULL) {
		return true;
	}
	return false;
}
  • 如果队头和队尾指针同时NULL则说明队列为空。否则肯定不为空。

2.2.6、获取队列头部元素

queData QueueFront(que* queue){
	assert(queue);
	assert(!QueEmpty(queue));
	return queue->head->data;
}
  • 直接返回队头的data

2.2.7、获取队列队尾元素

queData QueueBack(que* queue) {
	assert(queue);
	assert(!QueEmpty(queue));//如果队列为空还哪有说明头部元素
	return queue->tail->data;
}
  • 直接返回队尾的data

2.2.8、获取队列中有效元素个数

int QueSize(que* queue) {
	assert(queue);
	qnode* p1 = queue->head;
	int sz = 0;
	while (p1 != NULL) {
		sz++;
		p1 = p1->next;
	}
	return sz;
}
  • 遍历++即可

2.2.9、销毁队列

void QueDestory(que* queue) {
	assert(queue);
	while (!QueEmpty(queue)) {
		free(QuePop(queue));
	}
}
  • 循环出队列即可。

三、栈与队列的相互实现

3.1、栈模拟实现队列

  • 队列的特性是先入先出。即:FIFO(First In First Out)的原则。
  • 栈的特性是后入先出。即:LIFO(Last In First Out)的原则。
  • 我们可以利用两栈后入先出的特性,先用栈1把入队列的值存起来,在存起来后,就把栈1的值出栈到栈2中进行保存,这样就完成栈中底部的值变为顶部的值了。(如下图)在这里插入图片描述
  • 在上图中可以看到,当栈2出栈的时候,就变成了栈1底部的数据先出,即:先入先出
  • 所以我们只需要创建两个栈一个栈负责入数据栈1一个栈负责出数据栈2,为了不影响先后顺序,我们必须等到栈2为空的时候才栈1的数据导入进去。

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int stackData;
typedef struct stack
{
	stackData* val;
	int size;
	int top;
}stack;//栈的结构体

typedef struct {
    stack* stack1;//接收数据
    stack* stack2;//出数据
} MyQueue;//栈模拟的队列

//栈函数的实现
void SKinit(stack** head) {
	*head = (stack*)malloc(sizeof(stack));
	assert(*head);
	(*head)->val = (stackData*)malloc(sizeof(stackData) * 4);
	assert((*head)->val);
	(*head)->size = 4;
	(*head)->top = 0;
}
//入栈
void SKpush(stack* pHead, stackData x) {
	assert(pHead);
	stack* head = pHead;
	if (head->top == head->size) {
		stackData* p1 = (stackData*)realloc(head->val,sizeof(stackData) * head->size * 2);
		assert(p1);
		head->val = p1;
		head->size *= 2;
	}
	head->val[head->top] = x;
	head->top++;

}
//出栈
stackData SKPop(stack* pHead) {
	assert(pHead);

	stack* head = pHead;
	stackData date = head->val[head->top - 1];
	head->top--;
	return date;
}
//销毁
void SKdestory(stack* pHead) {
	while (pHead->top) {
		SKPop(pHead);
	}
}
//大小
int SKsize(stack* pHead) {
	return pHead->top;
}
//判断为空
int SKEmpty(stack* pHead) {
	return pHead->top == 0;
}
stackData StackTop(stack* ps) {
	assert(ps);

	return ps->val[ps->top - 1];
}

//队列函数的实现

// 创建一个队列
MyQueue* myQueueCreate() {
    MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));  // 为队列分配内存
    SKinit(&obj->stack1);  // 初始化栈1,用于接收数据
    SKinit(&obj->stack2);  // 初始化栈2,用于出数据
    return obj;  // 返回队列对象
}

// 入队操作,将元素x压入栈1
void myQueuePush(MyQueue* obj, int x) {
    SKpush(obj->stack1, x);  // 直接将元素压入栈1
}

// 出队操作,从栈2中弹出元素
int myQueuePop(MyQueue* obj) {
    // 如果栈2为空,将栈1中的元素移动到栈2
    if (obj->stack2->top == 0) {
        // 从栈1依次弹出元素并压入栈2
        while (!SKEmpty(obj->stack1)) {
            stackData n = SKPop(obj->stack1);
            SKpush(obj->stack2, n);
        }
    }
    // 从栈2中弹出元素
    return SKPop(obj->stack2);
}

// 查看队列头部元素
int myQueuePeek(MyQueue* obj) {
    // 如果栈2为空,将栈1中的元素移动到栈2
    if (obj->stack2->top == 0) {
        while (!SKEmpty(obj->stack1)) {
            stackData n = SKPop(obj->stack1);
            SKpush(obj->stack2, n);
        }
    }
    // 返回栈2的栈顶元素
    return StackTop(obj->stack2);
}

// 判断队列是否为空
bool myQueueEmpty(MyQueue* obj) {
    return (obj->stack2->top == 0) && (obj->stack1->top == 0);  // 栈1和栈2都为空时,队列为空
}

// 销毁队列,释放所有资源
void myQueueFree(MyQueue* obj) {
    // 一直出队直到队列为空
    while (!myQueueEmpty(obj)) {
        myQueuePop(obj);
    }

    // 释放栈1和栈2的内存
    free(obj->stack1->val);
    free(obj->stack2->val);
    free(obj->stack1);
    free(obj->stack2);
    
    // 释放队列对象
    free(obj);
}

3.2、队列模拟实现栈

  • 队列的特性是先入先出。即:FIFO(First In First Out)的原则。
  • 栈的特性是后入先出。即:LIFO(Last In First Out)的原则。
  • 我们可以利用两个队列进行互相入队,把队列中的最后一个元素进行返回,这样就完成栈的先入先出的原则了。(如下图)在这里插入图片描述
  • 至此,再下一次出栈时候只需要判断哪个队列不为空,就循环出队列到另一个队列中,这样就可以模拟实现栈了。

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

// 定义栈中存储的数据类型
typedef int stackData;

// 栈的结构体定义
typedef struct stack {
    stackData* val;  // 动态数组,用来存储栈的元素
    int size;        // 栈的容量
    int top;         // 栈顶元素的索引
} stack;

// 初始化栈函数
void SKinit(stack** head) {
    *head = (stack*)malloc(sizeof(stack));
    assert(*head);  // 确保内存分配成功
    (*head)->val = (stackData*)malloc(sizeof(stackData) * 4);
    assert((*head)->val);  // 确保内存分配成功
    (*head)->size = 4;
    (*head)->top = 0;
}

// 入栈操作
void SKpush(stack* pHead, stackData x) {
    assert(pHead);  // 确保栈指针有效
    stack* head = pHead;
    // 如果栈满,进行扩容
    if (head->top == head->size) {
        // 重新分配内存,栈容量加倍
        stackData* p1 = (stackData*)realloc(head->val, sizeof(stackData) * head->size * 2);
        assert(p1); 
        head->val = p1; 
        head->size *= 2;  
    }
    head->val[head->top] = x;
    head->top++;  
}

// 出栈操作
stackData SKPop(stack* pHead) {
    assert(pHead);  // 确保栈指针有效
    stack* head = pHead;
    stackData date = head->val[head->top - 1];
    head->top--;
    return date;
}

// 销毁栈,释放栈中所有数据
void SKdestory(stack* pHead) {
    while (pHead->top) {
        SKPop(pHead);  // 依次出栈,直到栈为空
    }
}

// 获取栈的大小(栈中的元素个数)
int SKsize(stack* pHead) {
    return pHead->top;  // 栈顶指针即为栈的大小
}

// 判断栈是否为空
int SKEmpty(stack* pHead) {
    return pHead->top == 0;  // 如果栈顶指针为0,则栈为空
}

// 获取栈顶元素
stackData StackTop(stack* ps) {
    assert(ps);  // 确保栈指针有效
    return ps->val[ps->top - 1];  // 返回栈顶元素
}

// 队列结构体定义
typedef struct {
    stack* stack1;  // 用于接收数据(入队)
    stack* stack2;  // 用于出数据(出队)
} MyQueue;

// 创建队列
MyQueue* myQueueCreate() {
    // 为队列分配内存
    MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
    
    // 初始化两个栈
    SKinit(&obj->stack1);  // 用于接收数据
    SKinit(&obj->stack2);  // 用于出数据
    
    return obj;  // 返回队列对象
}

// 入队操作,将元素x压入栈1
void myQueuePush(MyQueue* obj, int x) {
    SKpush(obj->stack1, x);  // 将数据压入栈1
}

// 出队操作,从栈2弹出元素
int myQueuePop(MyQueue* obj) {
    // 如果栈2为空,将栈1中的元素转移到栈2
    if (obj->stack2->top == 0) {
        // 将栈1中的元素依次弹出并压入栈2
        while (!SKEmpty(obj->stack1)) {
            stackData n = SKPop(obj->stack1);
            SKpush(obj->stack2, n);
        }
    }
    // 从栈2中弹出元素并返回
    return SKPop(obj->stack2);
}

// 查看队列头部元素
int myQueuePeek(MyQueue* obj) {
    // 如果栈2为空,将栈1中的元素转移到栈2
    if (obj->stack2->top == 0) {
        while (!SKEmpty(obj->stack1)) {
            stackData n = SKPop(obj->stack1);
            SKpush(obj->stack2, n);
        }
    }
    // 返回栈2的栈顶元素
    return StackTop(obj->stack2);
}

// 判断队列是否为空
bool myQueueEmpty(MyQueue* obj) {
    return (obj->stack2->top == 0) && (obj->stack1->top == 0);  // 栈1和栈2都为空时,队列为空
}

// 销毁队列,释放所有资源
void myQueueFree(MyQueue* obj) {
    // 一直出队直到队列为空
    while (!myQueueEmpty(obj)) {
        myQueuePop(obj);
    }

    // 释放栈1和栈2的内存
    free(obj->stack1->val);
    free(obj->stack2->val);
    free(obj->stack1);
    free(obj->stack2);

    // 释放队列对象
    free(obj);
}

四、循环队列

在操作系统的生产者消费者模型中可以就会使用循环队列环形队列可以使用数组实现,也可以使用循环链表实现。

因为是环形队列,所以不存在扩容队列的情况,本篇笔记以数组实现为例。
以数组为数据存储模型比链表实现方便的多。

4.1、循环队列的结构体定义

typedef struct {
    int* a;
    int front;
    int rear;
    int k;//开辟的数组大小
} MyCircularQueue;
  • 首先创建一个数组,用来存储队列的数据。

  • 定义两个指针,用来指向队列数据中的队头和队尾。

  • front:只需要保存队头元素在数组中的下标即可

  • rear:保存队尾元素的下一个下标,这样可以保证队列位空时rearfront的下标相同,队列满时frontrear的下一位。如下图在这里插入图片描述

  • 为了达到上图的效果,需要在初始化时候把数组开大1个空间。多开的空间作为数组的预留位置。

  • 定义一个整形变量k用来保存队列大小。

4.2、循环队列的初始化

MyCircularQueue* myCircularQueueCreate(int k) {//传入开辟队列的大小
    MyCircularQueue* que = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    assert(que);
    que->a = (int*)malloc(sizeof(int) * (k + 1));
    que->k = k;

    que->front = 0;
    que->rear = 0;
    return que;
}
  • 动态开辟一个队列结构体
  • 动态开辟一个队列空间,其中队列空间需要比k大一个1
  • 并且把结构体中的其他值进行初始化赋值

4.3、循环队列的判断是否为空

bool IsEmpty(MyCircularQueue* obj) {
    return (obj->front == obj->rear);
}
  • 根据我们设计逻辑,只有obj->front == obj->rear时候队列位空

4.4、循环队列的判断是否满了

bool IsFull(MyCircularQueue* obj) {
    return (obj->rear + 1) % (obj->k + 1) == obj->front;
}
  • 根据我们设计逻辑,如果rear + 1front的情况就说明队列满了。

  • 解析(obj->rear + 1) % (obj->k + 1) == obj->front假设当前容量是:3。k == 3):

    • 第一种只入队列且未满的情况
      • 只入队列的情况下说明front值是未进行改变的,且rear值是最后元素下标的下一位(如下图)。在这里插入图片描述这时候判断,队列是否未满,只需要(rear +1) % (k + 1)== front小数模大数结果还是本身
    • 第二种只入队列且已经满的情况
      • 只入队列的情况下说明front值是未进行改变的,且rear值是最后元素下标的下一位(如下图)。在这里插入图片描述在队列满的情况下,当前的rear的值是:3rear + 1就超出了数组的大小(如下图)在这里插入图片描述但是在循环队列rear + 1是代表着数组原始起点的(如下逻辑图)在这里插入图片描述为了达到rear+1就能回到数组起始位置,进行rear+1 % k + 1rear+1的值:4k+1的值也为:4。相同的值同时模的到的值是0。此时我们只需要判断下标0处是否也是front的值就可以判断出队列是否为满了(obj->rear + 1) % (obj->k + 1) == obj->front。(如上图)
    • 第三种入满队列后了一些且未满的情况
      • 此时队列的front元素进行了改变,不再是0下标的值,此时我们可以和第二种结合理解。先画出此时的数组的物理结构图逻辑结构图。(如下图)在这里插入图片描述为了使rear + 1回到逻辑结构中的下一个位置,我们使用第二种中介绍的方法,使用(obj->rear + 1) % (obj->k + 1)方法,在把rear + 1回到数组起始位置,之后判断此时的front是否也是此下标。

4.5、循环队列的入队列

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if (myCircularQueueIsFull(obj)) {
        return false;
    }
    else {
        obj->a[obj->rear] = value;
        obj->rear++;
        obj->rear = (obj->rear) % (obj->k + 1);
        return true;
    }
}

  • 入队列返回结果:如果成功插入则返回真
  • 入队列前,需要检查队列是否为满。
  • 如果队列不为满,只需要在rear位置中,存放插入的数据即可。
  • 在每次插入完成后,rear数据都需要进行逻辑后移一位。在逻辑后移中,需要分成2中情况
    • 第一种rear不在数组最后一位(如下图)在这里插入图片描述此时小数模大数,结果还是本身。不受到(obj->rear) % (obj->k + 1)的影响。
    • 第二种rear在数组最后一位(如下图)在这里插入图片描述rear+1的值:4k+1的值也为:4。相同的值同时模的到的值是0(obj->rear) % (obj->k + 1)即:把rear的值重新返回到数组头部。(如下图)
      在这里插入图片描述
  • 只有每次都对rear取模才能完成循环队列的逻辑。

4.6、循环队列的出队列

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (IsEmpty(obj)) {
        return false;
    }
    else {
        obj->front++;
        obj->front = (obj->front) % (obj->k + 1);
        return true;
    }
}
  • 出队列返回结果:如果成功出队则返回真
  • 出队列前,需要检查队列是否为空。
  • 出队列需要把front下标++,把队列的队头在数组中往后移动一个位置,这样就完成了出队。
  • front在每次插入完成后,front数据都需要进行逻辑后移一位。在逻辑后移中,也分成2中情况,但是这两种情况与入队列的read相同,这里不做详细说明。

4.7、循环队列获取头队列元素

int myCircularQueueFront(MyCircularQueue* obj) {
    if (IsEmpty(obj)) {
        return -1;
    }
    return obj->a[obj->front];
}
  • 先判断是否为空,不为空直接返回队头元素即可。

4.7、循环队列获取尾队列元素

int myCircularQueueRear(MyCircularQueue* obj) {
    if (IsEmpty(obj)) {
        return -1;
    }
    //return obj->a[obj->rear - 1];
    return obj->a[((obj->rear - 1) + (obj->k + 1)) % (obj->k+1)];
}

  • 正常来说是:先判断是否为空,不为空直接返回rear - 1的位置元素即可。

  • 但是有一种特殊情况即:rear是数组首元素下标时,rear - 1就造成了越界访问。在这里插入图片描述

  • 为了避免越界访问的情况,必须确保在数组首元素时,返回的是数组尾元素的值(可以使用分支语句处理)。这里使用取模处理:(k = 3)

    • 我们知道元素本身模本身的结果为:0,并且小数模大数结果为:小数本身
    • 所以当rear-1时,如果在非首元素位置中,通过分配率((obj->rear - 1) + (obj->k + 1)) % (obj->k+1)可以变成(obj->rear - 1) % (obj->k+1),结果是obj->rear - 1本身
    • 如果在非首元素位置中时,(obj->rear - 1) + (obj->k + 1)的值:-1 + 4 == 3。这时候我们用得到的结果对k+1进行取模就变成了小数模大数结果为:小数本身。这时候rear的结果是数组尾元素的下标返回的结果就是队列的尾节点元素在这里插入图片描述

4.7、循环队列的销毁

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}
  • 干脆利落的销毁了用于存储队列元素的数组队列结构体的两个动态开辟空间~

以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖 如果对大家有帮助的话,就请多多为我点赞收藏吧~~~💖💖
请添加图片描述

ps:表情包来自网络,侵删🌹

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

科技改变工作方式:群晖NAS安装内网穿透实现个性化办公office文档分享(1)

文章目录 前言1. 本地环境配置2. 制作本地分享链接3. 制作公网访问链接4. 公网ip地址访问您的分享相册5. 制作固定公网访问链接 前言 本文将详细介绍如何在群晖NAS上安装Synology Office和Synology Drive Server&#xff0c;并利用Cpolar内网穿透工具为本地文档配置固定的公网…

无插件H5播放器EasyPlayer.js网页web无插件播放器选择全屏时,视频区域并没有全屏问题的解决方案

EasyPlayer.js H5播放器&#xff0c;是一款能够同时支持HTTP、HTTP-FLV、HLS&#xff08;m3u8&#xff09;、WS、WEBRTC、FMP4视频直播与视频点播等多种协议&#xff0c;支持H.264、H.265、AAC、G711A、MP3等多种音视频编码格式&#xff0c;支持MSE、WASM、WebCodec等多种解码方…

rocketmq5源码系列--(一)--搭建调试环境

说在前头&#xff1a;阿里的rocketmq的文档是真他妈的烂的1b&#xff0c;很多东西都不说&#xff0c;全靠自己看源码&#xff0c;摸索&#xff0c;草&#xff0c;真的要吐血了 rocketmq的版本5而不是版本4&#xff0c;版本5比版本4多了个proxy rocketmq5 三个组件&#xff1a;…

【网页设计】CSS3 进阶(动画篇)

1. CSS3 2D 转换 转换&#xff08;transform&#xff09;是CSS3中具有颠覆性的特征之一&#xff0c;可以实现元素的位移、旋转、缩放等效果 转换&#xff08;transform&#xff09;你可以简单理解为变形 移动&#xff1a;translate旋转&#xff1a;rotate缩放&#xf…

django安装与项目创建

一、安装 在终端输入 pip install django //或者(&#xff09;指定安装版本 pip install django2.2 二、创建项目 2.1创建项目 django-admin startproject 项目名 2.2Django 项目中的关键文件 _init_.py:将目录标识为python包setting.py:核心配置文件&#xff0c;定义项目…

【redis】—— 初识redis(redis基本特征、应用场景、以及重大版本说明)

序言 本文将引导读者探索Redis的世界&#xff0c;深入了解其发展历程、丰富特性、常见应用场景、使用技巧等&#xff0c;最后会对Redis演进过程中具有里程碑意义的版本进行详细解读。 目录 &#xff08;一&#xff09;初始redis &#xff08;二&#xff09;redis特性 &#…

SpringBoot学习记录(三)之多表查询

SpringBoot学习记录&#xff08;三&#xff09;之多表查询 一、多表查询概述1、数据准备2、介绍3、分类 二、内连接三、外连接四、子查询1、标量子查询2、列子查询3、行子查询4、表子查询 三、案例1、准备环境2、需求实现3、&#xff08;附&#xff09;数据准备 一、多表查询概…

泰矽微重磅发布超高集成度车规触控芯片TCAE10

市场背景 智能按键和智能表面作为汽车智能化的重要部分&#xff0c;目前正处于快速发展阶段&#xff0c;电容式触摸按键凭借其操作便利性与小体积的优势&#xff0c;在汽车内饰表面的应用越来越广泛。对于空调控制面板、档位控制器、座椅扶手、门饰板、车顶控制器等多路开关的…

HarmonyOs学习笔记-布局单位

鸿蒙开发中布局存在很多单位 鸿蒙的默认单位是vp 下方先展示一下在RrkTsUI中我们应该怎么书写&#xff0c;然后讲一下各大单位具体的含义。 Text("这是一个文本, 用默认单位进行展示&#xff0c;也就是vp") .width(100) .height(100);//此段代码与上方代码是一样的…

操作系统实验 C++实现生产者-消费者问题

实验目的 1、进一步加深理解进程同步的概念 2、加深对进程通信的理解 3、了解Linux下共享内存的使用方法 实验内容 1、按照下面要求&#xff0c;写两个c程序&#xff0c;分别是生产者producer.c以及customer.c 2、一组生产者和一组消费者进程共享一块环形缓冲区 使用共…

Easyexcel(1-注解使用)

文章链接&#xff1a; Easyexcel&#xff08;1-注解使用&#xff09; 版本依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.3</version> </dependency>ExcelProperty 指定…

最新版xAI LLM 模型Grok-2 上线

xAI&#xff01;Grok-2 最新版开启公测&#xff01;”。这是我注册成功的截图&#xff0c;使用国内的邮箱就可以注册使用了&#xff01; Grok API公测与免费体验: Grok API开启公测&#xff0c;提供免费体验128k上下文支持&#xff0c;。Grok-Beta与马斯克: 马斯克庆祝特朗普当…

css数据不固定情况下,循环加不同背景颜色

<template><div><p v-for"(item, index) in items" :key"index" :class"getBackgroundClass(index)">{{ item }}</p></div> </template><script> export default {data() {return {items: [学不会1, …

MySQL的聚簇索引和二级索引

索引按照物理实现方式&#xff0c;索引可以分为 2 种&#xff1a;聚簇&#xff08;聚集&#xff09;和非聚簇&#xff08;非聚集&#xff09;索引。也可以把非聚集索引称为二级索引或者辅助索引。 一.聚簇索引 聚簇索引并不是一种单独的索引类型&#xff0c;而是一种数据存储方…

【Pytorch】torch.nn.functional模块中的非线性激活函数

在使用torch.nn.functional模块时&#xff0c;需要导入包&#xff1a; from torch.nn import functional 以下是常见激活函数的介绍以及对应的代码示例&#xff1a; tanh (双曲正切) 输出范围&#xff1a;(-1, 1) 特点&#xff1a;中心对称&#xff0c;适合处理归一化后的数据…

神经网络11-TFT模型的简单示例

Temporal Fusion Transformer (TFT) 是一种用于时间序列预测的深度学习模型&#xff0c;它结合了Transformer架构的优点和专门为时间序列设计的一些优化技术。TFT尤其擅长处理多变量时间序列数据&#xff0c;并且能够捕捉到长期依赖关系&#xff0c;同时通过自注意力机制有效地…

学习threejs,使用TWEEN插件实现动画

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.PLYLoader PLY模型加…

世界坐标系、相机坐标系、图像物理坐标系、像素平面坐标系

坐标系及其转换在计算机视觉领域占据核心地位。理解如何从一个坐标系转换到另一个坐标系&#xff0c;不仅是理论上的需要&#xff0c;也是实际应用中不可或缺的技能。 一、世界坐标系的定义 世界坐标系是一个全局的坐标系统&#xff0c;用于定义场景中物体的位置。在这个坐标…

03-axios常用的请求方法、axios错误处理

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…

Redis/Codis性能瓶颈揭秘:网卡软中断的影响与优化

目录 现象回顾 问题剖析 现场分析 解决方案 总结与反思 1.调整中断亲和性&#xff08;IRQ Affinity&#xff09;&#xff1a; 2.RPS&#xff08;Receive Packet Steering&#xff09;和 RFS&#xff08;Receive Flow Steering&#xff09;&#xff1a; 近期&#xff0c;…