数据结构初阶之栈和队列(C语言版)

加粗样式

数据结构初阶之栈和队列(C语言版)

  • ✍栈
    • ♈栈的结构设计
    • ♈栈的各个接口的实现
      • 👺StackInit(初始化)
      • 👺push(入栈)
      • 👺pop(出栈)
      • 👺获取栈顶元素
      • 👺获取栈中有效元素的个数
      • 👺判断栈是否为空
      • 👺销毁栈
  • ✍队列
    • 👻队列的结构的设计
    • 👻队列的各个接口实现
      • 🐷Init(初始化队列)
      • 🐷队尾入队列
      • 🐷队头出队列
      • 🐷获取队列队头元素
      • 🐷获取队列队尾元素
      • 🐷判断队列是否为空
      • 🐷获取队列的有效元素个数
      • 🐷销毁队列
  • ✍OJ题之用两个队列实现栈
  • ✍OJ题之用两个栈实现队列
  • ⭕总结

✍栈

栈是数据结构的一种,一个栈可以用来对数据进行增删查改,但是它遵循一个原则,就是数据必须是后入先出,什么意思呢?就是先入栈的数据后出栈,后入栈的数据先出栈。我们可以使用链表和动态数组来实现栈。

我们使用动态数组来实现栈,为了实现后入先出,我们可以将栈看作只有尾插和尾删功能(也就是没有头插头删)的动态顺序表,其它都很相似。

♈栈的结构设计

// 支持动态增长的栈
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);

♈栈的各个接口的实现

这里我们使用动态增长的数组来实现栈尾插和尾删对应我们的入栈和出栈,利用链表实现也可以,但是动态数组操作起来更加简单,也不需要挪动数据,出栈更是只需要简单的_top--就可以完成,何乐而不为呢。

👺StackInit(初始化)

栈对象在主程序已经创建好了,传结构体指针可以修改结构体里面的成员,初始化栈只需要把动态数组、栈的元素个数、栈的空间大小初始化就可以了。

// 初始化栈 
void StackInit(Stack* ps)
{ps->_a = NULL;ps->_top = ps->_capacity = 0;
}

👺push(入栈)

入栈其实就是动态顺序表里的尾插,如果小伙伴有疑惑,可以看数据结构初阶之顺序表(C语言实现)

// 入栈 
void StackPush(Stack* ps, STDataType data)
{assert(ps);if (ps->_top == ps->_capacity)//判断是否需要扩容{STDataType* tmp = ps->_capacity == 0 ? (STDataType*)realloc(ps->_a, sizeof(STDataType) * 4) : (STDataType*)realloc(ps->_a, sizeof(STDataType)* (ps->_capacity * 2));if (tmp == NULL){perror("realloc Failed");exit(-1);}ps->_a = tmp;ps->_capacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;}ps->_a[ps->_top++] = data;//尾插新的元素,_top++
}

上述代码用到了三目操作符,如果不知道这个操作符的话可能就看不懂代码,这里博主来简单的说一下。它的构成是这样的:

表达式1?语句1: 语句二;
如果表达式1成立则执行语句1,否则执行语句二。

👺pop(出栈)

出栈操作就更不用说了,由于入栈是尾插由于要满足栈后进先出的原则,我们直接把最后一个入栈的删除就可以了,等价于元素个数_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,为空返回true,反之返回false。

// 检测栈是否为空,如果为空返回非零结果,如果不为空返回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 = 0;ps->_top = 0;
}

✍队列

队列也是数据结构的一种,它和栈很像,区别就是队列数据必须是先进先出,什么意思呢?就是先入队列的数据先出队列,后入队列的数据后出队列。我们可以使用链表和动态数组来实现栈。

这里我们使用链表来实现队列,为了实现队列先入先出的功能,我们可以将我们实现的队列看成出数据只能头删、入数据只能尾插的单链表。

可能有小伙伴说,那我将最后面的数据视作队头,最前面的数据视作队尾,利用单链表的头插和尾删也可以实现队列呀,这也是可行的,只要保证先入先出的原则就可以,下面的图是两者的区别。

