当前位置: 首页 > news >正文

【学习笔记】数据结构(三)

栈和队列

文章目录

  • 栈和队列
    • 3.1 栈 - Stack
      • 3.1.1 抽象数据类型栈的定义
      • 3.1.2 栈的表示和实现
    • 3.2 栈的应用举例
      • 3.2.1 数制转换
      • 3.2.2 括号匹配的检验
      • 3.2.3 迷宫求解
      • 3.2.4 表达式求值 - 波兰、逆波兰
      • 3.2.5 反转一个字符串或者反转一个链表
    • 3.3 栈与递归的实现
    • 3.4 队列 - Queue
      • 3.4.1 抽象数据类型队列的定义
      • 3.4.2 链队列--队列的链式表示和实现
      • 3.4.3 循环队列--队列的顺序表示和实现

3.1 栈 - Stack

3.1.1 抽象数据类型栈的定义

栈(stack)是限定仅在表尾进行插人或删除操作的线性表。栈又称为后进先出(last in first out)的线性表(简称 LIFO 结构)。

表尾端称为栈顶(top),表头端称为栈底(bottom)。

不含元素的空表称为空栈。

在这里插入图片描述

栈的抽象数据类型的定义:

在这里插入图片描述

3.1.2 栈的表示和实现

顺序栈 , 即栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top指示栈顶元素在顺序栈中的位置。通常的top=0表示空栈。

由于栈在使用过程中所需最大空间的大小很难估计,因此,一般来说,在初始化设空栈时不应限定栈的最大容量。一个较合理的做法是:先为栈分配一个基本容量,然后在应用过程中,当栈的空间不够使用时再逐段扩大。为此,可设定两个常量:STACK_INIT_SIZE(存储空间初始分配量)和STACKINCREMENT(存储空间分配增量)。

typedef struct {SElemType * base;SElemType * top;int stacksize; //指示栈的当前可使用的最大容量
}SqStack;

栈的初始化操作为:

  • 按设定的初始分配量进行第一次存储分配;

  • 称base为栈底指针, 在顺序栈中,它始终指向栈底的位置, 若base的值为 NULL, 则表明栈结构不存在。

  • 称top为栈顶指针,其初值指向栈底,即top=base可作为栈空的标记,每当插人新的栈顶元素时,指针top增1;删除栈顶元素时,指针top减1,因此,非空栈中的栈顶指针始终在栈顶元素的下一个位置上。

在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>#define STACK_INIT_SIZE 100 //存储空间初始分配量
#define STACKINCREMENT 10 //存储空间分配增量#define OK 1 //完成
#define OVERFLOW -1 //失败
#define ERROR -2 //错误typedef int Status;
typedef struct {int* base; // 在栈构造之前和销毁之后,base的值为NULLint* top; // 栈顶指针int stacksize; //指示栈的当前可使用的最大容量
}SqStack;Status InitStack(SqStack* S) {// 构造一个空栈SS->base = (int*) malloc(STACK_INIT_SIZE * sizeof(int));if (!S->base) exit(OVERFLOW);S->top = S->base;S->stacksize = STACK_INIT_SIZE;return OK;
}Status Push(SqStack* S, int e) {// 插入元素 e为新的栈顶元素if (S->top - S->base >= S->stacksize) { //栈满,追加存储空间S->base = (int*)realloc(S->base, (S->stacksize + STACKINCREMENT) * sizeof(int));if (!S->base) exit(OVERFLOW);S->top = S->base + S->stacksize;S->stacksize += STACKINCREMENT;}*S->top++ = e;return OK;
}Status Pop(SqStack* S, int* e) {// 若栈不空,则删除s的栈顶元素,用e返回其值,并返回OK;否则返回 ERRORif (S->top == S->base) return ERROR;*e = *--S->top;return OK;
}Status GetTop(SqStack S, int* e) {// 若栈不空, 则用e返回s的栈顶元素, 并返回0K; 否则返回ERRORif (S.top == S.base) return ERROR;*e = *(S.top - 1);return OK;
}Status DestroyStack(SqStack* S) {// 销毁栈Sfree(S->base);S->base = NULL;S->top = NULL;S->stacksize = 0;return OK;
}int main() {SqStack S;if (InitStack(&S) != OK) {printf("Stack initialization failed.\n");return OVERFLOW;}int e; // 使用一个整数变量而不是指针Push(&S, 1);Push(&S, 2);Push(&S, 3);Push(&S, 4);Status status = Pop(&S, &e);if (status == OK) {// 删除的元素printf("Popped element: %d\n", e);}else {printf("Error: Stack is empty.\n");}if (GetTop(S, &e) == OK) {// 此时栈顶的元素printf("Top element: %d\n", e);}DestroyStack(&S);return 0;
}

使用数组实现一个栈

#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>#define MAX_SIZE 101
int A[MAX_SIZE];
int top = -1;void Push(int x)
{if (top == MAX_SIZE - 1){printf("Error:stack overflow\n"); return;}A[++top] = x;
}void Pop() {if (top == -1) {printf("Error: No element to pop\n");return;}top--;
}int Top()
{if (top != -1){return A[top];}	
}void Print()
{int i;printf("Stack: ");for (i = 0; i <= top; i++) {printf("%d ", A[i]);}printf("\n");
}int main() {Push(2); Print(); Push(5); Print(); Push(10); Print(); Pop(); Print(); Push(12); Print();return 0;
}

使用链表实现一个栈

#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>typedef struct Node {int data;struct Node* link;
}Node;
struct Node* top;void Push(int x) {Node* temp = (Node*)malloc(sizeof(Node));temp->data = x;temp->link = top;top = temp;
}void Pop() {Node* temp;if (top == NULL) return;temp = top;top = top->link;free(temp);
}int Top() {return top->data;
}bool IsEmpty() {if (top == NULL) {return true;}return false;
}void Print() {Node* temp = top;printf("List: ");while (temp != NULL) {printf("%d ", temp->data);temp = temp->link;}printf("\n");
}int main() {top = NULL;printf("%d \n", IsEmpty());Push(2);Push(4);Push(1);Print();Pop();Print();printf("%d \n", Top());printf("%d \n", IsEmpty());return 0;
}

3.2 栈的应用举例

3.2.1 数制转换

十进制数N和其他d进制数的转换
N = ( N div  d ) × d + N m o d d (其中:div为整除运算,mod为求余运算) N = (N \text{ div } d) \times d + N \bmod d \text{ (其中:div为整除运算,mod为求余运算)} N=(N div d)×d+Nmodd (其中:div为整除运算,mod为求余运算)
在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>#define STACK_INIT_SIZE 100 //存储空间初始分配量
#define STACKINCREMENT 10 //存储空间分配增量#define OK 1 //完成
#define OVERFLOW -1 //失败
#define ERROR -2 //错误typedef int Status;
typedef struct {int* base; // 在栈构造之前和销毁之后,base的值为NULLint* top; // 栈顶指针int stacksize; //指示栈的当前可使用的最大容量
}SqStack;Status InitStack(SqStack* S) {// 构造一个空栈SS->base = (int*) malloc(STACK_INIT_SIZE * sizeof(int));if (!S->base) exit(OVERFLOW);S->top = S->base;S->stacksize = STACK_INIT_SIZE;return OK;
}Status StackEmpty(SqStack S) {return (S.top == S.base) ? OK : ERROR;
}Status Push(SqStack* S, int e) {// 插入元素 e为新的栈顶元素if (S->top - S->base >= S->stacksize) { //栈满,追加存储空间S->base = (int*)realloc(S->base, (S->stacksize + STACKINCREMENT) * sizeof(int));if (!S->base) exit(OVERFLOW);S->top = S->base + S->stacksize;S->stacksize += STACKINCREMENT;}*S->top++ = e;return OK;
}Status Pop(SqStack* S, int* e) {// 若栈不空,则删除s的栈顶元素,用e返回其值,并返回OK;否则返回 ERRORif (S->top == S->base) return ERROR;*e = *--S->top;return OK;
}Status GetTop(SqStack S, int* e) {// 若栈不空, 则用e返回s的栈顶元素, 并返回0K; 否则返回ERRORif (S.top == S.base) return ERROR;*e = *(S.top - 1);return OK;
}Status DestroyStack(SqStack* S) {// 销毁栈Sfree(S->base);S->base = NULL;S->top = NULL;S->stacksize = 0;return OK;
}void conversion() {SqStack S;if (InitStack(&S) != OK) {printf("Stack initialization failed.\n");}int e, N;printf("请输入一个十进制数:");int num = scanf("%d", &N);if (num == 1) {while (N) {Push(&S, N % 8);N = N / 8;}while (StackEmpty(S) != OK) {Pop(&S, &e);printf("%d", e);}DestroyStack(&S);}}int main() {conversion();return 0;
}

