数据结构——中缀表达式

利用中缀表达式直接求值

要实现中缀表达式直接求值,必须设置两个栈,一个栈用于存放操作数,记作 OPND; 另一个栈用于存放操作符,记作 OPTR

中缀表达式求值算法步骤如下:

  1. 初始化:操作符栈中放置一个元素 @
  2. 依次读取中缀表达式中的每一个字符,对于不同类型的字符按以下情况处理:
    1. 若读到的是操作数,则压入操作数栈,并读取下一个字符。
    2. 若读到的是操作符 c,则将操作符栈的栈顶元素 pre_op与之进行比较,会出现以下 3 种情况:
      • pre_op < c,则将 c 入栈,并读取下一个字符。
      • pre_op = c,则将 pre_op 出栈,并读取下一个字符。
      • pre_op > c,则将 pre_op 出栈,并在操作数栈中退栈 2 次,依次得到操作数 ba,然后进行 a pre_op b 运算,将运算结果压入操作数栈。
  3. 扫描完毕时,操作数栈中只有一个元素,即计算结果。

IMG_20241026_164151

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//中缀表达式,实数
double Expression_Eval2()
{
SeqStack<char, 100> OPTR;
SeqStack<double, 100> OPND;
OPTR.Push('@');
char ch = getchar();
while (ch != '@' || OPTR.Top() != '@')
{
if (isdigit(ch) || ch == '.')
{
// 处理多位数和小数
string number;
while (isdigit(ch) || ch == '.')
{
number += ch;
ch = getchar();
}
OPND.Push(stod(number));
}
else
{
char pre_op = OPTR.Top();
switch (Precede(pre_op, ch))
{
case '<':
OPTR.Push(ch);
ch = getchar();
break;
case '=':
OPTR.Pop();
ch = getchar();
break;
case '>':
char pre_op = OPTR.Pop();
double b = OPND.Pop();
double a = OPND.Pop();
OPND.Push(Operate(a, pre_op, b));
break;

}
}
}
return OPND.Top();
}

Precede(pre_op, ch)为进行算符优先级比较的函数

Operate(a, pre_op, b)为计算函数

利用后缀表达式求值

优点

后缀是指把操作符放在两个操作数的后面。采用后缀表示的算术表达式被称为后缀表达式后缀算
在后缀表达式中,完全按照操作符出现的先后顺序进行计算过程,不存在括号,也不存在优先级的差别

将中缀表达式转换成等价的后缀表达式求值时,不需要再考虑操作符的优先级,只需从左到右扫描一边后缀表达式即可。只需设置一个OPND栈用于存放操作数

先将中缀表达式转成后缀表达式

把中缀表达式转换为后缀表达式算法的基本思路如下:

  1. 初始化:操作符栈中放置一个元素 @

  2. 依次读入中缀表达式中的每个字符

    ,对于不同类型的字符按不同情况进行处理:

    1. 若读到的是操作数,则输出该操作数,并读取下一个字符。
    2. 若读到的是左括号 (,则把它压入 OPTR 栈中,并读取下一个字符。
    3. 若读到的是右括号 ),则表明括号内的中缀表达式已经扫描完毕,将 OPTR 栈从栈顶直到左括号之前的操作符依次出栈并输出,然后将左括号出栈,并读取下一个字符。
    4. 若读到的是操作符 c,则将操作符栈的栈顶元素 pre_op与之进行比较:
      • pre_op < c,则将 c 入栈,并读取下一个字符。
      • pre_op >= c,则将 pre_op 出栈并输出。
    5. 若读到的是结束符 @,则把栈中剩余的操作符依次出栈并输出,即可得到转换成的后缀表达式。

后缀表达式求值

后缀表达式求值算法的基本思路如下

  1. 依次读入后缀表达式中的每个字符

    ,直至表达式结束。

    • 若读到的是操作数,则入 OPND 栈。
    • 若读到的是操作符,则在 OPND 栈中退栈两个元素(先退出的是操作符右侧,后退出的是操作符左侧),然后用该操作符进行运算,并将运算结果压入 OPND 栈中。
  2. 后缀表达式扫描完毕时,若 OPND 栈中仅有一个元素,即为运算结果。