在这里插入图片描述

无论怎么样,要保证先入先出,队头始终是先进队列的。这里可能就有小伙伴突发奇想了,既然队列可以这样,栈可不可以把栈顶放在动态数组的左边去,使用头插和头删来维护栈呢?由于动态顺序表的头删和头插都需要挪动数据,时间开销太大,我们一般不这样做。

👻队列的结构的设计

typedef  int QDataType;//typedef重命名数据类型,下次改数据类型时改这个地方就可以了。
// 链式结构:表示队列 
typedef struct QListNode
{struct QListNode* _next;QDataType _data;
}QNode;// 队列的结构 
typedef struct Queue
{QNode* _front;QNode* _rear;
}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);// 销毁队列 
n'/void QueueDestroy(Queue* q);

👻队列的各个接口实现

队列用到了很多基础的链表的知识,如果你不太懂链表,请看这篇博客。

🐷Init(初始化队列)

初始化,将头节点指针和尾节点置空就可以了。

// 初始化队列 
void QueueInit(Queue* q)
{assert(q);q->_front = NULL;q->_rear = NULL;
}

🐷队尾入队列

和单链表里的尾插的区别是,这里我们已经维护了尾节点,不用循环遍历去找尾节点了,没有数据的情况需要特殊判断一下。

// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{assert(q);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc Failed\n");exit(-1);}newnode->_data = data;newnode->_next = NULL;if (q->_rear == NULL)//没有节点的情况{q->_front = q->_rear = newnode;}else//一般情况{q->_rear->_next = newnode;q->_rear = newnode;}
}

🐷队头出队列

等价于链表中的头删,但是需要特判一下只有一个数据的情况。

// 队头出队列 
void QueuePop(Queue* q)
{assert(q);assert(q->_front);QNode* new_front = q->_front->_next;free(q->_front);q->_front = new_front;if (q->_front == NULL)//特判一下一个节点的情况q->_rear = NULL;
}

🐷获取队列队头元素

直接返回头节点的节点值。

// 获取队列头部元素 
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;
}

🐷判断队列是否为空

等价于判断头节点或者尾节点是否为空。

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q)
{assert(q);return q->_front == NULL;
}

🐷获取队列的有效元素个数

循环遍历累加到临时变量中去,然后返回。

// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{assert(q);int size = 0;QNode* cur = q->_front;while (cur){size++;cur = cur->_next;}return size;
}

注意:这里计算队列的大小不能使用指针减指针,因为链表每一个节点都是随机开的空间,而不是像动态数组一样连续的空间。

🐷销毁队列

由于链表的节点的空间是一个一个开的,所以需要一个一个依次销毁,最后不要忘了将队列的两个指针置空。

