ZhX589 发布的文章

Linux系统的安装

目录:

1. 系统的选择

Linux发展了几十年,到今天已经诞生了上百种发行版 我个人建议新手使用Ubnutu系的发行版(或debian系),软件源十分丰富,而且入手难度低;

当然,国产系统也是不错的选择,比如Deppin、统信USO

这里以Ubuntu系的LinuxMint为例来演示,这也是我现在正在使用的发行版。

下载ISO镜像文件

访问LinuxMint官网(或其他发行版官网)下载ISO系统镜像,里面有很多镜像地址,如alibaba的。

制作启动盘

目前启动盘制作主要可以用以下几种方法(都要准备一个至少8G的U盘):

方法1:将文件写入U盘中

优点:历史悠久,稳定; 缺点:每次制作都要格式化U盘,且只能同时制作1张

方法2:使用Ventoy

Ventoy只需要在U盘中放入镜像文件(放进去就行)即可作为启动盘,可以一次放入多个系统镜像,也可以放入杂七杂八的文件。建议使用64G以上的U盘(最低8G不影响)因为以后你可能像我一样放七八个镜像文件、一包安装包和一堆备份文件

注意:请确保U盘容量正常不虚标,否则制作将失败

首先前往官网下载相应系统最新版本的Ventoy,插入U盘,正对电脑启动方式选择对应的分区类型。

简单来说,你无法在MBR磁盘上安装UEFI模式的操作系统,也无法在GPT磁盘上安装传统的BIOS模式操作系统。

所以如果待重装系统的电脑是UEFI模式,则需要选择GPT类型,反之则选择MBR类型

一般来讲,新电脑一般是UEFI模式的。

插入U盘后

设备中选择到你的U盘,点击安装(确认U盘内没有重要文件),就会格式化U盘并安装Ventoy到你的U盘上。此时会创建两个分区,