数据结构——线性表

线性表的定义

线性表(List):零个或多个数据元素的有限序列。

c8b3abeb72098a922a9ac4f6ff62f563

顺序存储结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef SEQLIST_H
#define SEQLIST_H

#include <iostream>
using namespace std;

// 模板类定义,表示顺序表
template <class T, int MaxSize>
class SeqList {
T data[MaxSize]; // 存储顺序表数据的数组
int length; // 顺序表的长度
public:

};

#endif

链式存储结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef LINKLIST_H
#define LINKLIST_H

#include <iostream>
using namespace std;

template <class T>
struct Node {
T data;
Node<T>* next;
};

template <class T>
class LinkList {
Node<T>* head;

public:

};
#endif // LINKLIST_H

归并排序

归并排序 | 菜鸟教程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 友元函数:合并两个有序顺序表
template <class T, int MaxSize1, int MaxSize2>
SeqList<T, MaxSize1 + MaxSize2> Merge(const SeqList<T, MaxSize1>& list1, const SeqList<T, MaxSize2>& list2) {
SeqList<T, MaxSize1 + MaxSize2> MergedList; // 创建一个新的顺序表
int i = 0, j = 0, n = 0; // 定义三个指针,分别表示 list1、list2 和合并表的当前索引

// 获取两个顺序表的长度
int length1 = list1.length;
int length2 = list2.length;

// 合并两个有序顺序表
while (i < length1 && j < length2) {
if (list1.data[i] <= list2.data[j]) {
MergedList.data[n++] = list1.data[i++];
}
else {
MergedList.data[n++] = list2.data[j++];
}
}

// 将 list1 中剩余的元素插入到 MergedList 中
while (i < length1) {
MergedList.data[n++] = list1.data[i++];
}

// 将 list2 中剩余的元素插入到 MergedList 中
while (j < length2) {
MergedList.data[n++] = list2.data[j++];
}

MergedList.length = n; // 设置合并后的顺序表长度
return MergedList;
}

数字逻辑电路——CMOS逻辑门电路

MOS管

MOSFET全称金属-氧化物-半导体场效应三极管

从载流子极性来看,分为NMOS(电子型)管PMOS(空穴型)管两种

按照导电机制的不同,MOS管又可以分为增强型耗尽型

因此MOSFET共有四种:E型NMOS管、D型NMOS管、E型PMOS管、D型PMOS管

NMOS和PMOS区别

MOS管的管脚有三个:源极S(source)、栅极G(Gate)和漏极(Drain)

MOS管有两种:一个是PMOS管,一个是NMOS管;PMOS管就是positive管,是积极的管,而NMOS管是negative管,是消极的管。积极的管就是顺应潮流,顺势而为;消极的管就是违背趋势,逆流而上。
很显然,电流从源极(输入端)到漏极(输出端),那就是顺势而为,因为源极就是源头嘛,因此这种管就是PMOS管;而电流要是从漏极(输入端)到源极(输出端),那就是逆流而上,是NMOS管。

判定是N沟道MOS还是P沟道MOS:
箭头指向G极的是N沟道
箭头背向G极的是P沟道

从导通特性上区分:

NMOS:当电压高于阈值电压可以导通;当电压低于阈值电压不能导通

PMOS:当电压高于阈值电压不能导通;当电压低于阈值电压可以导通

22e26add55da2a09c803a6ed67ee4777

增强型和耗尽型的区别

NMOS管为例:

当$\ V{GS}=0$时没有导电沟道,需要依靠$\ V{GS}$的作用才能产生导电沟道,称为增强型FET

当$\ V{GS}=0$时有导电沟道,需要依靠$\ V{GS}$的作用是削弱导电沟道,称为耗尽型FET