// 销毁队列 
void QueueDestroy(Queue* q)
{assert(q);assert(q->_front);//如果队列已经为空,就不用销毁了QNode* cur = q->_front;while (cur){QNode* next = cur->_next;free(cur);cur = next;}q->_front = q->_rear = NULL;

下面我们用两道力扣上的简单题来熟悉一下栈和队列:

✍OJ题之用两个队列实现栈

在这里插入图片描述
这是题目链接
先贴C语言ak代码:

typedef  int QDataType;  // 定义队列中数据类型为整数// 链式结构:表示队列节点
typedef struct QListNode {struct QListNode* _next;  // 指向下一个节点的指针QDataType _data;          // 节点存储的数据
} QNode;// 队列的结构
typedef struct Queue {QNode* _front;  // 指向队列头部节点的指针QNode* _rear;   // 指向队列尾部节点的指针
} Queue;// 初始化队列
void QueueInit(Queue* q) {assert(q);  // 确保指针不为空q->_front = NULL;  // 将队列头部指针置空q->_rear = NULL;   // 将队列尾部指针置空
}// 队尾入队列
void QueuePush(Queue* q, QDataType data) {assert(q);  // 确保指针不为空// 创建新的节点并分配内存空间QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL) {perror("malloc Failed\n");exit(-1);}newnode->_data = data;  // 存储数据到新节点中newnode->_next = NULL;  // 新节点的下一个节点置空// 如果队列为空,则新节点即为队列的唯一节点if (q->_rear == NULL) {q->_front = q->_rear = newnode;} else {  // 否则将新节点接入队列尾部,并更新队列尾部指针q->_rear->_next = newnode;q->_rear = newnode;}
}// 队头出队列
void QueuePop(Queue* q) {assert(q);        // 确保指针不为空assert(q->_front); // 确保队列不为空// 移动队列头部指针至下一个节点,并释放原头部节点的内存空间QNode* new_front = q->_front->_next;free(q->_front);q->_front = new_front;// 如果队列为空,则同时更新队列尾部指针为空if (q->_front == NULL)q->_rear = NULL;
}// 获取队列头部元素
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);  // 确保指针不为空int size = 0;QNode* cur = q->_front;while (cur) {  // 遍历队列节点计算节点数目size++;cur = cur->_next;}return size;
}// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q) {assert(q);  // 确保指针不为空return q->_front == NULL;  // 判断队列是否为空
}// 销毁队列
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;
}// 定义一个栈结构,使用两个队列实现
typedef struct {Queue* q1;  // 第一个队列Queue* q2;  // 第二个队列
} MyStack;// 创建一个新的栈结构
MyStack* myStackCreate() {MyStack* obj = (MyStack*)malloc(sizeof(MyStack));  // 分配栈结构内存空间obj->q1 = (Queue*)malloc(sizeof(Queue));  // 初始化第一个队列QueueInit(obj->q1);obj->q2 = (Queue*)malloc(sizeof(Queue));  // 初始化第二个队列QueueInit(obj->q2);return obj;
}// 元素入栈
void myStackPush(MyStack* obj, int x) {Queue* noempty = obj->q1;  // 初始化非空队列指针为q1if (QueueEmpty(noempty))  // 如果q1为空,则指向q2noempty = obj->q2;QueuePush(noempty, x);  // 将元素入队到非空队列中
}// 元素出栈
int myStackPop(MyStack* obj) {Queue* noempty = obj->q1;  // 初始化非空队列指针为q1Queue* empty = obj->q2;    // 初始化空队列指针为q2if (QueueEmpty(noempty)) {  // 如果q1为空,则交换指针noempty = obj->q2;empty = obj->q1;}// 将非空队列中的数据放入空队列中,并留下最后一个数据while (QueueSize(noempty) > 1) {QueuePush(empty, QueueFront(noempty));QueuePop(noempty);}int top = QueueFront(noempty);  // 获取栈顶元素QueuePop(noempty);  // 将栈顶元素出队return top;
}// 获取栈顶元素
int myStackTop(MyStack* obj) {int top = myStackPop(obj);  // 获取栈顶元素Queue* noempty = obj->q1;   // 初始化非空队列指针为q1if (QueueEmpty(noempty))    // 如果q1为空,则指向q2noempty = obj->q2;QueuePush(noempty, top);    // 将栈顶元素重新入栈return top;
}// 检测栈是否为空
bool myStackEmpty(MyStack* obj) {return QueueEmpty(obj->q1) && QueueEmpty(obj->q2);  // 判断两个队列是否均为空
}// 释放栈结构内存空间
void myStackFree(MyStack* obj) {QueueDestroy(obj->q1);  // 销毁队列1QueueDestroy(obj->q2);  // 销毁队列2free(obj->q1);  // 释放队列1的内存空间obj->q1 = NULL;free(obj->q2);  // 释放队列2的内存空间obj->q2 = NULL;free(obj);      // 释放栈结构的内存空间
}

ak截图:

在这里插入图片描述

这里由于C语言没有现成的轮子(也就是写好的队列)给我们用,所以只能自己手撕一个队列上去,所以代码比较长,不过小伙伴如果刚开始接触编程不用担心,等以后学了C++/java,上面那段代码可以减少很多,下面是C++代码示例:

#include <queue>class MyStack {
private:std::queue<int>* q1;  // 第一个队列指针std::queue<int>* q2;  // 第二个队列指针public:// 构造函数初始化两个队列MyStack() {q1 = new std::queue<int>;q2 = new std::queue<int>;}// 元素入栈void push(int x) {std::queue<int>* noempty = q1;  // 初始化非空队列指针为q1if (q1->empty())  // 如果q1为空,则指向q2noempty = q2;noempty->push(x);  // 将元素入队到非空队列中}// 元素出栈int pop() {std::queue<int>* noempty = q1;  // 初始化非空队列指针为q1std::queue<int>* empty = q2;    // 初始化空队列指针为q2if (q1->empty()) {  // 如果q1为空,则交换指针noempty = q2;empty = q1;}// 将非空队列中的数据放入空队列中,并留下最后一个数据while (noempty->size() > 1) {empty->push(noempty->front());noempty->pop();}int top = noempty->front();  // 获取栈顶元素noempty->pop();  // 将栈顶元素出队return top;}// 获取栈顶元素int top() {int top = pop();  // 获取栈顶元素std::queue<int>* noempty = q1;  // 初始化非空队列指针为q1if (q1->empty())  // 如果q1为空,则指向q2noempty = q2;noempty->push(top);  // 将栈顶元素重新入栈return top;}// 检测栈是否为空bool empty() {return q1->empty() && q2->empty();  // 判断两个队列是否均为空}// 析构函数释放队列内存空间~MyStack() {delete q1;delete q2;}
};

ak截图:

在这里插入图片描述

C++的代码看不懂不要紧,关键我们要把这题的思路学会:

在这里插入图片描述

相信如果小伙伴了解了本题的思路,再去看代码就很简单了。
这里就C语言代码的几个关键点作一下说明:

  1. myStackCreate()函数部分,这个函数是用来初始化栈的,返回值是指针(地址),所以我们必须使用动态开辟函数在堆上申请空间,否则出了这个函数,那片地址就会被回收,另外栈的两个指针成员也要初始化,同样需要在其它函数中使用,需要动态开辟申请空间,如果不初始化他们就是野指针了。
  2. 在pop、push栈函数部分我们都使用到了noempty或者是empty,这样做就不用考虑谁是空,谁不是空了,不容易出错,这也是我们设计栈的成员的时候使用指针的原因,如果这里empty、noempty不是指针就无法改变原队列的数据,就需要去具体的考虑谁是空,谁不是空了。
  3. myStackFree()释放内存函数部分,由于我们给两个队列的节点和队列对象q1、q2、栈对象都在堆上申请了空间,需要把他们一一释放,注意顺序即可。

✍OJ题之用两个栈实现队列

这是题目:

在这里插入图片描述

原题点这里

这里我们也是先贴ak代码:

// 定义栈结构
typedef int STDataType; // 定义栈中数据类型为整数
typedef struct Stack {STDataType* _a; // 存储栈元素的数组指针int _top;       // 栈顶位置索引int _capacity;  // 栈的容量
} Stack;// 初始化栈
void StackInit(Stack* ps) {ps->_a = NULL;ps->_top = ps->_capacity = 0;
}// 入栈
void StackPush(Stack* ps, STDataType data) {assert(ps); // 确保栈指针不为空if (ps->_top == ps->_capacity) { // 当栈满时扩容STDataType* tmp = ps->_capacity == 0 ? (STDataType*)realloc(ps->_a, sizeof(STDataType) * 4) : (STDataType*)realloc(ps->_a, sizeof(STDataType) * (ps->_capacity * 2));if (tmp == NULL) {perror("realloc Failed");exit(-1);}ps->_a = tmp;ps->_capacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;}ps->_a[ps->_top++] = data; // 将元素入栈并更新栈顶位置
}// 出栈
void StackPop(Stack* ps) {assert(ps); // 确保栈指针不为空assert(ps->_top > 0); // 确保栈不为空ps->_top--; // 出栈操作,栈顶位置减一
}// 获取栈顶元素
STDataType StackTop(Stack* ps) {assert(ps); // 确保栈指针不为空return ps->_a[ps->_top - 1]; // 返回栈顶元素
}// 获取栈中有效元素个数
int StackSize(Stack* ps) {assert(ps); // 确保栈指针不为空return ps->_top; // 返回栈中元素个数即栈顶位置索引
}// 检测栈是否为空
bool StackEmpty(Stack* ps) {assert(ps); // 确保栈指针不为空return ps->_top == 0; // 返回栈是否为空
}// 销毁栈
void StackDestroy(Stack* ps) {assert(ps); // 确保栈指针不为空free(ps->_a); // 释放栈数组内存空间ps->_a = NULL;ps->_capacity = 0;ps->_top = 0;
}// 定义一个队列结构,使用两个栈实现
typedef struct {Stack* st1; // 入队列栈Stack* st2; // 出队列栈
} MyQueue;// 创建一个新的队列结构
MyQueue* myQueueCreate() {MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue)); // 分配队列结构内存空间obj->st1 = (Stack*)malloc(sizeof(Stack)); // 初始化入队列栈StackInit(obj->st1);obj->st2 = (Stack*)malloc(sizeof(Stack)); // 初始化出队列栈StackInit(obj->st2);return obj;
}// 元素入队
void myQueuePush(MyQueue* obj, int x) {while (!StackEmpty(obj->st2)) { // 将出队列栈中的元素全部转移到入队列栈中StackPush(obj->st1, StackTop(obj->st2));StackPop(obj->st2);}StackPush(obj->st1, x); // 将新元素入栈到入队列栈中
}// 元素出队
int myQueuePop(MyQueue* obj) {while (!StackEmpty(obj->st1)) { // 将入队列栈中的元素全部转移到出队列栈中StackPush(obj->st2, StackTop(obj->st1));StackPop(obj->st1);}int peek = StackTop(obj->st2); // 获取队头元素StackPop(obj->st2); // 出队操作return peek;
}// 获取队头元素
int myQueuePeek(MyQueue* obj) {int peek = myQueuePop(obj); // 获取队头元素StackPush(obj->st2, peek); // 将队头元素重新入队return peek;
}// 检测队列是否为空
bool myQueueEmpty(MyQueue* obj) {return StackEmpty(obj->st1) && StackEmpty(obj->st2); // 判断两个栈是否均为空
}// 释放队列结构内存空间
void myQueueFree(MyQueue* obj) {StackDestroy(obj->st1); // 销毁入队列栈StackDestroy(obj->st2); // 销毁出队列栈free(obj->st1); // 释放入队列栈的内存空间obj->st1 = NULL;free(obj->st2); // 释放出队列栈的内存空间obj->st2 = NULL;free(obj); // 释放队列结构的内存空间
}

这道题队列的成员可以不用Stack指针(这样的话在使用Stack函数时就需要取地址了),因为没有涉及到使用其它变量更改原栈变量的情况。

ak截图:

在这里插入图片描述


关键思路:

在这里插入图片描述

代码部分很多都和第一道题相似这里我们不再做过多的叙述。

⭕总结

本篇博客主要讲到了数据结构中栈和队列的一些知识,并给出了他们的C语言模拟实现,最后以两道OJ题强化了对后入先出、先入先出特性的了解,欢迎小伙伴指出不足和提出宝贵的建议,下面给出本篇博客思维导图,希望本篇博客对你有所帮助。

在这里插入图片描述

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

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

相关文章

软件工程期末总结

软件工程期末总结 软件危机出现的原因软件生命周期软件生命周期的概念生命周期的各个阶段 软件开发模型极限编程 可行性研究与项目开发计划需求分析结构化分析的方法结构化分析的图形工具软件设计的原则用户界面设计结构化软件设计面向对象面向对象建模 软件危机出现的原因 忽视…

7.13N皇后(LC51-H)

算法&#xff1a; N皇后是回溯的经典题 画树&#xff1a; 假设N3 皇后们的约束条件&#xff1a; 不能同行不能同列不能同斜线 回溯三部曲&#xff1a; 1.确定函数参数和返回值 返回值&#xff1a;void 参数&#xff1a; int n&#xff1a;题目给出&#xff0c;N皇后的…

骨传导蓝牙耳机什么牌子好用?为你揭晓不踩雷的骨传导耳机排行

喜欢运动的朋友们&#xff0c;你们一定不能错过骨传导耳机&#xff01;它真的是我们运动时的好帮手。为什么这么说呢&#xff1f;因为它不会像普通耳机那样塞住我们的耳朵&#xff0c;让我们在运动时感觉不舒服&#xff0c;甚至伤害耳朵。而且&#xff0c;它还可以帮助我们听到…

如何选择适用于光束分析应用的工业相机?

为光束质量分析系统选择合适的相机时&#xff0c;需要考虑许多关键特性。例如&#xff1a; ◈ 合适的波长范围&#xff1a;支持准确拍摄和测量所需波长的光束。 ◈ 高空间分辨率&#xff1a;更好地分析光束特征&#xff0c;如光束宽度、形状和强度分布。 ◈ 合适的传感器尺寸…

ClickHouse基础知识(七):ClickHouse的分片集群

副本虽然能够提高数据的可用性&#xff0c;降低丢失风险&#xff0c;但是每台服务器实际上必须容纳全量数据&#xff0c;对数据的横向扩容没有解决。 要解决数据水平切分的问题&#xff0c;需要引入分片的概念。通过分片把一份完整的数据进行切 分&#xff0c;不同的分片分布到…

Sectigo和Certum的区别

为了保护用户在互联网的隐私&#xff0c;网站使用SSL数字证书为http明文传输协议加上安全套接层&#xff0c;对网站传输数据加密。Sectigo和Certum是正规的CA认证机构&#xff0c;它们颁发的SSL证书经过市场认证&#xff0c;已经兼容大多数浏览器以及终端&#xff0c;今天就随S…

令人吃惊的SLM34x系列SLM340CK-DG 通过国际安全标准兼容光耦的单通道隔离驱动器

40V, 1A兼容光耦的单通道隔离驱动器SLM34x系列SLM340CK-DG产品已通过UL1577认证&#xff0c;通过UL1577安规标准的认可&#xff0c;意味着产品已符合相关的国际安全标准&#xff0c;在产品质量及可靠性上。 关于UL1577科普&#xff1a; UL1577规范适用于光隔离器、磁隔离器以…

同城拼车约车顺风车/同城顺风车小程序/顺风车小程序/拼车小程序

同城拼车约车顺风车/同城顺风车小程序/顺风车小程序/拼车小程序 演示小程序搜索:上车信息 可以打开封装APP 套餐一:源码+包安装+包过审(无需许可证)=300 套餐二:全包服务 包服务器+域名+APP+免费认证小程序+H5+PC=800 包审核 PC端联系客服看 PC端+H5+公众号+小程序…

深度生成模型之自编码器与变分自编码器 ->(个人学习记录笔记)

文章目录 深度生成模型之自编码器与变分自编码器自编码器AE1. 定义2. 自编码器的应用 变分自编码器(VAE)1. 理论求解2. 模型求解3. 优化目标4. 再参数化策略 AE与VAE的对比AE与VAE的主要局限性 深度生成模型之自编码器与变分自编码器 自编码器AE 1. 定义 Auto-Encoder&#…

线性代数_对角矩阵

对角矩阵是线性代数中一种特殊的矩阵类型&#xff0c;它在数学理论和实际应用中都有着重要的地位。对角矩阵的定义如下&#xff1a; 设 \( A \) 是一个 \( n \times n \) 的方阵&#xff0c;如果满足除主对角线上的元素外&#xff0c;其他元素都为零&#xff0c;即 \( A_{ij} …

C语言---扫雷(Minesweeper)

扫雷 1 了解扫雷游戏1.1 基本规则1.2 基础知识1.2.1字符相减 2 实现过程1.1 棋盘设定1.2 初始化棋盘1.3 打印棋盘1.4 放置雷1.5 排查雷1.6 game()函数 3 完整代码3.1 Minesweeper.h3.2 Minesweeper.c3.3 Test.c 4 参考 1 了解扫雷游戏 点击右侧进入扫雷游戏网页版 1.1 基本规…

探秘AI数字人克隆系统OEM源码:实现24小时无人值守直播间的奥秘

随着人工智能技术的不断发展&#xff0c;AI数字人克隆系统OEM源码正在引起广泛的关注。其中&#xff0c;实现24小时无人值守直播间成为了许多企业和机构的追求。本文将深入探讨如何利用AI数字人克隆系统OEM源码实现24小时无人值守直播间&#xff0c;并揭示其背后的奥秘。 一、…

【Linux操作系统】探秘Linux奥秘:Linux开发工具的解密与实战

&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《操作系统实验室》&#x1f516;诗赋清音&#xff1a;柳垂轻絮拂人衣&#xff0c;心随风舞梦飞。 山川湖海皆可涉&#xff0c;勇者征途逐星辉。 目录 &#x1fa90;1 初识Linux OS &…

Nginx解决跨域问题过程

学习Nginx解决跨域问题 结果 server {listen 22222;server_name localhost;location / {if ($request_method OPTIONS) {add_header Access-Control-Allow-Origin http://localhost:8080;add_header Access-Control-Allow-Headers *;add_header Access-Control-Allo…

redis 从0到1完整学习 (十四):RedisObject 之 ZSet 类型

文章目录 1. 引言2. redis 源码下载3. redisObject 管理 ZSet 类型的数据结构4. 参考 1. 引言 前情提要&#xff1a; 《redis 从0到1完整学习 &#xff08;一&#xff09;&#xff1a;安装&初识 redis》 《redis 从0到1完整学习 &#xff08;二&#xff09;&#xff1a;re…

学习记录——BiFormer

BiFormer Vision Transformer with Bi-Level Routing Attention BiFormer:具有双电平路由注意的视觉变压器 摘要作为视觉转换器的核心组成部分,注意力是捕捉长期依赖关系的有力工具。然而,这样的能力是有代价的:当计算跨所有空间位置的成对令牌交互时,它会产生巨大的计算负…

如何理解图卷积网络GCN

文章目录 基本概念度矩阵&#xff08;degree&#xff09;邻接矩阵&#xff08;Adjacency&#xff09; 理解GCN两层GCN网络层数设置 搭建GCN网络定义GCN层定义GCN网络 基本概念 图的一些基本知识&#xff1a;图&#xff0c;邻居&#xff0c;度矩阵&#xff0c;邻接矩阵 度矩阵…

【力扣100】22.括号生成 || 为什么搜索几乎都是用深度优先遍历?

添加链接描述 class Solution:def generateParenthesis(self, n: int) -> List[str]:# 思路是根据左右括号剩余数量进行生成# 剩余左括号小于剩余右括号时&#xff0c;可以加左或者加右# 剩余左括号大于剩余右括号时&#xff0c;舍弃def backtrack(cur,left,right,res):if …

.NET DevOps 接入指南 | 1. GitLab 安装

引言 容器、DevOps和微服务被称为驱动云原生快速发展的三架马车。而DevOps是其中非常重要的一环&#xff0c;DevOps 是由Developers&#xff08;Dev&#xff09;和Operations&#xff08;Ops&#xff09;两个单词简称组成&#xff0c;中文直译就是“开发运维一体化”。 DevOps…

Spring-AOP综述

文章迁移自语雀。 今天下雪了, 完成了spring aop的应用学习, 学到了以前很多忽略的知识点,也写完了各种试验. 今天给程程分享了很多了随州下雪的场景,写了一二三, ♡程程, i miss u 等字体,在雪地上写给程程看, 还拍摄了照片和视频给她, 上午的时候,还做了一个雪人, 哈哈哈, 她…