C++笔记:二叉搜索树(Binary Search Tree)

news/发布时间2024/5/15 8:44:01

文章目录

  • 二叉搜索树的概念
  • 二叉搜索树操作
    • 1. 框架搭建
    • 2. 遍历
    • 3. 查找
      • 迭代实现
      • 递归实现
    • 4. 插入
      • 迭代实现
      • 递归实现
    • 5. 删除
      • 迭代实现
      • 递归实现
    • 6. 析构与销毁
    • 7. 拷贝构造与赋值重载
  • 二叉搜索树的应用
  • 二叉搜索树的性能分析
  • 二叉搜索树模拟实现源码

二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

在这里插入图片描述

二叉搜索树操作

1. 框架搭建

// struct BinarySearchTreeNode - 结点类
template<class K>
struct BSTreeNode
{BSTreeNode* _left;BSTreeNode* _right;K _key;BSTreeNode(const K& key): _left(nullptr), _right(nullptr), _key(key){}
};// class BinarySearchTreeNode - 树类
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;public:protected:Node* _root;
};

【说明】

  1. BSTreeNode 类使用 struct 定义,其成员受默认访问限定符 public 修饰,BSTree 类能够直接访问结点的成员而不需要提供 Get 系列的接口。
  2. BSTree 类的主要功能是维护树的结构,包括插入、删除、搜索等操作,这些操作都是基于根节点展开的。因此,只需要一个根节点的指针就可以代表和维护整棵树。
  3. typedef 操作只是为了简化类型和规范命名,无特别深意。
  4. new 一个新节点时,编译器肯定要调用结点类的构造函数,默认生成的构造函数无法满足要求,所以要显示实现。
  5. 为什么用的是protected而不是private,在不涉及继承的情况下,二者并无区别,如何涉及继承protected的使用优于private

2. 遍历

我们都知道二分查找是一个十分厉害的算法,它能够在 O ( l o g n ) O(logn) O(logn) 的时间复杂度内找到一个目标值,但是它同时又是一个不实用的算法,① 二分查找的前提是要求数据是有序的,对数据预排序会带来额外开销,特别是大型数据集;② 二分查找依赖于顺序表结构的,顺序表结构的头部和中间插入删除开销大,而且插入删除之后需要重新排序,维护成本极高。

但是二叉搜索树规避了这些问题,如果对二叉搜索树进行中序遍历之后就会发现,它从某种意义上来说就是一个天然有序的结构,而且由于其性质的规定,二叉搜索树的插入删除不会影响结构,维护成本低。

public:void Inorder(){_Inorder(_root);cout << endl;}protected:void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_key << " ";_Inorder(root->_right);}

【说明】

  • 将中序遍历写成子函数然后再封装一层的原因是在类外调用函数要传根节点指针作为参数,但由于成员变量 _root 是私有的,类外无法访问,后面操作的递归实现由于这个原因,也是要封装上一层。
  • 因为子函数_Inorder仅仅只是给Inorder调用,为了保证封装性,使用protected访问限定符修饰。
  • 至于为什么用的是protected而不是private,在不涉及继承的情况下,二者并无区别,如何涉及继承protected的使用优于private

3. 查找

查找到具体过程如下:

  1. 从根结点开始比较、查找。
  2. 目标值比结点的值大则往右边走查找,目标值比结点的值小则往左边走查找。
  3. 找到返回true,走到到空,还没找到,说明值不存在,返回false
  4. 最多查找高度次。

在这里插入图片描述

迭代实现

bool Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return true;}}return false;
}

递归实现

public:bool FindR(const K& key){return _FindR(_root, key);}
protected:bool _FindR(Node* root, const K& key){if (root == nullptr)return false;if (root->_key < key){return _FindR(root->_left, key);}else if (root->_key > key){return _FindR(root->_right, key);}else{return true;}}

4. 插入

插入到具体过程如下:

  1. 树为空,则直接新增节点,赋值给 _root 指针,返回true
  2. 树不空,按二叉搜索树性质查找插入位置,插入新节点,返回true
  3. 如果待插入的值已存在,按插入失败处理,返回false

迭代实现