CMOS逻辑门电路

N沟道和P沟道增强型MOS管组成的电路称为互补MOSCMOS电路。

非门

img

或门和或非门

4

img

与门和与非门

5

7

异或门和同或门

1

2

参考文献

【硬核科普】带你认识CPU第00期——什么是MOSFET_哔哩哔哩_bilibili

【硬核科普】带你认识CPU第01期——什么是逻辑门_哔哩哔哩_bilibili

NMOS管与PMOS管的区别与总结_pmos和nmos的区别-CSDN博客

数据结构——类模板

类模板

类模板是一种用于创建通用类的机制,它可以让程序员编写一次类,然后让它适用于多种数据类型,在实际编程中非常实用。

优点:使用类模板时,可以为同一个类使用不同的数据类型,这使得代码更加灵活。例如,一个通用的栈类模板可以用于intdoublechar等不同类型,而不需要为每种类型分别定义栈类。

定义方式

1
2
3
4
5
6
7
8
9
template <class T>
class MyStack {
public:
// 构造函数、析构函数、入栈、出栈等函数
private:
T* data;
int top;
};

template 关键字用于声明类模板

函数声明与定义

类内定义

如果是在类内进行函数定义,则不需添加template<>,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 类模板
template <class T1,class T2>
class Data {
private:
T1 a;
T2 b;
public:
Data(T1 a, T2 b){
this->a = a;
this->b = b;
cout << "Data的有参构造" << endl;
}
void showData()
{
cout << a << " " << b <<endl;
}
};

类外定义

如果成员函数在类外定义

  • 在每个成员函数前必须添加template<>
  • 作用域需要添加<>修饰。
1
2
3
4
5
template<class T1,class T2>
void Data<T1,T2>::showData()
{
cout << a << " " << b << endl;
}

友元函数

1
2
3
4
5
6
7
8
9
//类内声明
friend void MyPrint(Data<T3, T4> &ob);

//类外定义
template <typename T3,typename T4>
void MyPrint(Data<T3, T4> &ob)
{
cout << "函数模板友元:" << ob.a << " " << ob.b << endl;
}

模板类分文件书写问题

方案一

将类的声明和成员函数的定义全部写在一个.h文件中,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef _DATA_H_
#define _DATA_H_
#include <iostream>
using namespace std;

// 类模板
template <class T1, class T2>
class Data {
private:
T1 a;
T2 b;
public:
void showData();
};

template<class T1, class T2>
void Data<T1, T2>::showData()
{
cout << a << " " << b << endl;
}

#endif // !_DATA_H_

方案二

若将函数的定义写在.cpp文件中,main.cpp中调用时,一定要同时include”.h”和“.cpp”文件,否则编译错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//main.cpp
#include <iostream>
using namespace std;

#include "Data.h"
#include "Data.cpp"
int main()
{
// 类模板实例化对象

Data<int, char> ob2(100,'A');
ob2.showData();
return 0;
}

参考文献

【035】深入理解C++类模板(最全讲解):从基础到实战-CSDN博客

数据结构——广义表

广义表的定义和相关概念

广义表是线性表的推广,其中的元素可以是原子(即不可再分的基本数据项),也可以是子表。广义表的一些常用术语包括:

  • 长度:广义表的长度是其顶层元素的个数。
  • 深度:广义表中元素嵌套的最大深度。
  • 表头:广义表中的第一个元素。
  • 表尾:去掉表头后剩下的部分。