3.2.2 括号匹配的检验

算法的设计思想:

  1. 凡出现左括弧,则进栈;

  2. 凡出现右括弧,首先检查栈是否空;

  • 若栈空,则表明“右括弧”多了
  • 否则和栈顶元素比较
    • 若相匹配,则“左括弧出栈”
    • 否则不匹配
  1. 表达式检验结束时
  • 若栈空, 则匹配正确

  • 否则表明“左括弧”多了

#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>#define OVERFLOW -1#define STACK_INIT_SIZE 100 //存储空间初始分配量
#define STACKINCREMENT 10 //存储空间分配增量typedef struct {char* base;char* top;int stackSize;
} Stack;void InitStack(Stack* s) {s->base = (char*)malloc(100 * sizeof(char));if (!s->base) exit(1); // 分配内存失败s->top = s->base;s->stackSize = STACK_INIT_SIZE;
}void Push(Stack* s, char elem) {if (s->top - s->base >= s->stackSize) { // 栈满,需要扩容s->base = (char*)realloc(s->base, (s->stackSize + STACKINCREMENT) * sizeof(char));if (!s->base) exit(OVERFLOW); // 扩容失败s->top = s->base + s->stackSize;s->stackSize += STACKINCREMENT;}*(s->top++) = elem;
}char Pop(Stack* s) {if (s->top == s->base) return '\0'; // 栈空return *(--s->top);
}int StackEmpty(Stack s) {return s.top == s.base;
}void DestroyStack(Stack* s) {free(s->base);s->base = NULL;s->top = NULL;s->stackSize = 0;
}int CheckBrackets(const char* str) {Stack s;InitStack(&s);char c, topChar;while (*str) {switch (*str) {case '(':case '[':case '{':Push(&s, *str);break;case ')':case ']':case '}':if (StackEmpty(s)) {DestroyStack(&s);return 0; // 没有匹配的左括号}topChar = Pop(&s);if ((topChar == '(' && *str != ')') ||(topChar == '[' && *str != ']') ||(topChar == '{' && *str != '}')) {DestroyStack(&s);return 0; // 括号不匹配}break;}str++;}int isEmpty = StackEmpty(s);DestroyStack(&s);return isEmpty; // 如果栈为空,所有括号正确匹配
}int main() {char expression[100];printf("Enter an expression: ");scanf("%99s", expression);if (CheckBrackets(expression)) {printf("The brackets are correctly matched.\n");}else {printf("The brackets are not matched.\n");}return 0;
}

3.2.3 迷宫求解

迷宫路径算法的基本思想是:

  • 若当前位置“可通”,则纳入路径继续前进
  • 若当前位置“不可通”,则后退,换向探索
  • 若四周均“不可通”,则从路径中删除
#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>#define MAXSIZE 100  // 堆栈最大容量
#define MAZE_SIZE 5  // 迷宫大小#define OK 1 //完成
#define OVERFLOW -1 //失败
#define ERROR -2 //错误typedef struct {int x;int y;
} PosType;typedef struct {int ord;  // 通道块在路径上的序号PosType seat; // 通道块在迷宫中的坐标位置int di; // 从此通道块走向下一通道块的方向
} SElemType; // 栈的元素类型typedef struct {SElemType* base;SElemType* top;int stacksize;
} Stack;typedef int Status;
typedef int MazeType[MAZE_SIZE][MAZE_SIZE];void InitStack(Stack* S) {S->base = (SElemType*)malloc(MAXSIZE * sizeof(SElemType));if (!S->base) exit(ERROR);S->top = S->base;S->stacksize = MAXSIZE;
}Status Push(Stack* S, SElemType e) {if (S->top - S->base >= S->stacksize) {S->base = (SElemType*)realloc(S->base, (S->stacksize + 10) * sizeof(SElemType));if (!S->base) exit(ERROR);S->top = S->base + S->stacksize;S->stacksize += 10;}*S->top++ = e;return OK;
}Status Pop(Stack* S, SElemType* e) {if (S->top == S->base) return ERROR;*e = *--S->top;return OK;
}Status StackEmpty(Stack s) {return s.top == s.base;
}// 检查当前位置是否可以通过
Status Pass(MazeType maze, PosType curpos) {// 检查坐标是否在迷宫范围内if (curpos.x < 0 || curpos.x >= MAZE_SIZE || curpos.y < 0 || curpos.y >= MAZE_SIZE) {return ERROR;  // 超出边界,不可通过}return maze[curpos.x][curpos.y] == 0;  // 返回1如果是通道,0如果是墙或已访问
}// 留下足迹,标记位置已访问
Status FootPrint(MazeType maze, PosType curpos) {// 检查坐标是否在迷宫范围内if (curpos.x >= 0 && curpos.x < MAZE_SIZE && curpos.y >= 0 && curpos.y < MAZE_SIZE) {maze[curpos.x][curpos.y] = -1;  // 使用-1标记已访问}
}// 标记位置为死胡同
void MarkPrint(MazeType maze, PosType pos) {// 检查坐标是否在迷宫范围内if (pos.x >= 0 && pos.x < MAZE_SIZE && pos.y >= 0 && pos.y < MAZE_SIZE) {maze[pos.x][pos.y] = 2;  // 使用2标记为死胡同}
}PosType NextPos(PosType pos, int di) {PosType next = pos;switch (di) {case 1: next.y++; break;  // 向东case 2: next.x++; break;  // 向南case 3: next.y--; break;  // 向西case 4: next.x--; break;  // 向北}return next;
}Status MazePath(MazeType maze, PosType start, PosType end) {// 若迷宫 maze 中存在从入口 start 到出口 end 的通道,则求得一条存放在栈中(从栈底到栈顶),并返回 TRUE; 否则返回 FALSEStack s;InitStack(&s);PosType curpos = start; // 设定“当前位置”为“入口位置”int curstep = 1; // 探索第一步SElemType pop_elem;do {if (Pass(maze, curpos) == 1) { // 当前位置可以通过,即是未曾走到过的通道块FootPrint(maze, curpos); // 留下足迹SElemType e = { curstep, curpos, 1 };Push(&s, e); //  加入路径if (curpos.x == end.x && curpos.y == end.y) //  到达终点(出口)return OK;curpos = NextPos(curpos, 1);// 下一位置是当前位置的东邻curstep++;// 探索下一步}else { // 当前位置不能通过if (!StackEmpty(s)) {Pop(&s, &pop_elem);while (pop_elem.di == 4 && !StackEmpty(s)) {MarkPrint(maze, pop_elem.seat); // 留下不能通过的标记,并退回一步Pop(&s, &pop_elem);}if (pop_elem.di < 4) {pop_elem.di++;Push(&s, pop_elem); // 换下一个方向探索curpos = NextPos(pop_elem.seat, pop_elem.di); // 设定当前位置是该新方向上的相邻块}}}} while (!StackEmpty(s));
}int main() {MazeType maze = {{0, 1, 0, 0, 0},{0, 1, 1, 1, 0},{0, 0, 0, 1, 0},{0, 1, 0, 0, 0},{0, 0, 0, 1, 0}};PosType start = { 0, 0 };PosType end = { 4, 4 };MazePath(maze, start, end);printf("\nAfter MarkPrint:\n");for (int i = 0; i < MAZE_SIZE; i++) {for (int j = 0; j < MAZE_SIZE; j++) {if (maze[i][j] == -1) {printf("(%d,%d) ", i, j);}}printf("\n");}return 0;
}

3.2.4 表达式求值 - 波兰、逆波兰

//10以内计算
#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>#define OK 1 // 完成
#define OVERFLOW -1 // 失败
#define ERROR -2 // 错误
#define INF 1e9 // 不合法#define STACK_INIT_SIZE 100 //存储空间初始分配量
#define STACKINCREMENT 10 //存储空间分配增量typedef char SElemType;
typedef int Status;
typedef struct {SElemType* base;SElemType* top;int stackSize;
}Stack;Status InitStack(Stack* S) {// 构造一个空栈SS->base = (SElemType*)malloc(STACK_INIT_SIZE * sizeof(SElemType));if (!S->base) exit(OVERFLOW);S->top = S->base;S->stackSize = STACK_INIT_SIZE;return OK;
}Status Push(Stack* S, char e) {// 插入元素 e为新的栈顶元素if (S->top - S->base >= S->stackSize) { //栈满,追加存储空间S->base = (SElemType*)realloc(S->base, (S->stackSize + STACKINCREMENT) * sizeof(SElemType));if (!S->base) exit(OVERFLOW);S->top = S->base + S->stackSize;S->stackSize += STACKINCREMENT;}*S->top++ = e;return OK;
}Status Pop(Stack* S, char* e) {// 若栈不空,则删除s的栈顶元素,用e返回其值,并返回OK;否则返回 ERRORif (S->top == S->base) return ERROR;*e = *--S->top;return OK;
}Status DestroyStack(Stack* S) {// 销毁栈Sfree(S->base);S->base = NULL;S->top = NULL;S->stackSize = 0;return OK;
}char GetTop(Stack S) {if (S.top == S.base) return ERROR;SElemType e = *(S.top - 1);return e;
}int In(char c, const char* OP) {// 使用 strchr 函数检查 c 是否在 OP 字符串中return strchr(OP, c) != NULL;
}char Precede(char a, char b)
{char x[10] = { '+','-','*','/','(',')','#' };char OP[10][10] = { {'>','>','<','<','<','>','>'},{'>','>','<','<','<','>','>'},{'>','>','>','>','<','>','>'},{'>','>','>','>','<','>','>'},{'<','<','<','<','<','=',' '},{'>','>','>','>',' ','>','>'},{'<','<','<','<','<',' ','='} };for (int i = 0; i < 7; i++){if (a == x[i]) {a = i;}if (b == x[i]) {b = i;}}return OP[a][b];
}char Operate(char p, char theta, char q)
{if (theta == '+')return p + q;else if (theta == '-')return q - p;else if (theta == '*')return p * q;else if (theta == '/'){if (p == 0){printf("输入不合法!");return INF;}return q / p;}else return INF;
}char EvaluateExpression() {// 算术表达式求值的算符优先算法。设OPTR和OPND分别为运算符栈和运算数栈,// OP 为运算符集合。const char* OP = "+-*/()#";char x;char theta, p, q;Stack OPTR, OPND;InitStack(&OPTR);Push(&OPTR, '#');InitStack(&OPND);char c = getchar();while (c != '#' || GetTop(OPTR) != '#') {printf("OPTR:"); for (int i = 0; i < OPTR.top - OPTR.base; i++)printf("%c ", OPTR.base[i]); puts("");printf("OPND:"); for (int i = 0; i < OPND.top - OPND.base; i++)printf("%d ", OPND.base[i]); puts("\n");if (!In(c, OP)) {  // 不是运算符则进栈OPNDPush(&OPND, c - '0');c = getchar();} else {switch (Precede(GetTop(OPTR), c)){case'<': // 栈顶元素优先权低Push(&OPTR, c);c = getchar();break;case'=': // 脱括号并接收下一字符Pop(&OPTR, &x);c = getchar();break;case'>': // 退栈并将运算结果入栈Pop(&OPTR, &theta);Pop(&OPND, &p);Pop(&OPND, &q);Push(&OPND, Operate(p, theta, q));break;default:break;}}}char res = GetTop(OPND);DestroyStack(&OPTR);DestroyStack(&OPND);return res;
}int main() {printf("请输入一串表达式,以等号“#”结尾:");printf("最终结果为:%d", EvaluateExpression());return 0;
}

⭐️ 中缀、前缀、后缀

Order of operation:

  1. Parentheses (){} []

  2. Exponents (right to lett ) ^

  3. Multiplication and division (left to right)

  4. Addition and Subtraction (left to right)

中缀表达式 Infix : 运算符在运算数的中间

  • ​ <operand><operator><operand>
  • ​ 缺点:关系符号优先级和结合,所以对计算机来说却不好操作,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式)

前缀表达式 - 波兰表达式 Prefix

  • ​ <operator><operand><operand>

  • ​ 中缀表达式:(a + b) * c - d 转换为 前缀表达式(波兰表达式):- * + a b c d

  • ​ 特点:一个操作数只能和一个操作符进行结合, 连续出现的两个操作数和在它们之前且紧靠它们的运算符构成一个最小表达式

  • 前缀表达式的计算机求值:

    • 右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果

    • 例如:(3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:

      • 从右至左扫描,将6、5、4、3压入堆栈
      • 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈
      • 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
      • 最后是-运算符,计算出35-6的值,即29,由此得出最终结果

后缀表达式 - 逆波兰表达式 Postfix

  • ​ <operand><operand><operator>
  • ​ 中缀表达式 :(a + b) * c - d 转换为 后缀表达式(逆波兰表达式):a b + c * d -
  • ​ 特点: 运算符在式中出现的顺序恰为表达式的运算顺序;每个运算符和在它之前出现 且紧靠它的两个操作数构成一个最小表达式
  • 后缀表达式的计算机求值
    • 从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
    • 例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
      • 从左至右扫描,将3和4压入堆栈;
      • 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
      • 将5入栈;
      • 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
      • 将6入栈;
      • 最后是-运算符,计算出35-6的值,即29,由此得出最终结果

👉 逆波兰计算器简版

  • 计算器说明

    ​ 输入一个逆波兰表达式(后缀表达式), 使用栈(Stack),计算其结果

  • 代码思路

    计算后缀表达式无需考虑运算符优先级问题

    分为两种情况:

    ​ 遇到数:压入数栈

    ​ 遇到运算符:从数栈中弹出两个数,进行计算,计算结果压入数栈

    处理完表达式就代表计算完成

#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define MAX_SIZE 100typedef struct {int top;int data[MAX_SIZE];
} Stack;void push(Stack* s, int item) {if (s->top == MAX_SIZE - 1) {printf("Stack overflow\n");exit(1);}s->data[++s->top] = item;
}int pop(Stack* s) {if (s->top == -1) {printf("Stack underflow\n");exit(1);}return s->data[s->top--];
}int isOperator(char c) {return c == '+' || c == '-' || c == '*' || c == '/';
}int calculate(int num1, int num2, char op) {switch (op) {case '+':return num1 + num2;case '-':return num1 - num2;case '*':return num1 * num2;case '/':return num1 / num2;default:printf("Invalid operator\n");exit(1);}
}/*逆波兰计算器 - 整数
*/
int evaluateRPN(char* suffixExpression) {Stack stack;stack.top = -1;// strtok两个参数:要分割的字符串和分隔符,然后返回分割后的标记。char* token = strtok(suffixExpression, " ");while (token != NULL) {if (isOperator(token[0])) {// num2 先出栈,所以 num2 是减数或除数int num2 = pop(&stack);// num1 后出栈,所以 num1 是被减数或被除数int num1 = pop(&stack);int res = calculate(num1, num2, token[0]);push(&stack, res);}else {// 将字符串 token 转换为整数。这函数会忽略字符串前面的空白字符,直到遇到数字或正负号为止,然后将遇到的数字部分转换为整数。push(&stack, atoi(token));}// 在已经使用strtok函数分割过的字符串上继续分割,使用空格作为分隔符。传入NULL作为第一个参数表示继续从上一次的位置开始分割token = strtok(NULL, " ");}return pop(&stack);
}int main() {char suffixExpression[] = "4 5 * 8 - 60 + 8 2 / +";int result = evaluateRPN(suffixExpression);printf("The result is: %d\n", result);return 0;
}

👉 中缀表达式转后缀表达式

  1. 初始化两个栈:运算符栈operStack和储存中间结果的栈tempStack;
  2. 从左至右扫描中缀表达式;
  3. 遇到操作数时,将其压tempStack;
  4. 遇到运算符时,比较其与operStack栈顶运算符的优先级:
    1. 如果operStack为空,或栈顶运算符为左括号“(”,则直接将此运算符入tempStack栈(分如下两种情况)
      1. operStack 栈顶为空:之前的优先级别高的运算已经处理完成,已经得到了一个结果,将当前运算符直接压入 operStack 栈即可
      2. operStack 栈顶为左括号:当从operStack 出栈,用于运算后,这对括号中的表达式的值也就计算出来了
    2. 如果当前运算符优先级比栈顶运算符的高,也将运算符压入tempStack(当前运算符优先级高,先执行运算)
    3. 否则,当前运算符优先级 <= 栈顶运算符优先级,将operStack栈顶的运算符弹出并压入tempStack中(operStack 栈顶运算符优先级高,先执行运算),再次转到(4.1)与operStack中新的栈顶运算符相比较(分如下两种情况);
      1. 一直循环,将 tempStack 栈顶元素取出,直到在 operStack 栈中找到比当前运算符优先级高的运算符,让其先执行运算
      2. 如果在 tempStack 栈中找不到比当前运算符优先级高的运算符,则会直接将 operStack 栈掏空,然后将当前运算符压入 tempStack 栈中(放在栈底)
  5. 遇到括号时:
    1. 如果是左括号“(”,则直接压入operStack,等待与其配对的右括号,因为括号中的表达式需要优先运算
    2. 如果是右括号“)”,则依次弹出operStack栈顶的运算符,并压入tempStack,直到遇到左括号为止,此时将这一对括号丢弃(此时括号内的运算完成,并将结果压入了tempStack)
  6. 重复步骤2至5,直到表达式的最右边
  7. 将operStack中剩余的运算符依次弹出并压入tempStack(operStack 栈中剩下的运算都是优先级相同的运算符,按顺序执行即可)
  8. 依次弹出tempStack中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

例子:1 + (( 2 + 3 )* 4 ) - 5

扫描到的元素储存中间结果的栈tempStack(栈底 -> 栈顶)运算符栈operStack(栈底 -> 栈顶)说明
11数字,直接入栈 - 3
+1+operStack 为空,直接入栈- 4.1.1
(1+ (左括号,直接入栈 - 5.1
(1+ ( (左括号,直接入栈 - 5.1
21 2+ ( (数字,直接入栈 - 3
+1 2+ ( ( +operStack 栈顶为左括号,直接入栈 - 4.1.2
31 2 3+ ( ( +数字,直接入栈 - 3
)1 2 3 ++ (右括号,弹出operStack,并压入tempStack,直到出现左括号 - 5.2
*1 2 3 +operStack 栈顶为左括号,直接入栈 - 4.1.2
41 2 3 + 4+ ( *数字,直接入栈 - 3
)1 2 3 + 4 *+右括号,弹出operStack,并压入tempStack,直到出现左括号 - 5.2
-1 2 3 + 4 * +-- 与 + 同级,operStack弹出+,+压入tempStack,operStack 为空则 - 压入operStack - 4.3
51 2 3 + 4 * + 5-数字,直接入栈 - 3
到达最右端1 2 3 + 4 * + 5 -将operStack中剩余的运算符依次弹出并压入tempStack - 8
#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h> // 用于isspace()函数#define MAX_SIZE 100int getPriority(char op) {switch (op) {case '+':case '-':return 1;case '*':case '/':return 2;default:return 0;}
}// 检查字符串是否只包含有效的字符
int isValidExpression(const char* expr) {while (*expr) {if (!isdigit(*expr) && !isspace(*expr) && strchr("+-*/()", *expr) == NULL) {return 0; // 非法字符}expr++;}return 1; // 表达式有效
}int infixToPostfix(const char* infix, char* postfix) {if (!isValidExpression(infix)) {return 0; // 表达式无效}char stack[MAX_SIZE];int top = -1;int j = 0;for (int i = 0; infix[i] != '\0'; i++) {char token = infix[i];if (isdigit(token)) {postfix[j++] = token;}else if (token == '(') {stack[++top] = token;}else if (token == ')') {while (top > -1 && stack[top] != '(') {postfix[j++] = stack[top--];}if (top == -1) return 0; // 没有匹配的'('top--; // 弹出'('}else {while (top > -1 && getPriority(stack[top]) >= getPriority(token)) {postfix[j++] = stack[top--];}if (token == ')') return 0; // ')' 不是运算符stack[++top] = token;}}while (top > -1) {postfix[j++] = stack[top--];}postfix[j] = '\0';return 1; // 成功转换
}int main() {char infix[] = "1+((2+3)*4)-5";char postfix[MAX_SIZE] = { 0 };if (infixToPostfix(infix, postfix)) {printf("Infix Expression: %s\n", infix);printf("Postfix Expression: %s\n", postfix);}else {printf("Invalid expression.\n");}return 0;
}

👉 完整的逆波兰计算器

#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h> // 用于isspace()函数#define MAX_SIZE 100typedef struct {int top;double data[MAX_SIZE];
} Stack;void push(Stack* s, double item) {if (s->top == MAX_SIZE - 1) {printf("Stack overflow\n");exit(1);}s->data[++s->top] = item;
}double pop(Stack* s) {if (s->top == -1) {printf("Stack underflow\n");exit(1);}return s->data[s->top--];
}int getPriority(char op) {switch (op) {case '+':case '-':return 1;case '*':case '/':return 2;default:return 0;}
}// 判断字符是否为操作符
int is_operator(char c) {return (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')');
}// 检查字符串是否只包含有效的字符
int isValidExpression(const char* expr) {while (*expr) {if (!isdigit(*expr) && !isspace(*expr) && strchr("+-*/().", *expr) == NULL) {return 0; // 非法字符}expr++;}return 1; // 表达式有效
}int infixToPostfix(const char* infix, char* postfix) {if (!isValidExpression(infix)) {return 0; // 表达式无效}char stack[MAX_SIZE];int top = -1;int j = 0;for (int i = 0; infix[i] != '\0'; i++) {char token = infix[i];if (isdigit(token) || (token == '.' && i + 1 < strlen(infix) && isdigit(infix[i + 1]))) {// 复制数字到后缀表达式while (isdigit(token) || token == '.') {postfix[j++] = token;i++;token = infix[i];}postfix[j++] = ' '; // 添加空格以分隔数字if (is_operator(token) || (i == strlen(infix))){i--;}}else if (token == '(') {stack[++top] = token;}else if (token == ')') {while (top > -1 && stack[top] != '(') {postfix[j++] = stack[top--];postfix[j++] = ' ';}if (top == -1) return 0; // 没有匹配的'('top--; // 弹出'('}else {if (isspace(token)) {continue;}while (top > -1 && getPriority(stack[top]) >= getPriority(token)) {postfix[j++] = stack[top--];postfix[j++] = ' ';}if (token == ')') return 0; // ')' 不是运算符top++;stack[top] = token;}}while (top > -1) {if (strchr("(",stack[top])){return 0;}postfix[j++] = stack[top--];postfix[j++] = ' ';}postfix[j] = '\0';return 1; // 成功转换
}double calculate(double num1, double num2, char op) {switch (op) {case '+':return num1 + num2;case '-':return num1 - num2;case '*':return num1 * num2;case '/':return num1 / num2;default:printf("Invalid operator\n");exit(1);}
}/*逆波兰计算器 
*/
double evaluateRPN(char* suffixExpression) {Stack tempStack;tempStack.top = -1;// strtok两个参数:要分割的字符串和分隔符,然后返回分割后的标记。char* token = strtok(suffixExpression, " ");char* double_tpken;while (token != NULL) {if (is_operator(token[0])) {// num2 先出栈,所以 num2 是减数或除数double num2 = pop(&tempStack);// num1 后出栈,所以 num1 是被减数或被除数double num1 = pop(&tempStack);double res = calculate(num1, num2, token[0]);push(&tempStack, res);}else {// 将字符串 token 转换为double。这函数会忽略字符串前面的空白字符,直到遇到数字或正负号为止,然后将遇到的数字部分转换为整数。push(&tempStack, strtod(token, &double_tpken));}// 在已经使用strtok函数分割过的字符串上继续分割,使用空格作为分隔符。传入NULL作为第一个参数表示继续从上一次的位置开始分割token = strtok(NULL, " ");}return pop(&tempStack);
}int main() {char infix[] = "(12.8 + 20) - 3.55 * 4 + 10 / 5.0";char postfix[MAX_SIZE] = { 0 };if (infixToPostfix(infix, postfix)) {printf("Infix Expression: %s\n", infix);printf("Postfix Expression: %s\n", postfix);double result = evaluateRPN(postfix);printf("The result is: %.2f\n", result);}else {printf("Invalid expression.\n");}return 0;
}

3.2.5 反转一个字符串或者反转一个链表

反转一个字符串

#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>typedef struct Node {int data;struct Node* link;
}Node;
struct Node* top = NULL;void Push(int x) {Node* temp = (Node*)malloc(sizeof(Node));if (temp){temp->data = x;temp->link = top;top = temp;}
}void Pop() {Node* temp;if (top == NULL) return;temp = top;top = top->link;free(temp);
}int Top() {if (top == NULL) {printf("Error: Stack is empty\n");return -1;  // Return an error value or handle error appropriately}return top->data;
}bool IsEmpty() {if (top == NULL) {return true;}return false;
}void Reverse(char* C, int n)
{int i;for (i = 0; i < n; i++){Push(C[i]);}int j;for (j = 0; j < n; j++){C[j] = Top();Pop();}
}int main() {char C[51] = "Hello";Reverse(C, strlen(C));printf("Output = %s", C);return 0;
}

反转一个链表到链栈

#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>typedef struct Node {int data;struct Node* link;
}Node;
Node* head; // Linked List
Node* top = NULL; // Stack// Linked Listvoid Insert(int data, int n) {Node* temp1 = (Node*)malloc(sizeof(Node));if (temp1 != NULL) {temp1->data = data;temp1->link = NULL;if (n == 1) {temp1->link = head;head = temp1;return;}Node* temp2 = head;int i;for (i = 0; i < n - 2; i++) {temp2 = temp2->link;}temp1->link = temp2->link;temp2->link = temp1;}
}void Delete(int n) {//if (head == NULL) return;Node* temp = head;if (n == 1) {head = temp->link;free(temp);return;}int i;for (i = 1; i < n - 1; i++) {temp = temp->link;}Node* temp1 = temp->link;temp->link = temp1->link;free(temp1);
}//iteration
void Print(Node* headerNode) {Node* temp = headerNode;printf("List is: ");while (temp != NULL){printf("%d ", temp->data);temp = temp->link;}printf("\n");
}// Stack
void Push(int x) {Node* temp = (Node*)malloc(sizeof(Node));if (temp){temp->data = x;temp->link = top;top = temp;}
}void Pop() {Node* temp;if (top == NULL) return;temp = top;top = top->link;free(temp);
}int Top() {if (top == NULL) {printf("Error: Stack is empty\n");return -1;  // Return an error value or handle error appropriately}return top->data;
}bool IsEmpty() {if (top == NULL) {return true;}return false;
}// 反转链表到栈
void ReverseToStack() {while (head != NULL) {Push(head->data);Node* temp = head;head = head->link;free(temp);}
}int main() {Insert(2, 1);Insert(4, 2);Insert(6, 3);Insert(5, 4);Print(head);ReverseToStack();Print(top);return 0;
}

使用数组实现的栈反转一个链表

#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>typedef struct Node {int data;struct Node* link;
}Node;
Node* head; // Linked List// Stack
#define MAX_SIZE 101
Node* A[MAX_SIZE];
int top = -1;// Linked Listvoid Insert(int data, int n) {Node* temp1 = (Node*)malloc(sizeof(Node));if (temp1 != NULL) {temp1->data = data;temp1->link = NULL;if (n == 1) {temp1->link = head;head = temp1;return;}Node* temp2 = head;int i;for (i = 0; i < n - 2; i++) {temp2 = temp2->link;}temp1->link = temp2->link;temp2->link = temp1;}
}void Delete(int n) {//if (head == NULL) return;Node* temp = head;if (n == 1) {head = temp->link;free(temp);return;}int i;for (i = 1; i < n - 1; i++) {temp = temp->link;}Node* temp1 = temp->link;temp->link = temp1->link;free(temp1);
}//iteration
void Print() {Node* temp = head;printf("List is: ");while (temp != NULL){printf("%d ", temp->data);temp = temp->link;}printf("\n");
}// Stack
void Push(Node* x)
{if (top == MAX_SIZE - 1){printf("Error:stack overflow\n");return;}A[++top] = x;
}void Pop() {if (top == -1) {printf("Error: No element to pop\n");return;}top--;
}Node* Top()
{if (top != -1){return A[top];}return;
}void Reverse()
{if (head == NULL){return;}Node* temp = head;while (temp != NULL){Push(temp);temp = temp->link;}Node* temp1 = Top();head = temp1;Pop();while (top != -1){temp1->link = Top();Pop();temp1 = temp1->link;}temp1->link = NULL;
}int main() {Insert(1, 1);Insert(4, 2);Insert(6, 1);Insert(3, 3);Print();Reverse();Print();return 0;
}

3.3 栈与递归的实现

多个函数嵌套调用的规则是:后调用先返回此时的内存管理实行““栈式管理”

一个递归函数的运行过程类似于多个函数的嵌套调用,只是调用函数和被调用函数是同一个函数。

当在一个函数的运行期间调用另一个函数时,在运行该被调用函数之前,需先完成三件事:

  • 将所有的实在参数、返回地址等信息传递给被调用函数保存;
  • 为被调用函数的局部变量分配存储区;
  • 将控制转移到被调用函数的入口。

从被调用函数返回调用函数之前,应该完成:

  • 保存被调函数的计算结果;
  • 释放被调函数的数据区;
  • 依照被调函数保存的返回地址将控制转移到调用函数。

函数之间的信息传递和控制转移必须通过“栈”来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就为它在栈顶分配一个存储区,每当从一个函数退出时,就释放它的存储区,则当前正运行的函数的数据区必在栈顶。

  • 递归过程指向过程中占用的数据区,称之为递归工作栈
  • 每一层的递归参数合成一个记录,称之为递归工作记录
  • 栈顶记录指示当前层的执行情况,称之为当前活动记录
  • 栈顶指针,称之为当前环境指针

汉诺塔:

汉诺塔问题的描述如下:有三根柱子,A、B、C。A柱子上从下往上按大小顺序叠放着n个圆盘,目标是将这n个圆盘移动到C柱子上。移动过程中必须遵守以下规则:

  • 一次只能移动一个圆盘。

  • 大圆盘不能叠放在小圆盘上。

  • 可以利用B柱子作为辅助柱子。

对于任何一个具体的步骤,实际上都需要进行3次移动:

  • 将上面的 n-1 个盘子从起始柱子(如 A)移到另一个柱子(如 B);
  • 将最大的盘子从起始柱子移到目标柱子(如 C);
  • 最后,将 n-1 个盘子从临时柱子(如 B)移到目标柱子(如 C)。
#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>// 将n个盘子从start移动到end,通过temporary辅助
void towerOfHanoi(int n, char start, char end, char temporary) {if (n == 1) {printf("Move disk 1 from %c to %c\n", start, end);return;}// 首先将n-1个盘子从start移动到temporarytowerOfHanoi(n - 1, start, temporary, end);// 然后将最大的盘子从start移动到endprintf("Move disk %d from %c to %c\n", n, start, end);// 最后将n-1个盘子从temporary移动到endtowerOfHanoi(n - 1, temporary, end, start);
}int main() {int n; // 盘子的数量printf("Enter the number of disks: ");scanf("%d", &n);// 开始移动盘子,'A'是起始位置,'B'是辅助位置,'C'是目标位置towerOfHanoi(n, 'A', 'C', 'B');return 0;
}

3.4 队列 - Queue

3.4.1 抽象数据类型队列的定义

队列(queue)是一种先进先出(first in first out, FIFO)的线性表。它只允许在表的一端进行插入,而在另一端删除元素。

在队列中,允许插人的一端叫做队尾(rear),允许删除的一端则称为队头(front)。

在这里插入图片描述

队列的抽象数据类型的定义:

在这里插入图片描述

3.4.2 链队列–队列的链式表示和实现

在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>#define MAXQSIZE 10
#define OK 1
#define OVERFLOW -1
#define ERROR -2typedef struct Node {int data;struct Node* next;
}Node;
Node* front = NULL;
Node* rear = NULL;void Enqueue(int x) {Node* temp = (Node*)malloc(sizeof(Node));if (!temp) {exit(1); // 内存分配失败,退出程序}temp->data = x;temp->next = NULL;if (front == NULL && rear == NULL) {front = rear = temp;return;}rear->next = temp;rear = temp;
}void Dequeue() {Node* temp = front;if (front == NULL) return;if (front == rear) {front = rear = NULL;}else{front = front->next;}printf("Dequeue : %d\n", temp->data);free(temp);
}int main() {Enqueue(1);Enqueue(2);Enqueue(3);Dequeue();Dequeue();Dequeue();return 0;
}

3.4.3 循环队列–队列的顺序表示和实现

在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS *1
#include <stdio.h>
#include <stdlib.h>#define MAXQSIZE 10
#define OK 1
#define OVERFLOW -1
#define ERROR -2typedef int Status;
typedef struct {int* base;int front;int rear;
} SqQueue;Status InitQueue(SqQueue* Q) {Q->base = (int*)malloc(MAXQSIZE * sizeof(int));if (!Q->base) exit(1); // 使用exit(1)表示错误退出Q->front = Q->rear = 0;return OK;
}Status EnQueue(SqQueue* Q, int e) {if ((Q->rear + 1) % MAXQSIZE == Q->front) {return OVERFLOW; // 队列已满,返回OVERFLOW}Q->base[Q->rear] = e;Q->rear = (Q->rear + 1) % MAXQSIZE;return OK;
}Status DeQueue(SqQueue* Q, int* e) {if (Q->front == Q->rear) {return ERROR; // 队列为空,返回ERROR}*e = Q->base[Q->front]; // 使用引用来修改e的值Q->front = (Q->front + 1) % MAXQSIZE;return OK;
}void DestroyQueue(SqQueue* Q) {free(Q->base);Q->base = NULL;Q->front = Q->rear = 0;
}int main() {SqQueue Q;if (InitQueue(&Q) == OK) {EnQueue(&Q, 1);EnQueue(&Q, 2);EnQueue(&Q, 3);int e;if (DeQueue(&Q, &e) == OK) {printf("%d ", e);}if (DeQueue(&Q, &e) == OK) {printf("%d ", e);}if (DeQueue(&Q, &e) == OK) {printf("%d", e);}}DestroyQueue(&Q);return 0;
}

参考:

教材:严蔚敏《数据结构》(C语言版).pdf

博客:栈的基本性质

视频:深入浅出数据结构、数据结构

相关文章:

【学习笔记】数据结构(三)

栈和队列 文章目录 栈和队列3.1 栈 - Stack3.1.1 抽象数据类型栈的定义3.1.2 栈的表示和实现 3.2 栈的应用举例3.2.1 数制转换3.2.2 括号匹配的检验3.2.3 迷宫求解3.2.4 表达式求值 - 波兰、逆波兰3.2.5 反转一个字符串或者反转一个链表 3.3 栈与递归的实现3.4 队列 - Queue3.4…...

学习python笔记:10,requests,enumerate,numpy.array

requests库&#xff0c;用于发送 HTTP 请求的 Python 库。 requests 是一个用于发送 HTTP 请求的 Python 库。它使得发送 HTTP 请求变得简单且人性化。以下是一些基本的 requests 函数及其用途&#xff1a; requests.get(url, **kwargs) 发送一个 GET 请求到指定的 URL。 i…...

经典神经网络(13)GPT-1、GPT-2原理及nanoGPT源码分析(GPT-2)

经典神经网络(13)GPT-1、GPT-2原理及nanoGPT源码分析(GPT-2) 2022 年 11 月&#xff0c;ChatGPT 成功面世&#xff0c;成为历史上用户增长最快的消费者应用。与 Google、FaceBook等公司不同&#xff0c;OpenAI 从初代模型 GPT-1 开始&#xff0c;始终贯彻只有解码器&#xff0…...

MySQL库与表的操作

目录 一、登录并进入数据库 1、登录 2、USE 命令 检查当前数据库 二、库的操作 1、创建数据库语法 2、举例演示 3、退出 三、字符集和校对规则 1、字符集&#xff08;Character Set&#xff09; 2、校对集&#xff08;Collation&#xff09; 总结 3、操作命令 …...

TTS 语音合成技术学习

TTS 语音合成技术 TTS&#xff08;Text-to-Speech&#xff0c;文字转语音&#xff09;技术是一种能够将文字内容转换为自然语音的技术。通过 TTS&#xff0c;机器可以“说话”&#xff0c;这大大增强了人与机器之间的互动能力。无论是在语音助手、导航系统还是电子书朗读器中&…...

小公司做自动化的困境

1. 人员数量不够 非常常见的场景, 开发没几个, 凭什么测试要那么多, 假设这里面有3个测试, 是不是得有1个人会搞框架? 是不是得有2人搞功能测试, 一个人又搞框架, 有些脚本, 真来得及吗? 2. 人员基础不够 现在有的大公司, 是这样子协作的, 也就是某模块需求谁谁测试的, 那么…...

基于pytorch框架的手写数字识别(保姆级教学)

1、前言 本文基于PyTorch框架,采用CNN卷积神经网络实现MNIST手写数字识别,不仅可以在GPU上,同时也可以在CPU上运行。方便即使只有CPU的小伙伴也可以运行该模型。本博客手把手教学,如何手写网络层(3层),以及模型训练,详细介绍各参数含义与用途。 2、模型源码解读 该模型…...

注意力机制在大语言模型中的应用

在大语言模型中&#xff0c;注意力机制&#xff08;Attention Mechanism&#xff09;用于捕获输入序列中不同标记&#xff08;token&#xff09;之间的关系和依赖性。这种机制可以动态地调整每个标记对当前处理任务的重要性&#xff0c;从而提高模型的性能。具体来说&#xff0…...

qt 实现对字体高亮处理原理

在Qt中实现对文本的字体高亮处理&#xff0c;通常涉及到使用QTextDocument、QTextCharFormat和QSyntaxHighlighter。下面是一个简单的例子&#xff0c;演示如何为一个文本编辑器&#xff08;假设是QTextEdit&#xff09;添加简单的关键词高亮功能&#xff1a; 步骤 1: 定义关键…...

SAP中通过财务科目确定分析功能来定位解决BILLING问题实例

接用户反馈&#xff0c;一笔销售订单做发货后做销售发票时&#xff0c;没有成功过账到财务&#xff0c;提示财户确定错误。 这个之前可以通过VF02中点击小绿旗来重新执行过财动作&#xff0c;看看有没有相应日志来定位问题。本次尝试用此方法&#xff0c;也没有找到相关线索。 …...

充电站,正在杀死加油站

最近&#xff0c;深圳公布了一组数据&#xff0c;深圳的超级充电站数量已超过传统加油站数量&#xff0c;充电枪数量也已超过加油枪数量。 从全国范围看&#xff0c;加油站关停的速度在加快。 充电站正在杀死加油站。 加油站&#xff0c;未来何去何从&#xff1f; 01. 减少 我…...

哪个牌子的超声波清洗机好?四样超卓超声波清洗机独具特色!

眼镜是许多人日常生活中必不可少的工具&#xff0c;然而&#xff0c;相信很多人都有过清洗眼镜的烦恼。传统的清洗眼镜的方法往往不够彻底&#xff0c;容易留下污渍或者划伤镜片。因此&#xff0c;超声波洗眼镜机成为了现代人清洗眼镜的新选择。超声波洗眼镜机通过利用超声波震…...

vue3中若v-model绑定的响应字段出现三级,该如何实现rules验证规则

比如以下内容&#xff1a; 配置的rules内容 const rulesref({title:[{required:true,message:"请输入标题",trigger:"blur"},{max:50,message:"最大不能超过256个字",trigger:"blur"}],Category:[{required:true,message:"请选择…...

Docker-Compose一键部署项目

Docker-Compose一键部署项目 目录 Docker-Compose一键部署项目介绍部署Django项目项目目录结构 docker-compose.ymlnginx的default.conf文件后端Dockerfile文件mysql.env一键部署DNS域名解析引起的跨域问题 介绍 Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的…...

【C++】相机标定源码笔记-线激光点云处理工具类

一个线激光点云处理工具类&#xff0c;它包含了一系列的方法用于处理和分析线激光扫描得到的点云数据。提供的功能包括&#xff1a; 通过文件或直接数据设置点云。计算线激光在机器人坐标系下的精度&#xff0c;输出内点的平均距离、最大距离、最小距离、总点数和内点数。提供了…...

解决Transformer根本缺陷,所有大模型都能获得巨大改进

即使最强大的 LLM 也难以通过 token 索引来关注句子等概念&#xff0c;现在有办法了。 最近两天&#xff0c;马斯克和 LeCun 的口水战妥妥成为大家的看点。这两位 AI 圈的名人你来我往&#xff0c;在推特&#xff08;现为 X&#xff09;上相互拆对方台。 LeCun 在宣传自家最新论…...

如何排查Java应用的死锁

排查Java应用中的死锁问题是一个复杂但重要的任务&#xff0c;因为死锁会导致应用程序停止响应&#xff0c;影响用户体验和系统稳定性。以下是一些方法和步骤&#xff0c;帮助你排查Java应用中的死锁。 1. 理解死锁的概念 在计算机科学中&#xff0c;死锁是指两个或多个线程相…...

JS面试题1

1. 延迟加载JS有哪些方式&#xff1f; defer: 等html全部解析完成&#xff0c;才会执行js代码&#xff0c;顺次执行js脚本 async&#xff1a;是和html解析同步的&#xff0c;不是顺次执行js脚本&#xff08;当有很多个js时&#xff09;&#xff0c;是谁先加载完谁先执行。 <…...

Linux网络 - 再谈、详谈UDP和TCP协议

文章目录 前言预备netstatpidofcat /etc/services 一、UDP协议UDP协议端格式UDP的缓冲区基于UDP的应用层协议 二、TCP协议1.TCP协议段格式确认应答(ACK)机制三次握手疑问1 最后一次客户端发给服务端的ACK请求怎么保证服务端能够收到&#xff1f; 四次挥手疑问2 为什么挥手是四次…...

el-form重置后input无法输入问题

新增用户遇到的问题&#xff1a; 如果你没有为 formData 设置默认值&#xff0c;而只是将其初始化为空对象 {}&#xff0c;则在打开dialog时&#xff0c;正常输入&#xff0c; formdata会变成如下 但是&#xff0c;打开后&#xff0c;直接使用 resetFields 或直接清空表单&…...

Java网络编程(JavaWeb的基础)

Java网络编程&#xff08;JavaWeb的基础&#xff09; 文章目录 Java网络编程&#xff08;JavaWeb的基础&#xff09;前言一、网络编程概述1.1 软件架构&网络基础1.2 网络通信要素:IP/端口/通信协议1.3 传输层协议:tcp/udp 二、网络编程API2.1 InetAddress类2.2 Socket类&am…...

鸿蒙Harmony开发实战案例:使用OpenGL绘制3D图形

XComponent控件常用于相机预览流的显示和游戏画面的绘制,在OpenHarmony上&#xff0c;可以配合Native Window创建OpenGL开发环境&#xff0c;并最终将OpenGL绘制的图形显示到XComponent控件。本文将采用"Native C"模板&#xff0c;调用OpenGL ES图形库绘制3D图形&…...

DM达梦数据库存储过程

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…...

【python】OpenCV—Color Correction

文章目录 cv2.aruco 介绍imutils.perspective.four_point_transform 介绍skimage.exposure.match_histograms 介绍牛刀小试遇到的问题 参考学习来自 OpenCV基础&#xff08;18&#xff09;使用 OpenCV 和 Python 进行自动色彩校正 cv2.aruco 介绍 一、cv2.aruco模块概述 cv2.…...

Java基础知识整理笔记

目录 1.关于Java概念 1.1 谈谈对Java的理解&#xff1f; 1.2 Java的基础数据类型&#xff1f; 1.3 关于面向对象的设计理解 1.3.1 面向对象的特性有哪些&#xff1f; 1.3.2 重写和重载的区别&#xff1f; 1.3.3 面向对象的设计原则是什么&#xff1f; 1.4 关于变量与方…...

知识图谱——Neo4j数据库实战

数据与代码链接见文末 1.Neo4j数据库安装 JDK 安装:https://www.oracle.com/java/technologies/javase-downloads.html Neo4j 安装:https://neo4j.com/download-center/ 配置好 JDK 和 Neo4j 的环境变量...

第十一次Javaweb作业

4.登录校验 4.1会话 --用户打开浏览器&#xff0c;访问web服务器的资源&#xff0c;会话建立&#xff0c;直到有一方断开连接&#xff0c;会话结束。在一次会话中可以包含多次请求和响应。 会话跟踪&#xff1a;一种维护浏览器状态的方法&#xff0c;服务器需要识别多次请求…...

人工智能AI风口已开:如何赋予UI设计与视频剪辑新生命

随着科技的浪潮不断向前推进&#xff0c;人工智能&#xff08;AI&#xff09;正以惊人的速度重塑着我们的世界&#xff0c;特别是在创意产业的核心领域——UI设计与视频剪辑中&#xff0c;AI正逐步成为驱动行业创新与变革的关键力量。在这个AI技术全面开花的新时代&#xff0c;…...

计算机专业课面试常见问题-编程语言篇

目录 1. 程序的编译执行流程&#xff1f; 2. C浅拷贝和深拷贝的区别&#xff1f; 3. C虚函数&#xff1f; …...

CSS|05 继承性与优先级

继承性 一、继承性的特点&#xff1a; 1.外层元素身上的样式会被内层元素所继承 2.如果内层元素与外层元素身上的演示相同时&#xff0c;外层元素的样式会被内层元素所覆盖 二、关于继承性的问题 是不是所有样式都能被继承&#xff1f; 答&#xff1a;并不是所有样式能被继承…...

KVM性能优化之内存优化(宿主机)

linux系统自带了一技术叫透明巨型页&#xff08;transparent huge page&#xff09;&#xff0c;它允许所有的空余内存被用作缓存以提高性能&#xff0c;而且这个设置是默认开启的&#xff0c;我们不需要手动去操作。 Centos下&#xff0c;我们用cat /sys/kernel/mm/transpare…...

【Linux杂货铺】Linux学习之路:期末总结篇1

第一章 什么是Linux? Linux 是 UNIX 操作系统的一个克隆&#xff1b;它由林纳斯 本纳第克特 托瓦兹从零开始编写&#xff0c;并在网络上众多松散的黑客团队的帮助下得以发展和完善&#xff1b;它遵从可移植操作系统接口&#xff08;POSIX&#xff09;标准和单一 UNIX 规范…...

GPT-5的到来:智能飞跃与未来畅想

IT之家6月22日消息&#xff0c;在美国达特茅斯工程学院的采访中&#xff0c;OpenAI首席技术官米拉穆拉蒂确认了GPT-5的发布计划&#xff0c;预计将在一年半后推出。穆拉蒂形象地将GPT-4到GPT-5的飞跃比作高中生到博士生的成长。这一飞跃将给我们带来哪些变化&#xff1f;GPT-5的…...

gin中间件

在web应用服务中&#xff0c;完整的业务处理在技术上包含客户端操作&#xff0c;服务端处理&#xff0c;返回处理结果给客户端三个步骤。但是在在更负责的业务和需求场景。一个完整的系统可能要包含鉴权认证&#xff0c;权限管理&#xff0c;安全检查&#xff0c;日志记录等多维…...

swagger常用注解

最近查看接口文档的时候发现&#xff0c;POST方法中的query没法在swagger中显示&#xff0c;查了才发现这是因为Swagger或OpenAPI规范默认将HTTP POST请求的参数识别为请求体&#xff08;body&#xff09;参数&#xff0c;而不是查询字符串&#xff08;query&#xff09;参数。…...

【Flink metric(1)】Flink指标系统的系统性知识:获取metric以及注册自己的metric

文章目录 一. Registering metrics&#xff1a;向flink注册新自己的metrics1. 注册metrics2. Metric types:指标类型2.1. Counter2.2. Gauge2.3. Histogram(ing)2.4. Meter 二. Scope:指标作用域1. User Scope2. System Scope ing3. User Variables 三. Reporter ing四. System…...

命令模式(Command Pattern)

命令模式&#xff08;Command Pattern&#xff09; 定义 命令模式是对命令的封装&#xff0c;每一个命令都是一个操作&#xff1a;请求的一方发出请求要求执行一个操作&#xff1b;接收的一方收到请求&#xff0c;并执行操作。 命令模式解耦了请求方和接收方&#xff0c;请求…...

掌握Symfony的模板继承:构建强大且灵活的Web界面

掌握Symfony的模板继承&#xff1a;构建强大且灵活的Web界面 在Symfony框架中&#xff0c;模板继承是一个强大的功能&#xff0c;它允许开发者创建可重用的布局模板&#xff0c;并通过扩展这些模板来构建具体的页面。这种机制不仅提高了代码的可维护性&#xff0c;还使得页面结…...

uboot基本使用网络命令和从服务器端下载linux内核启动

网络命令ip地址设置: setenv gmac_debug 0; setenv mdio_intf rgmii; setenv bootdelay 1; setenv ethaddr 00:xxxx:81:70; // mac地址 setenv ipaddr xxx; //开发板 IP 地址 setenv netmask 255.255.255.0; setenv gatewayip xxx.1; setenv serverip xxxx; //服…...

解决ArcGIS导出的svg格式的图片插入Word后的字体问题

背景 在ArcGIS中设置字体为Times New Roman&#xff0c;但导入Word后字体转为等线。 ArcGIS中的Layout 导入Word​​​​​​ 原因分析 Word无法识别嵌入进SVG格式文件中的字体。 解决方案 在Export Layer窗口中&#xff0c;将Embed fonts取消勾选&#xff0c;Convert cha…...

如何确保 Puppet 配置在复杂网络环境中的可靠分发和同步?

在复杂网络环境中确保 Puppet 配置的可靠分发和同步可以采取以下措施&#xff1a; 网络拓扑规划&#xff1a;在复杂网络环境中&#xff0c;首先需要进行网络拓扑规划&#xff0c;确保网络结构合理&#xff0c;并能够支持可靠的分发和同步机制。 Puppet Master 多节点部署&…...

2024最新!将mysql的数据导入到Solr

Solr导入mysql的数据 如何安装导入数据前准备配置Solr的Jar包以及Mysql驱动包1.1、将solr-8.11.3\dist下的两个包进行移动1.2、将mysql-connect包也移动到该位置1.3、重启Solr项目 配置xml2.1、第一步我们需要创建核心2.2、第二步修改xml(这里是结合19年的教程)2.3、 创建data-…...

Python数据分析第二课:conda的基础命令

Python数据分析第二课&#xff1a;conda的基础命令 1.conda是什么? conda是一个开源的包管理系统&#xff0c;可以帮助我们进行管理多个不同版本的软件包&#xff0c;还可以帮助我们建立虚拟环境&#xff0c;以便对不同的项目进行隔离。 简单来说&#xff0c;conda是一个软…...

LayoutInflater加载流程

简介 LayoutInflater在日常的Android开发中是经常使用的类&#xff0c;常常用于XML中View的加载相关流程。本文主要总结一些其常见api的源码流程。 获取LayoutInflater 我们一般会在Activity的onCreate方法中会通过setContentView方法设置自己的布局layoutId&#xff0c;Act…...

PLC数据采集案例

--------天津三石峰科技案例分享 项目介绍 项目背景 本项目为天津某钢铁集团下数字化改造项目&#xff0c;主要解决天津大型钢厂加氢站数字化改造过程中遇到的数据采集需求。项目难点PLC已经在运行了&#xff0c;需要采集里面数据&#xff0c;不修改程序&#xff0c;不影响P…...

基于单片机和LabVIEW 的远程矿井水位监控系统设计

摘要 &#xff1a; 针 对 现 有 矿 井 水 位 监 控 系 统 存 在 结 构 复 杂 和 不 能 远 程 监 控 的 问 题 &#xff0c; 设计了基于单片机和&#xff2c;&#xff41;&#xff42;&#xff36;&#xff29;&#xff25;&#xff37; 的远程矿井水位监控系统 &#xff0c; 详…...

element 表格嵌套表单验证指定行

elementui表格嵌套动态表单&#xff0c;单独验证某一行输入项是否符合校验规则&#xff1b; input动态绑定校验 :prop"imgTable. scope.$index .bxName" <el-form :model"formTable" ref"formTable" inline size"small"><…...

CORE Mobility Errorr的调试

在运行CORE tutorial 3中的mobility示例时&#xff0c;出现如下错误&#xff1a; 当看到这个问题的时候&#xff0c;并没有仔细去分析日志和现象&#xff0c;在core-daemon的进程打印界面只看了一下最后的出错堆栈&#xff1a; 2024-06-27 10:43:48,614 - ERROR - _server:_ca…...

基于weixin小程序乡村旅游系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;商家管理&#xff0c;旅游景点管理&#xff0c;景点类型管理&#xff0c;景点路线管理&#xff0c;系统管理 商家帐号账号功能包括&#xff1a;系统首页&#xff0c;旅游景点管理&…...

详解三种常用标准化 Batch Norm Layer Norm RMSNorm

参考&#xff1a; BN究竟起了什么作用&#xff1f;一个闭门造车的分析《动手学深度学习》7.5 节 深度学习中&#xff0c;归一化是常用的稳定训练的手段&#xff0c;CV 中常用 Batch Norm&#xff1b; Transformer 类模型中常用 layer norm&#xff0c;而 RMSNorm 是近期很流行…...