bool Insert(const K& key)
{// 树空,直接作为根结点if (_root == nullptr){_root = new Node(key);return true;}// 树不空,查找何时位置再插入Node* cur = _root;Node* parent = cur;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return false;
}

递归实现

public:
bool InsertR(const K& key){return _InsertR(_root, key);}
protected:bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_key < key){return _InsertR(root->_right, key);}else if (root->_key > key){return _InsertR(root->_left, key);}else{return false;}}

【说明】

  1. 递归实现不像迭代器实现那样要分情况,只要root是空就直接插入。
  2. Node*& root能够如此简单的实现,多亏引用运用,
    如果不加引用,root只是一个临时变量指向待插入位置,还要想办法去找双亲结点;
    加了引用之后,root就是待插位置的双亲结点的孩子指针的别名。

5. 删除

搜索二叉树的删除操作比较复杂,首先,待删结点有五种可能性:
1、待除结点不存在。
2、待删结点是叶子结点。
3、待删结点只存在左子树。
4、待删结点只存在右子树。
5、待删结点左、右子树都存在。

而实际情况中,可能性 2 可以与可能性 3 或者可能性 4 合并起来,因此真正的删除过程如下:

情况1:二叉搜索树为空,或者找不到待删结点,函数返回false,表示删除失败。

情况2:待删结点只存在左子树,先保存待删结点,然后判断待删结点是不是整棵树的根节点:

  • 是根节点:使左子树的根节点作为整棵树的根节点,再删除结点。
  • 不是根节点:使待删节点的双亲结点指向待删节点的左孩子结点,再删除结点。
  • 待删节点有可能是其双亲结点左孩子或者有孩子,这个需要额外判断。

在这里插入图片描述

在这里插入图片描述

情况3:待删结点只存在右子树,先保存待删结点,然后判断待删结点是不是整棵树的根节点:

  • 根节点:使右子树的根节点作为整棵树的根节点,再删除结点。
  • 非根节点:使待删节点的双亲结点指向待删节点的右孩子结点,再删除结点。
  • 待删节点有可能是其双亲结点左孩子或者有孩子,这个需要额外判断。

在这里插入图片描述
在这里插入图片描述

情况4:待删结点左、右子树都存在,先找到待删结点的右子树的最小结点(或者左子树的最大结点),然后用它来替换待删结点(这里选取右子树的最小结点作为替换方案),然后删除找到的最小结点,删除结点时需要加判断:

  • 右子树的最小结点既有可能是其双亲结点的左孩子,也有可能是右孩子。

在这里插入图片描述
在这里插入图片描述

迭代实现

bool Erase(const K& key)
{if (_root == nullptr)return false;Node* cur = _root;Node* parent = cur;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else // cur->_key == key,执行删除操作{// 处理只存在右子树if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;delete cur;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}delete cur;}return true;}// 处理只存在左子树else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;delete cur;}else{if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}delete cur;}return true;}// 处理左右子树都存在,将待删结点替换成右子树的最小结点// 然后转换成删除右子树的最小结点else{Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMinParent->_left == rightMin)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;delete rightMin;}}}// cur == nullptrreturn false;
}

递归实现