例子

  • A = ()
  • B = (a, b, c)c)`
  • C = (a, (b, c, d), e)
  • D = (a, b, (e, f, g))
  • E = ((a, b), c, (d, e, (f, g)))
  • F = ((), ((), ()))

下表展示了图片中的几个广义表的长度、深度、表头和表尾的具体值:

广义表 长度 深度 表头 表尾
A 0 1 ()
B 3 1 a (b, c)
C 3 2 a ((b, c, d), e)
D 3 3 a (b, (e, f, g))
E 4 3 (a, b) (c, (d, e, (f, g)))
F 3 3 () ((), ((), ()))

广义表的存储结构

IMG_20241021_153619

由于广义表中的每个元素可能是原子或子表,因此在广义表的存储结构中存在两类结点:

  1. 原子节点:用于存储单个元素。
  2. 子表节点:用于存储子表的指针。

为了区分元素是原子还是子表,结构中还设置了一个标识域 type

1
enum GListNodeType { ATOM, LIST }; // 结点类型:原子或子表

当type值为1,说明存入原子的值;为2,说明存入子广义表的头指针

广义表定义如下:

1
2
3
4
5
6
7
8
9
typedef char ElemType; // 原子的类型定义为字符类型
typedef struct GListNode {
GListNodeType type; // 类型域,表示该节点是原子还是子表
union {
ElemType data; // 如果是原子节点,则存储数据
struct GListNode *sublist; // 如果是子表节点,则存储指向子表的指针
};
struct GListNode *next; // 指向下一个表节点的指针
} GListNode, *GList; // GList 表示广义表的指针类型

IMG_20241021_154058

代码

求广义表长度

1
2
3
4
5
6
7
8
9
10
// 求广义表的长度
int LengthGList(GList list) {
int length = 0;
GList current = list;
while (current != NULL) {
length++;
current = current->next;
}
return length;
}

求广义表深度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 求广义表的深度
int DepthGList(GList list) {
if (list == NULL) {
return 1; // 空表深度为 1
}
int maxDepth = 1;
GList current = list;
while (current != NULL) {
if (current->type == LIST) {
int sublistDepth = DepthGList(current->value.sublist) + 1;
if (sublistDepth > maxDepth) {
maxDepth = sublistDepth;
}
}
current = current->next;
}
return maxDepth;
}

打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 打印广义表
void PrintGList(GList list) {
if (list == NULL) {
printf("()");
return;
}
printf("(");
GList current = list;
while (current != NULL) {
if (current->type == ATOM) {
printf("%c", current->value.data);
} else if (current->type == LIST) {
PrintGList(current->value.sublist);
}
if (current->next != NULL) {
printf(", ");
}
current = current->next;
}
printf(")");
}

线性表章节小结

IMG_20241021_155320

数据结构——矩阵压缩

特殊矩阵的压缩

对称矩阵

关键点:

  • 是选择上三角行主序存储还是下三角列主序存储
  • 组的下标是从1开始还是0开始存储

image-20241019163345497

image-20241019163357393

三件矩阵

注意点:

  • 理解下三角列序和下三角行序的差异,后者是计算a[i][j]后面的元素数量和,再用总数减去后面,得到前面元素数量;前者是直接计算a[i][j]前面元素数量

  • 一位数组空间为n(n+1)/2+1,数组最后一位为0

image-20241019162703259

image-20241019162719970

对角矩阵

image-20241019163805806

稀疏矩阵的压缩存储

三元组表

IMG_20241019_164539

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 用于表示稀疏矩阵中非零元素的结构体
struct Triplet {
int row;
int col;
int value;
};

// 用于表示稀疏矩阵的类
class SparseMatrix {
private:
int rows;
int cols;
vector<Triplet> triplets;

public:
// 构造函数,用于初始化矩阵的维度
SparseMatrix(int rows, int cols) : rows(rows), cols(cols) {}

// 添加非零元素到矩阵的方法
void add_element(int row, int col, int value) {
if (value != 0) {
triplets.push_back({ row, col, value });
}
}

// 以三元组形式显示稀疏矩阵的方法
void display() const {
for (const auto& triplet : triplets) {
cout << "行: " << triplet.row << ", 列: " << triplet.col << ", 值: " << triplet.value << endl;
}
}

// 将稀疏矩阵转换为密集矩阵表示的方法
vector<vector<int>> to_dense() const {
vector<vector<int>> dense_matrix(rows, vector<int>(cols, 0));
for (const auto& triplet : triplets) {
dense_matrix[triplet.row][triplet.col] = triplet.value;
}
return dense_matrix;
}
};

朴素转置

IMG_20241019_181951

因为矩阵A的列是矩阵B的行,所以以此遍历A的列,将其存入新的三元组顺序表

不能直接交换i和j的值的原因:因为三元组表是行优先顺序,如果直接交换就是列优先

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
vector<Triplet> transposeTripletMatrix(const vector<Triplet>& tripletMatrix) {
// 获取原始三元组矩阵的行数、列数和非零元素个数信息
int rows = tripletMatrix[0].row;
int cols = tripletMatrix[0].col;
int numNonZero = tripletMatrix[0].value;

// 创建转置矩阵的三元组顺序表
vector<Triplet> transposedMatrix;
transposedMatrix.push_back({cols, rows, numNonZero});

// 通过列的顺序插入非零元素到转置矩阵中
if (numNonZero > 0) {
for (int col = 0; col < cols; ++col) {
for (int i = 1; i <= numNonZero; ++i) {
if (tripletMatrix[i].col == col) {
transposedMatrix.push_back({tripletMatrix[i].col, tripletMatrix[i].row, tripletMatrix[i].value});
}
}
}
}

return transposedMatrix;
}

快速转置

IMG_20241019_235512

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
vector<Triplet> fastTransposeTripletMatrix(const vector<Triplet>& tripletMatrix) {
// 获取原始三元组矩阵的行数、列数和非零元素个数信息
int rows = tripletMatrix[0].row;
int cols = tripletMatrix[0].col;
int numNonZero = tripletMatrix[0].value;

// 创建转置矩阵的三元组顺序表
vector<Triplet> transposedMatrix(numNonZero + 1);
transposedMatrix[0] = {cols, rows, numNonZero};

if (numNonZero > 0) {
// 初始化每一列中非零元素的个数和位置
vector<int> count(cols, 0);
vector<int> index(cols + 1, 2);

// 统计每一列中非零元素的个数
for (int i = 1; i <= numNonZero; ++i) {
count[tripletMatrix[i].col]++;
}

// 计算每一列在转置矩阵中的起始位置
for (int i = 1; i < cols; ++i) {
index[i + 1] = index[i] + count[i - 1];
}

// 填充转置矩阵的三元组顺序表
for (int i = 1; i <= numNonZero; ++i) {
int col = tripletMatrix[i].col;
int pos = index[col];
transposedMatrix[pos] = {tripletMatrix[i].col, tripletMatrix[i].row, tripletMatrix[i].value};
index[col]++;
}
}

return transposedMatrix;
}

十字链表

IMG_20241019_170755

定义

类中定义了两个链表数组,用于存储每一行和每一列的头节点指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 节点定义
struct OLNode {
int row; // 行号
int col; // 列号
int value; // 元素值
OLNode* right; // 指向右边的节点
OLNode* down; // 指向下面的节点

OLNode(int r, int c, int val) : row(r), col(c), value(val), right(nullptr), down(nullptr) {}
};

// 十字链表类
class OrthogonalList {
private:
int rows, cols;
vector<OLNode*> row_heads; // 行头链表数组
vector<OLNode*> col_heads; // 列头链表数组
}

构造函数

row_heads.resize(rows, nullptr); 是用于调整 row_heads 向量的大小,使其包含 rows 个元素,并将每个元素初始化为 nullptr

1
2
3
4
OrthogonalList(int rows, int cols) : rows(rows), cols(cols) {
row_heads.resize(rows, nullptr);
col_heads.resize(cols, nullptr);
}

插入函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 插入元素
void insert(int r, int c, int value) {
if (value == 0) return;

// 检查是否已存在节点,若存在则更新值
OLNode* current = row_heads[r];
while (current && current->col < c) {
current = current->right;
}
if (current && current->col == c) {
current->value = value; // 更新已有节点的值
return;
}

OLNode* newNode = new OLNode(r, c, value);

// 插入到行链表中
if (!row_heads[r]) {
//如果该行没有头节点,则成为该行头节点
row_heads[r] = newNode;
}
else {
current = row_heads[r];
OLNode* prev = nullptr;
//current向下遍历,直到遍历到该行链表的最后一个,或者到达插入的列的位置
//注意,该插入方式如果遇到该位置已有节点存在的情况,会用新节点覆盖旧节点
while (current && current->col < c) {
prev = current;
current = current->right;
}
//若prev存在,则说明头节点不为零,若为空则说明插在链表最前面
if (prev) {
prev->right = newNode;
}
else {
row_heads[r] = newNode;
}
newNode->right = current;
}

// 插入到列链表中
if (!col_heads[c]) {
col_heads[c] = newNode;
}
else {
current = col_heads[c];
OLNode* prev = nullptr;
while (current && current->row < r) {
prev = current;
current = current->down;
}
if (prev) {
prev->down = newNode;
}
else {
col_heads[c] = newNode;
}
newNode->down = current;
}
}

打印函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   // 打印矩阵
void print() {
for (int i = 0; i < rows; ++i) {
OLNode* current = row_heads[i];
for (int j = 0; j < cols; ++j) {
if (current && current->col == j) {
cout << current->value << " ";
current = current->right;
} else {
cout << "0 ";
}
}
cout << endl;
}
}
};

参考文献

【数据结构】特殊矩阵的压缩存储/对称矩阵/三角矩阵/对角矩阵(含经典题讲解)_哔哩哔哩_bilibili

【每个人都听得懂的】稀疏矩阵的快速转置算法_哔哩哔哩_bilibili

前言

史书上轻轻翻过的一页,便是他们波澜壮阔的一生。鉴史可以明志,知古方能鉴今,以史为镜,可以知兴替。我虽然是一位理科生,但我心里一直深知历史的重要,历史不是冰冷的文字,而是充满温度的生命轨迹。那些沉浮于历史长河中的伟大人物,用他们的智慧与勇气改写了时代的篇章,也为后人留下了宝贵的思想财富。迷茫时读史,退缩时读史,困惑时读史,或许都会有不一样的收获。


刘邦,

38岁,一事无成,骗吃骗喝,娶了老婆。
48岁,被逼无奈,起兵反秦。
50岁,被项羽吓得跪地求饶。
51岁,被项羽打得丢盔弃甲,老婆、父亲都被抓。
54岁,建立汉朝,君临天下。
55岁,干翻曾经对他豪横的人。

七年时间,纵横四海,天下归一。
60岁,出征匈奴。
62岁,衣锦还乡时,写下大气磅礴的《大风歌》:

大风起兮云飞扬,
威加海内兮归故乡,
安得猛士兮守四方。

有人少年得志,有人大器晚成。人生从来没有太晚的开始,尽自己最大的努力,你也只是在等待一个时机。


前言

我语文不好,始终羡慕那些说话出口成章,底蕴深厚,时不时冒出几句金句的人,所以,为了让我这么一个没有什么文化底蕴的人,说话不至于太俗气,既然我不会说,那我便学名人说,便有了记录名言警句的习惯,此外,这些名言警句背后的哲理,也经常能让我醍醐灌顶。之前在网上看到一些名言警句,就会顺手记录在手机的备忘录上,现在整理下来,希望再看到时能有所收获。我说的名人警句,不光是真正意义上的名人说的,其中也有部分是我看到网友写的,我觉得,学习一切可以学习的,任何人都可以是我的老师,他们很多的文字也能给我很大感触。


1. 励志与挑战

  • 十年运到龙困井,一朝得势入青云
  • 命定的局限尽可永在,不屈的挑战却不可须臾或缺 ——《霍乱时期的爱情》
  • 受任于败军之际,奉命于危难之间 ——诸葛亮《出师表》
  • 他时若遂凌云志,敢笑黄巢不丈夫 ——唐代罗隐
  • 燕雀安知鸿鹄之志 ——《史记·陈涉世家》
  • 攻心为上,攻城为下 ——《孙子兵法》
  • 技不外漏,海不露底,千两黄金不卖道,十字街头送故交
  • 将军不下马,各自奔前程
  • 胜败兵家事不期,包羞忍耻是男儿 ——宋代陆游《秋夜将晓出篱门迎凉有感二首》
  • 大丈夫生于天地之间,岂能郁郁久居人下 ——《三国志·蜀书》
  • 江东子弟多才俊,卷土重来未可知 ——唐代杜牧《题乌江亭》
  • 一位大师曾经说过要像水一样,那我应该就是海啸吧!
  • 命数如织,当为磐石

2. 爱情与亲密关系

  • 我想要爱、激情、真诚和亲密的关系、性,这些使我鲜活,然唯有灵魂的交流使我平静
  • 一顾倾人城, 再顾倾人国。宁不知倾城与倾国?佳人难再得 ——《汉书·李延年传》
  • 不见鹿,不见鲸,亦不见你

3. 人生与哲理

  • 你不妨大胆去冒险,只因生命终将逝去 ——尼采
  • 人生不需要意义,意义需要人生
  • 我曾踏足山巅,也曾进入谷底,二者都让我受益良多
  • 旧游无处不堪寻。无寻处、惟有少年心 ——宋代辛弃疾《南乡子·登京口北固亭有怀》
  • 天下万般兵刃 唯有过往伤人最深
  • 我见青山多妩媚,料青山见我应如是 ——宋代辛弃疾《贺新郎·别茂嘉十二弟》
  • 如果真相带来痛苦,谎言只会雪上加霜
  • 花团锦簇的节日用来铭记逝者,而我,宁愿被人遗忘
  • 坟墓里寂静无比,埋葬你的是所有你没说出口的话
  • 世界既不黑也不白,而是一道极致的灰
  • 梦醒时夜续,惊慌失措

4. 自由与个性

  • 因为生活过于教条,所以格外欣赏自由野性的东西
  • 是俗是雅,我已经分不清了,我只知道月亮正圆,我若不看一眼,倒显得我不解风情了
  • 国王们以世袭的权柄和虚名逼你下跪,诺克萨斯要你站起来,要你在荣耀中重获新生

5. 孤独与感伤

  • 忽有清风化剑气,直斩少年二十意
  • 生活的底片从来都不是遥远的白日梦,而是热爱生活的自己
  • 黄昏见证虔诚的信徒,巅峰诞生虚伪的拥护
  • 假作真时真亦假,无为有处有还无 ——《红楼梦》
  • 林深时雾起,不见归处
  • 海蓝时浪涌,望而却步

6. 经典与历史

  • 朕非亡国之君,臣乃亡国之臣 ——明代崇祯帝与大臣对话
  • 满腹经纶书香气,腹有诗书气自华
  • 天下熙熙,皆为利往 ——《史记·货殖列传》
  • 竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生 ——宋代苏轼《定风波》
  • 回首向来萧瑟处,归去,也无风雨也无晴 ——宋代苏轼《定风波》
  • 既生瑜何生亮 ——《三国演义》
  • 欲买桂花同载酒,终不似,少年游 ——宋代刘过《唐多令》
  • 愿以深心奉尘刹,不予自身求利益 ——明代张居正

7.长文

最绝望的人有三种,第一种是始出初之人,他没有同类,而身边全是未知,他无时无刻都在害怕着,你无法想象他是如何作为第一个人活下去的,因为他活着这件事本身,就已经违背了他被创造出来的本能。第二种是终焉之人,他也曾拥有同类,而现在,他便是最后之人,在迎接终焉时,他并不会感到孤单,因为同类早已用别的方式存在于他身上。他只能在可以活动的范围内活动,这使他对环境非常了解,了解到令自己感到绝望。第三种是活着之人,活着本身就是一种折磨,当你足够冷静时,你会发现,你做的一切都对自己没有任何意义,你本是一粒尘埃,最后也终回归尘埃,你认为的有意义只是你本能对你的奴役


我不喜欢读书,但却无比向往哲思的海洋,所以游戏常常成为引导我思考的老师,与其是娱乐消遣的工具,我更愿意把他当做一部艺术品,其背后可以是一次次引人深思的哲理,其背后也可以是作者对某种人,对某件事,对某个价值观的思考,其背后还可以是一部引人入胜的恢宏世界观与史诗,无论是哪一种都令我着迷,引领我思考


前言

我从小就不是一个喜欢读书的人,不管是小说还是文学作品,感觉文字始终无法对我产生兴趣,回忆起来,我自从初中开始,书读的可能最多的就是课本,课外甚至没有完整地看下来一本书。但我一直深知读书的重要性,而且把读书的感悟写下是很有意义的一件事,希望自己以后可以多读书,自勉。

数据结构——循环队列

思考

IMG_20241015_235239

头指针front指向的位置为队列头元素的前一个位置

尾指针rear指向的位置为队列尾元素

以上目的:为了区分队列是否为空或已满的判断条件

  • 队列为空:当 frontrear 相等时,说明队列中没有元素,此时为空队列。
  • 队列已满:当 (rear + 1) % MAXSIZE == front 时,说明队列已满,因为rear 紧跟在 front 的前面,队列的最后一个位置不可用,否则会与空队列的情况冲突。

注意事项:

由于判断队列满的条件需要 front 位置与 rear + 1 相等,意味着最多只能使用 MAXSIZE - 1 个元素的位置,这种策略用于避免空队列与满队列状态混淆。

若不做此区分,队空和队满的判断条件都是(rear + 1) % MAXSIZE == front,会发生混淆

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//循环队列
const int MAXSIZE = 100;
typedef struct {
int data[MAXSIZE];
int front;
int rear;
}Queue;

//初始化队列
void InitQueue(Queue& Q) {
Q.front = 0;
Q.rear = 0;
}

//判断队列是否为空
bool IsEmpty(Queue Q) {
return Q.front == Q.rear;
}

//判断队列是否已满
bool IsFull(Queue Q) {
return (Q.rear + 1) % MAXSIZE == Q.front;
}

//入队
void push(Queue& Q, int x) {
if (IsFull(Q)) {
cout << "队列已满" << endl;
return;
}
//指针先向后移动,再赋值
Q.rear = (Q.rear + 1) % MAXSIZE;
Q.data[Q.rear] = x;
}

//出队
int pop(Queue& Q) {
if (IsEmpty(Q)) {
cout << "队列为空" << endl;
return -1;
}
//指针先向后移动,再返回值
Q.front = (Q.front + 1) % MAXSIZE;
return Q.data[Q.front];
}

//获取队头元素
int getFront(Queue Q) {
if (IsEmpty(Q)) {
cout << "队列为空" << endl;
return -1;
}
//指针向后移动,再返回值,front指针指向的是队头元素的前一个位置
return Q.data[(Q.front + 1) % MAXSIZE];
}

//获取队尾元素
int getRear(Queue Q) {
if (IsEmpty(Q)) {
cout << "队列为空" << endl;
return -1;
}
//rear指针指向的是队尾元素
return Q.data[Q.rear];
}

//测试函数
int main() {
Queue Q;
InitQueue(Q);
push(Q, 1);
push(Q, 2);
push(Q, 3);
cout << getFront(Q) << endl;
cout << getRear(Q) << endl;
cout << pop(Q) << endl;
cout << getFront(Q) << endl;
cout << getRear(Q) << endl;
return 0;
}

注意事项:

入队逻辑:rear指针先向后移动一位,再赋值

出队逻辑:front指针先向后移动一位,再返回值,因为front指针指向的是队头元素的前一个位置

参考文献

【队列&循环队列】手动实现循环队列,掌握循环队列的每一处细节_哔哩哔哩_bilibili

0%