其中Ventoy用于放置你的ISO文件或其他需要存储的文件,VTOYEFI则是引导电脑寻找文件的分区。安装好并放入刚才下载的ISO文件后,重启电脑至启动设备菜单(需要按下特殊按键。

常见的按键有哪些?(需要快速连按)

  • 最常见的是 F12Esc
  • 华硕 (ASUS)F8
  • 技嘉 (GIGABYTE)F12
  • 微星 (MSI)F11
  • 联想 (Lenovo)F12 或 笔记本侧的 Novo键
  • 戴尔 (Dell)F12
  • 惠普 (HP)F9Esc
  • 宏碁 (Acer)F12
  • 三星 (Samsung)EscF2F10
  • Surface:按住 音量减键 再按一下电源键

随后选择通过你的U盘启动。随后你会打开一个体验LinuxMint的界面,双击运行桌面上的引导程序,你就可以根据提示与选项安装系统了。

注意:

安装后可能出现无法联网的现象,这是因为驱动缺失,建议使用网线/蓝牙先暂时连接网络,等按照欢迎画面的提示把

驱动和软件更新安装好就行了

常用软件推荐:

首先可以安装QQ微信,使用Firefox搜索进入官网下载即可。建议下载AppImage包,这个给到运行后即可直接使用,卸载只需将该文件删除;

# 进入下载目录
cd Downloads/
# 授予执行权限
chmod +x QQ_*.AppImage # 换成实际文件名
# 或者使用通配符
chmod +x *.AppImage

或下载.deb安装包

# 建议使用apt安装,避免依赖问题
sudo apt install ./QQ_*.deb # 一定要加上`./`,告诉apt于本地寻找文件而非在线查找
# 或者使用dpkg,最原始的方式
sudo dpkg -i QQ_*.deb # `./`加不加皆可

其他系统自行搜索教程

其他生产力工具:

markdown编辑器:Typora

sudo apt install typora

代码编辑器:sublime text

sudo apt install sublime-text

修图软件:GIMP(Linux上的PS)

sudo apt install gimp

游戏推荐

**经典中的经典:**steam

sudo apt install steam

吉祥物Tux

sudo apt install supertux
sudo apt install supertuxkart
sudo apt install extremetuxracer

**开元RTS:**Warzone2100

sudo apt install warzone2100

剩下的你们就自行探索啦!

二叉树的遍历

作者为蒟蒻高中牲,若有不妥之处请多多包含,并望于评论区指正,不胜感激!

目录:

什么是二叉树

二叉树是一个为空或者由不相交的左子树、右子树和根节点组成的集合。其中左右子树也为二叉树。

例如,这是一个二叉树:

flowchart TD
    A[A] --> B[B]
    A --> C[C]
    B --> D[D]
    B --> E[E]
    C --> F[F]
    C --> G[ ]
    style G fill:none, stroke:none

而这也是一个二叉树(已经退化成一条链了):

flowchart TD
A[1] --> B[2]
A --> None1[ ]
B --> C[3]
B --> None2[ ]
C --> D[4]
C --> None3[ ]
style None1 fill: none, stroke: none
style None2 fill: none, stroke: none
style None3 fill: none, stroke: none

左右子树为空集节点叫做叶子节点。

每一层被填满的二叉树叫做完美二叉树(满二叉树)。按照从上到下、从左到右的顺序编号,只有最后一层缺失、且缺失的编号都在最后在的二叉树叫做完全二叉树

如何存储

(1 动态存储

数据结构中一般这样定义二叉树:

struct Node {
	int value; // 节点的值,可以不止一个
    node *lson, *rson; // 指向左右子节点
}

动态新建一个Node时,使用new运算符动态申请一个。使用完毕记得使用delete命令释放防止内存泄漏。

动态二叉树的优点是不浪费空间,缺点是需要管理,容易出错。

(2 使用静态数组存储

比赛中一般使用这种方法,编码比较简单。

struct Node {
    int value;
    int lson, rson; // 子节点在数组中的编号
} tree[N];

特别地,可以使用单独的一个静态数组实现完全二叉树的存储

二叉树的各种遍历

常见遍历二叉树的方式有一下几种,我以以下树展示

flowchart TD
1 --> 2
1 --> 3
2 --> 4
2 --> 5
3 --> 6
3 --> 01[ ]
4 --> 7
4 --> 8
5 --> 02[ ]
5 --> 9
style 01 fill: none
style 02 fill: none

先序遍历

按照根节点 -> 左子树 -> 右子树的顺序遍历。这颗树的先序遍历是1 2 4 7 8 5 9 3 6

void preorder(Node root) {
    cout << root.value << ' ';
    if (root.lson)
	    preorder(tree[root.lson]);
    if (root.rson)
	    preorder(tree[root.rson]);
}

中序遍历

按照左子树 -> 根节点 -> 右子树的顺序遍历。这颗树的中序遍历是7 4 8 2 5 9 1 6 3

void inorder(Node root) {
    if (root.lson)
	    inorder(tree[root.lson]);
    cout << root.value << ' ';
    if (root.rson)
	    inorder(tree[root.rson]);
}

后序遍历

按照左子树 -> 右子树 -> 根节点的顺序遍历。这颗树的后序遍历是7 8 4 9 5 2 6 3 1

void postorder(Node root) {
    if (root.lson)
	    postorder(tree[root.lson]);
    if (root.rson)
	    postorder(tree[root.rson]);
    cout << root.value << ' ';
}

层序遍历

顾名思义,层序遍历即从上到下一层一层地遍历节点。

前面集中遍历方式都使用的DFS(深度优先搜索),层序遍历建议使用BFS(广度优先搜索)。

这颗树的层序遍历是1 2 3 4 5 6 7 8 9

void bfs(Node root) {
    queue<Node> Q;
    Q.push(root);
    
    while (!Q.empty()) {
        Node node = Q.front();
        Q.pop();
        cout << node.value << ' ';
        
        if (node.lson)
            Q.push(tree[node.lson]);
        if (node.rson)
            Q.push(tree[node.rson]);
    }
}

总结一下,可以是这样的:

#include "bits/stdc++.h"
using namespace std;

const int N = 100010; // 数组大小,根据需求调整

struct Node {
    int value;    // 节点值
    int lson;     // 左子节点索引,0表示空
    int rson;     // 右子节点索引,0表示空
} tree[N];

int idx = 1; // 当前可用的节点索引,从1开始(0表示空节点)
unordered_map<int, int> val_to_idx; // 值到索引的映射 
int n;


// 创建新节点(如果已存在则返回现有索引)
int create(int value) {
    if (val_to_idx.count(value)) {
        return val_to_idx[value];
    }
    tree[idx] = {value, 0, 0};
    val_to_idx[value] = idx;
    return idx++;
}

// 构建二叉树
void build(int n) {
    for (int i = 0; i < n; i++) {
        int root_val, l_val, r_val;
        cin >> root_val >> l_val >> r_val;
        
        int root_idx = create(root_val);
        
        if (l_val != 0) {
            int l_idx = create(l_val);
            tree[root_idx].lson = l_idx;
        }
        
        if (r_val != 0) {
            int r_idx = create(r_val);
            tree[root_idx].rson = r_idx;
        }
    }
}

void preorder(Node root) {
    cout << root.value << ' ';
    
    if (root.lson)
        preorder(tree[root.lson]);
    if (root.rson)
        preorder(tree[root.rson]);
}

void inorder(Node root) {
    if (root.lson)
        inorder(tree[root.lson]);
    
    cout << root.value << ' ';
    
    if (root.rson)
        inorder(tree[root.rson]);
}

void postorder(Node root) {
    if (root.lson)
        postorder(tree[root.lson]);
    if (root.rson)
        postorder(tree[root.rson]);
    
    cout << root.value << ' ';
}

void bfs(Node root) {
    queue<Node> Q;
    Q.push(root);
    
    while (!Q.empty()) {
        Node node = Q.front();
        Q.pop();
        cout << node.value << ' ';
        
        if (node.lson)
            Q.push(tree[node.lson]);
        if (node.rson)
            Q.push(tree[node.rson]);
    }
}

// 查找根节点(没有父节点的节点)
int find_root() {
    // 标记所有出现的节点
    vector<bool> has_parent(idx + 1, false);
    
    for (int i = 1; i < idx; i++) { // 对于节点i,它的子节点一定有父节点
        if (tree[i].lson != 0) {
            has_parent[tree[i].lson] = true;
        }
        if (tree[i].rson != 0) {
            has_parent[tree[i].rson] = true;
        }
    }
    
    // 找到没有父节点的节点(根节点)
    for (int i = 1; i < idx; i++) {
        if (!has_parent[i]) {
            return i;
        }
    }
    return 1; // 默认返回第一个节点
}

int main() {
    cin >> n;
    
    build(n);
    
    int root = find_root();
    
    cout << "先序遍历:" << endl;
    preorder(tree[root]);
    cout << '\n';
    cout << "中序遍历:" << endl;
    inorder(tree[root]);
    cout << '\n';
    cout << "后序遍历:" << endl;
    postorder(tree[root]);
    cout << '\n';
    cout << "层序遍历:" << endl;
    bfs(tree[root]);
    
    return 0;
}

输入格式:

第1行一个数n代表节点个数

第2至n+1行三个数root, l, r,代表根节点、左右子节点的值

输入:

9
1 2 3
2 4 5
3 6 0
4 7 8
5 0 9
6 0 0 
7 0 0 
8 0 0 
9 0 0 

输出:

先序遍历:
1 2 4 7 8 5 9 3 6 
中序遍历:
7 4 8 2 5 9 1 6 3 
后序遍历:
7 8 4 9 5 2 6 3 1 
层序遍历:
1 2 3 4 5 6 7 8 9 

改进建议:

这里传递的参数都是Node类型的,导致调用一次就复制一遍Node,效率不如int高。请尝试修改代码,传递索引为参数。


如何根据先序遍历、中序遍历求后序遍历,或根据后序遍历、中序遍历求先序遍历?

核心思想:

所有方法都基于二叉树遍历的一个核心性质

  1. 先序遍历 (Preorder)[根节点] + [左子树的先序遍历] + [右子树的先序遍历]
  2. 中序遍历 (Inorder)[左子树的中序遍历] + [根节点] + [右子树的中序遍历]
  3. 后序遍历 (Postorder)[左子树的后序遍历] + [右子树的后序遍历] + [根节点]

解决问题的关键是:利用“根节点”在中序遍历中的位置,将序列划分为左子树和右子树,然后递归地处理这些子树。

由此也可以看出:想要根据两种方式的遍历结果求树/另一种遍历,必须要有中序遍历,因为只有中序遍历才能把左右子树划分开来。

问题一:根据先序遍历和中序遍历求后序遍历

步骤(递归方法):
  1. 确定根节点:先序遍历的第一个元素必定是整个二叉树的根节点
  2. 定位根节点在中序中的位置:在中序遍历序列中找到这个根节点。该位置将中序遍历序列清晰地分割为三部分:
    • 根节点左边的所有元素:左子树的中序遍历
    • 根节点
    • 根节点右边的所有元素:右子树的中序遍历
  3. 计算左右子树的大小:根据上一步分割的结果,可以确定左子树和右子树分别包含的节点个数。
  4. 递归构建左右子树
    • 左子树的先序遍历:从原始先序序列中,跳过第一个根节点,取紧接着的 左子树节点个数 个元素。
    • 右子树的先序遍历:先序序列中剩下的部分。
  5. 递归处理:对左子树和右子树分别递归地重复步骤 1-4。
  6. 组合结果(后序):将递归得到的左子树的后序序列右子树的后序序列根节点按顺序组合,即 左子树后序 + 右子树后序 + 根

问题二:根据后序遍历和中序遍历求先序遍历

步骤(递归方法):

这个方法与前一个非常对称。

  1. 确定根节点:后序遍历的最后一个元素必定是整个二叉树的根节点
  2. 定位根节点在中序中的位置:在中序遍历序列中找到这个根节点。该位置将中序遍历序列分割为:
    • 左子树的中序遍历
    • 根节点
    • 右子树的中序遍历
  3. 计算左右子树的大小:根据分割结果确定左右子树的节点个数。
  4. 递归构建左右子树
    • 左子树的后序遍历:从原始后序序列中,从开头取 左子树节点个数 个元素。
    • 右子树的后序遍历:后序序列中紧接着的 右子树节点个数 个元素(最后一个根节点之前的那些元素)。
  5. 递归处理:对左子树和右子树分别递归地重复步骤 1-4。
  6. 组合结果(先序):将根节点、递归得到的左子树的先序序列右子树的先序序列按顺序组合,即 根 + 左子树先序 + 右子树先序

C++代码:

#include <bits/stdc++.h>
using namespace std;

vector<int> preInToPost(vector<int>& pre, vector<int>& in,
    int preStart, int preEnd,
    int inStart, int inEnd,
    unordered_map<int, int>& inMap) 
{
    if (preStart > preEnd) return {};
    
    int rootVal = pre[preStart];
    int rootIndex = inMap[rootVal];
    int leftSize = rootIndex - inStart;
    
    
    vector<int> leftPost = preInToPost(pre, in, 
        preStart + 1, preStart + leftSize,
        inStart, rootIndex - 1, inMap);
    
    vector<int> rightPost = preInToPost(pre, in,
        preStart + leftSize + 1, preEnd,
        rootIndex + 1, inEnd, inMap);
    
    // 组合结果:左子树后序 + 右子树后序 + 根
    vector<int> result;
    result.insert(result.end(), leftPost.begin(), leftPost.end());
    result.insert(result.end(), rightPost.begin(), rightPost.end());
    result.push_back(rootVal);
    
    return result; 
}

vector<int> postInToPre(vector<int>& post, vector<int>& in,
    int postStart, int postEnd,
    int inStart, int inEnd,
    unordered_map<int, int>& inMap)
{
    if (postStart > postEnd) return {};
    
    int rootVal = post[postEnd];
    int rootIndex = inMap[rootVal];
    int leftSize = rootIndex - inStart;
    
    vector<int> leftPre = postInToPre(post, in,
        postStart, postStart + leftSize - 1,
        inStart, rootIndex - 1, inMap);
    
    vector<int> rightPre = postInToPre(post, in,
        postStart + leftSize, postEnd - 1,
        rootIndex + 1, inEnd, inMap);
    
    // 组合结果:根 + 左子树先序 + 右子树先序
    vector<int> result;
    result.push_back(rootVal);
    result.insert(result.end(), leftPre.begin(), leftPre.end());
    result.insert(result.end(), rightPre.begin(), rightPre.end());
    
    return result;
}

int main() {
    int n;
    
    // 读取树1的先序和中序遍历
    cin >> n;
    vector<int> pre1(n), in1(n);
    for (int i = 0; i < n; i++) cin >> pre1[i];
    for (int i = 0; i < n; i++) cin >> in1[i];
    
    // 读取树2的后序和中序遍历
    cin >> n;
    vector<int> post2(n), in2(n);
    for (int i = 0; i < n; i++) cin >> post2[i];
    for (int i = 0; i < n; i++) cin >> in2[i];
    
    // 创建中序遍历值的索引映射(加速查找)
    unordered_map<int, int> inMap1, inMap2;
    for (int i = 0; i < (int)in1.size(); i++) inMap1[in1[i]] = i;
    for (int i = 0; i < (int)in2.size(); i++) inMap2[in2[i]] = i;
    
    // 计算树1的后序遍历
    vector<int> post1 = preInToPost(pre1, in1, 0, pre1.size()-1, 0, in1.size()-1, inMap1);
    
    // 计算树2的先序遍历
    vector<int> pre2 = postInToPre(post2, in2, 0, post2.size()-1, 0, in2.size()-1, inMap2);
    
    // 输出结果
    for (int i = 0; i < (int)post1.size(); i++) {
        if (i > 0) cout << " ";
        cout << post1[i];
    }
    cout << endl;
    
    for (int i = 0; i < (int)pre2.size(); i++) {
        if (i > 0) cout << " ";
        cout << pre2[i];
    }
    cout << endl;
    
    return 0;
}

输入格式:

  • 第一行:树1的节点数n
  • 接下来n行:树1的先序遍历
  • 接下来n行:树1的中序遍历
  • 下一行:树2的节点数n
  • 接下来n行:树2的后序遍历
  • 接下来n行:树2的中序遍历

输出格式:

  • 第一行:树1的后序遍历(空格分隔)
  • 第二行:树2的先序遍历(空格分隔)

示例输入:

7
1 2 4 5 3 6 7
4 2 5 1 6 3 7
7
4 5 2 6 7 3 1
4 2 5 1 6 3 7

示例输出:

4 5 2 6 7 3 1
1 2 4 5 3 6 7

链表

作者为蒟蒻高中牲,若有不妥之处请多多包含,并望于评论区指正,不胜感激!

目录:

链表是一种动态的数据结构,每一个节点存储两个数据:datanextdata是数据,next是下一个节点的地址。

链表经常是首尾相接的。

算法竞赛中,链表常用STL 中的list而非手写,以节约时间。

这里给到Luogu P1996的题解示范list的用法。

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, m;  cin >> n >> m;
    list<int> node;
    
    for (int i = 1; i <= n; ++i) node.push_back(i); // 建立列表
    list<int>::iterator it = node.begin(); // 创建迭代器
    while (node.size() > 1) {
        for (int i = 1; i < m; i++) {
            it++;
            if (it == node.end()) it = node.begin();
        }
        cout << *it << " ";
        list<int>::iterator next = ++it;
        if (next == node.end()) next = node.begin();
        node.erase(--it);
        it = next;
    }
    cout << *it;
    return 0;
}

C++的位运算

作者为蒟蒻高中牲,若有不妥之处请多多包含,并望于评论区指正,不胜感激!

目录:

C++中有6种位运算符

  • &: 与

    1 & 1 = 1

    1 & 0 = 0

    0 & 0 = 0

  • |: 或

    1 | 1 = 1

    1 | 0 = 1

    0 | 0 = 0

  • ~: 非

    ~ 1 = 0

    ~ 0 = 1

  • ^: 异或

    1 ^ 1 = 1

    1 ^ 0 = 0

    0 ^ 0 = 1

  • <<: 左移

    $1 << 1 = 10_2$

    $1 << 2 = 100_2$

  • >>: 右移

    $11_2 >> 1 = 110_2$

计算机的构成

作者为蒟蒻高中牲,若有不妥之处请多多包含,并望于评论区指正,不胜感激!

目录:

计算机由软件硬件构成。

- 硬件是指看得见摸得着的各种电子元器件。如主机、键盘、鼠标等。 - 软件看不见摸不着,但在逻辑上,我们确实可以感知到它的存在,它由人们事先编制的具有某种功能的程序组成。比如操作系统、编程语言等。

计算机的软件通常又分为:系统软件和应用软件

  • 系统软件:又称为系统程序,主要用来管理整个计算机系统,监视服务,使系统资源得到合理调度。它包括:操作系统、语言处理程序、数据库管理系统、网络软件、服务程序等。
  • 应用软件:又称为应用程序,它是用户根据需要事先编制的程序。比如 QQ、微信等。

硬件系统

在冯 · 诺依曼体系中,计算机硬件系统是由运算器、控制器、存储器、输入设备和输出设备五大部件组成的。

随着计算机技术的发展,计算机硬件系统的组织结构发生了许多重大的变化,如运算器控制器已组合成一个整体,称为中央处理器(Central Processing Unit,CPU)。

存储器已成为多级存储器体系,包含主存、高速缓存和外存三个层次。

软件系统

软件类别:系统软件应用软件

  • 系统软件:又称为系统程序,主要用来管理整个计算机系统,监视服务,使系统资源得到合理调度。它包括:操作系统、语言处理程序、数据库管理系统、网络软件、服务程序等。
  • 应用软件:又称为应用程序,它是用户根据需要事先编制的程序。比如 QQ、微信等。