public:bool EraseR(const K& key){return _EraseR(_root, key);}
protected:bool _EraseR(Node*& root, const K& key){if (root == nullptr){// 结点不存在,包含空树return false;}if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else // root->_key == key,执行删除操作{Node* del = root;// 左为空,右不为空,待删结点只存在右子树if (root->_left == nullptr){root = root->_right;}// 右为空,左不为空,待删结点只存在左子树else if (root->_right == nullptr){root = root->_left;}// 左右都不为空,左右子树都存在else{Node* rightMin = root->_right;while (rightMin->_left){rightMin = rightMin->_left;}swap(root->_key, rightMin->_key);// 为什么不能传rightMin?return _EraseR(root->_right, key);}delete del;return true;}}

6. 析构与销毁

public:~BSTree(){clear();}void clear(){_Destroy(_root);_root = nullptr;}
protected:void _Destroy(Node* root){if (root == nullptr)return;_Destroy(root->_left);_Destroy(root->_right);delete root;}

【说明】

  1. 在某些情况下,我们需要将一颗树清空,按照STL的常规做法该提供一个clear(),清空之后为了避免野指针问题,需要将作为树的入口的_root置空,避免野指针。
  2. 析构的作用是回收对象内部的资源,这个功能恰好可以复用clear()接口。
  3. 清空这棵树采取的做法是后续遍历删除,目的是为了避免内存泄漏。
  4. 后续遍历采用递归实现,需要再封装。

7. 拷贝构造与赋值重载

public:// default 关键字强制让编译器生成默认的构造函数BSTree() = default;BSTree(const BSTree<K>& t){_root = _Copy(t._root);}BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}
protected:Node* _Copy(const Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = _Copy(root->_left);newRoot->_right = _Copy(root->_right);return newRoot;}

【说明】

  1. 二叉搜索树的拷贝构造和赋值运算符重载涉及到深拷贝问题,编译器默认生成的函数无法满足要求得自己实现。
  2. 拷贝过程决定采用后序递归构建,由于是递归,所以实现一个子函数_Copy()来完成。
  3. 拷贝构造函数算是构造函数的重载,显式定义拷贝构造函数之后编译器不再会自己生成默认构造函数,这里使用关键字default强制让编译器生成默认构造函数。
  4. 赋值运算符重载参数为BSTree<K> t,对于该写法,编译器会自动调用拷贝构造生成一个临时对象,然后调用库中的swap函数互换_root内容。
  5. 赋值运算符要求支持连续赋值,所以要返回*this

二叉搜索树的应用

  1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
    比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
    • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
    • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
  2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
    • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
    • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

在这里插入图片描述

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N

最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

二叉搜索树模拟实现源码

#include <iostream>using namespace std;namespace ljh
{// struct BinarySearchTreeNode - 结点类template<class K>struct BSTreeNode{BSTreeNode* _left;BSTreeNode* _right;K _key;BSTreeNode(const K& key): _left(nullptr), _right(nullptr), _key(key){}};// class BinarySearchTreeNode - 树类template<class K>class BSTree{typedef BSTreeNode<K> Node;public:// default 关键字强制让编译器生成默认的构造函数BSTree() = default;BSTree(const BSTree<K>& t){_root = _Copy(t._root);}BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}~BSTree(){clear();}void clear(){_Destroy(_root);_root = nullptr;}bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return true;}}return false;}bool Insert(const K& key){// 树空,直接作为根结点if (_root == nullptr){_root = new Node(key);return true;}// 树不空,查找何时位置再插入Node* cur = _root;Node* parent = cur;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return false;}bool Erase(const K& key){if (_root == nullptr)return false;Node* cur = _root;Node* parent = cur;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else // cur->_key == key,执行删除操作{// 左为空,右不为空,待删结点只存在右子树if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;delete cur;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}delete cur;}return true;}// 右为空,左不为空,待删结点只存在左子树else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;delete cur;}else{if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}delete cur;}return true;}// 处理左右子树都存在,将待删结点替换成右子树的最小结点// 然后转换成删除右子树的最小结点else{Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMinParent->_left == rightMin)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;delete rightMin;}}}// cur == nullptrreturn false;}/// 递归实现的函数void Inorder(){_Inorder(_root);cout << endl;}bool FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}protected:void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_key << " ";_Inorder(root->_right);}bool _FindR(Node* root, const K& key){if (root == nullptr)return false;if (root->_key < key){return _FindR(root->_left, key);}else if (root->_key > key){return _FindR(root->_right, key);}else{return true;}}bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_key < key){return _InsertR(root->_right, key);}else if (root->_key > key){return _InsertR(root->_left, key);}else{return false;}}bool _EraseR(Node*& root, const K& key){if (root == nullptr){// 结点不存在,包含空树return false;}if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else // root->_key == key,执行删除操作{Node* del = root;// 左为空,右不为空,待删结点只存在右子树if (root->_left == nullptr){root = root->_right;}// 右为空,左不为空,待删结点只存在左子树else if (root->_right == nullptr){root = root->_left;}// 左右都不为空,左右子树都存在else{Node* rightMin = root->_right;while (rightMin->_left){rightMin = rightMin->_left;}swap(root->_key, rightMin->_key);// 为什么不能传rightMin?return _EraseR(root->_right, key);}delete del;return true;}}void _Destroy(Node* root){if (root == nullptr)return;_Destroy(root->_left);_Destroy(root->_right);delete root;}Node* _Copy(const Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = _Copy(root->_left);newRoot->_right = _Copy(root->_right);return newRoot;}protected:Node* _root = nullptr;};
}

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

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

相关文章

光伏气象站:实现自动化、高精度的气象监测

型号推荐&#xff1a;云境天合 TH-FGF9】光伏气象站是一种基于光伏技术的气象监测设备&#xff0c;它利用太阳能转化为电能&#xff0c;为气象站提供持续的电力供应&#xff0c;并实现自动化、高精度的气象监测。 光伏气象站的工作原理可以分为以下几个部分&#xff1a; 光伏发…

第2讲:C语言数据类型和变量

第2讲&#xff1a;C语言数据类型和变量 目录1.数据类型介绍1.1字符型1.2整型1.3浮点型1.4 布尔类型1.5 各种数据类型的长度1.5.1 sizeof 操作符1.5.2 数据类型长度1.5.3 sizeof 中表达式不计算 2.signed 和 unsigned3.数据类型的取值范围4. 变量4.1 变量的创建4.2 变量的分类 5…

汽修专用产品---选型介绍 汽修示波器 汽车示波器 汽车电子 汽修波形 汽车传感器波形 汽车检测

为了满足汽车电子用户的测量需求&#xff0c;我司特推出汽修专用版示波器&#xff0c;一键测量&#xff0c;轻松找出汽车问题。 LOTO各种型号的示波器其实都可以用作汽车传感器信号波形的检测。汽修应用中&#xff0c;工程师对示波器的性能要求对于LOTO产品来说不算高。 在我们…

【Docker】构建pytest-playwright镜像并验证

Dockerfile FROM ubuntu LABEL maintainer "langhuang521l63.com" ENV TZAsia/Shanghai #设置时区 #安装python3依赖与下载安装包 RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \&& apt update \&&…

数据表的关联关系(学习笔记)

关联关系介绍 MySQL是一个关系型数据库&#xff0c;不仅可以存储数据&#xff0c;还可以维护数据与数据之间的关系——通过在数据表中添加字段建立外键约束 数据与数据之间的关系分为四种&#xff1a; ● 一对一关联 ● 一对多关联 ● 多对一关联 ● 多对多关联 一对一关…

Python之numpy

目录 安装 ndarray 说明文档 NumPy(Numerical Python) 是 Python 语言的一个扩展程序库&#xff0c;支持大量的维度数组与矩阵运算&#xff0c;此外也针对数组运算提供大量的数学函数库。 安装 pip3 install --user numpy scipy matplotlib ndarray NumP提供了 N 维数组…

【鸿蒙 HarmonyOS 4.0】数据持久化

一、数据持久化介绍 数据持久化是将内存数据(内存是临时的存储空间)&#xff0c;通过文件或数据库的形式保存在设备中。 HarmonyOS提供两种数据持久化方案&#xff1a; 1.1、用户首选项&#xff08;Preferences&#xff09;&#xff1a; 通常用于保存应用的配置信息。数据通…

rancher v2.8.1 如何成功注册已有 k8s 集群

需要加入的集群为rke2部署的双节点集群 $ kubectl get node NAME STATUS ROLES AGE VERSION rke-master01 Ready control-plane,etcd,master,worker 94d v1.26.8rke2r1 rke-master02 Ready control-plane,etcd,mast…

java+springmvc+springboot众筹救助系统mybatis

儿童众筹救助系统在流畅性&#xff0c;续航能力&#xff0c;等方方面面都有着很大的优势。这就意味着儿童众筹救助系统的设计可以比其他系统更为出色的能力&#xff0c;可以更高效的完成最新的救助基金、救助申请、众筹项目、捐赠信息等功能。 此系统设计主要采用的是JAVA语言来…

无人机的视频图传技术

在操控无人机时&#xff0c;视频图传技术显得尤为关键。通过这项技术&#xff0c;无人机的摄像头所捕捉的画面能实时回传至遥控器&#xff0c;使操作者全面掌握无人机的拍摄情况。同时&#xff0c;无人机图传技术也是衡量无人机性能的重要标准&#xff0c;它关乎飞行距离与时间…

深度学习环境配置常见指令

首先打开anaconda prompt&#xff0c;激活对应虚拟环境。 导入torch并获取对应版本 import torch torch.__version__导入torchvision并获取对应版本 import torchvision torchvision.__version__ 检查cuda是否可用 torch.cuda.is_available() 获取CUDA设备数 torch.cuda.…

【开源】SpringBoot框架开发康复中心管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 普通用户模块2.2 护工模块2.3 管理员模块 三、系统展示四、核心代码4.1 查询康复护理4.2 新增康复训练4.3 查询房间4.4 查询来访4.5 新增用药 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的康复中…

flutter使用getx实现路由跳转,页面没有执行dispose

我们看一下flutter的StatefulWidget组件的生命周期&#xff1a; createState&#xff1a; 当一个StatefulWidget插入到渲染树结构、或者从渲染树结构移除时&#xff0c;都会调用StatefulWidget.createState方法&#xff0c;从而达到更新UI的效果&#xff1b; initState&#…

如何使用Docker搭建YesPlayMusic网易云音乐播放器并发布至公网访问

文章目录 1. 安装Docker2. 本地安装部署YesPlayMusic3. 安装cpolar内网穿透4. 固定YesPlayMusic公网地址 本篇文章讲解如何使用Docker搭建YesPlayMusic网易云音乐播放器&#xff0c;并且结合cpolar内网穿透实现公网访问音乐播放器。 YesPlayMusic是一款优秀的个人音乐播放器&am…

高效的工作学习方法

1.康奈尔笔记法 在这里插入图片描述 2. 5W2H法 3. 鱼骨图分析法 4.麦肯锡7步分析法 5.使用TODOLIST 6.使用计划模板&#xff08;年月周&#xff09; 7. 高效的学习方法 成年人的学习特点&#xff1a; 快速了解一个领域方法 沉浸式学习方法&#xff1a; 沉浸学习的判据&am…

Docker容器故障排查与解决方案

Docker是一种相对使用较简单的容器&#xff0c;我们可以通过以下几种方式获取信息&#xff1a; 1、通过docker run执行命令&#xff0c;或许返回信息 2、通过docker logs 去获取日志&#xff0c;做有针对性的筛选 3、通过systemctl status docker查看docker服务状态 4、通过…

qt-动画圆圈等待-LED数字

qt-动画圆圈等待-LED数字 一、演示效果二、关键程序三、下载链接 一、演示效果 二、关键程序 #include "LedNumber.h" #include <QLabel>LEDNumber::LEDNumber(QWidget *parent) : QWidget(parent) {//设置默认宽高比setScale((float)0.6);//设置默认背景色se…

Docker基础篇(三) 容器数据卷(-)之用 命令 添加 -v docker run -it -v 宿主机绝对路径:容器目录 [:ro] 镜像

volume&#xff1a; 体积、数据卷 docker run -it -v /宿主机目录:/容器目录 镜像名 docker run -it -v /hostvolume:/containvolume centos 容器&#xff1a;里会创建 containvolume 宿主机&#xff1a;会创建 hostvolume 在宿主机中查看容器内容 docker inspect 容器ID或容…

❤ hexo主题+Gitee搭建个人博客

Hexo的基本使用 ​官网 官网地址&#xff1a;https://hexo.io/zh-cn/ Hexo是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown&#xff08;或其他渲染引擎&#xff09;解析文章&#xff0c;在几秒内&#xff0c;即可利用靓丽的主题生成静态网页。即把用户的markdown文件…

CentOS 7.9.2009离线安装mysql 8.0客户端 (rpm包)

环境&#xff1a; #需求&#xff1a; 该服务器需要将csv文件入库到远端的mysql 服务器上。 CentOS Linux release 7.9.2009 (Core) 离线环境 &#xff0c;需安装mysql客户端 8.0.27#下载地址 https://downloads.mysql.com/archives/community/#按此顺序安装 rpm -ivh mysql…
推荐文章