【二十五】【C++】二叉搜索树及其简单实现

news2025/2/25 1:57:36

二叉搜索树的性质

二叉搜索树(BST)是一种特殊的二叉树,它具有以下性质:

  • 每个节点都有一个键(或值),并且每个节点最多有两个子节点。

  • 左子树上所有节点的键都小于其根节点的键。

  • 右子树上所有节点的键都大于其根节点的键。

  • 左右子树也都是二叉搜索树。

二叉搜索树的常见方法

1. 插入节点(Insertion)

插入操作是将一个新的键值对添加到二叉搜索树中。插入过程需要保证树的二叉搜索属性:节点的左子树只包含小于节点键的节点,节点的右子树只包含大于节点键的节点。

2. 搜索节点(Search)

搜索操作用于在二叉搜索树中查找一个键。如果键存在,返回对应的节点;否则,返回nullptr或一些标志值表示键不存在。

3. 删除节点(Deletion)

删除操作涉及三种情况:

  • 叶子节点:直接删除。

  • 一个子节点:删除节点并用其子节点替代。

  • 两个子节点:用其右子树的最小节点(或左子树的最大节点)替代该节点,然后删除那个最小(或最大)节点。

4. 遍历(Traversal)

二叉树的遍历通常有三种方式:前序(Pre-order)、中序(In-order)和后序(Post-order)遍历。对于二叉搜索树,中序遍历会按照键的升序访问所有节点。

二叉搜索树中序遍历

二叉搜索树(Binary Search Tree,BST)的中序遍历是指按照从小到大的顺序访问树中的所有节点。这种遍历方式是一种有序遍历,因为BST的性质保证了中序遍历的结果是有序的。

中序遍历,其遍历顺序为左子树 -> 根节点 -> 右子树。对于每一个小的单元,其左子树的值比根节点的值小,右子树的值比根节点的值大。中序遍历过程中,对于每一个单元都保证了值的从小到大输出。而对于单元的输出顺序,又满足最左边即最小的单元,依次变大。所以二叉搜索树的中序遍历,输出的结果就是值的升序序列。

二叉搜索又称作排序二叉树。因为其中序遍历输出的结果的有序的。

5. 查找最小和最大键(Finding Minimum and Maximum Key)

在二叉搜索树中查找最小和最大键很简单:沿左子树一直向下遍历可以找到最小键,而沿右子树一直向下遍历可以找到最大键。

6. 高度和深度(Height and Depth)

树的高度是从根节点到最远叶子节点的最长路径上的边数。树的深度是从根节点到某节点的路径上的边数。计算树的高度通常通过递归实现。

二叉搜索树的简单实现

 

#include <iostream>
using namespace std;


template<class T>
struct BSTNode {
    BSTNode(const T& x = T())
        : left(nullptr)
        , right(nullptr)
        , data(x)
    {}

    BSTNode<T>* left;
    BSTNode<T>* right;
    T data;
 };


// 假设:树中节点的值域唯一
template<class T>
class BinarySearchTree {
        typedef BSTNode<T> Node;

    public:
        BinarySearchTree()
            : root(nullptr)
        {}

        ~BinarySearchTree() {
            Destroy(root);
        }

        bool Insert(const T& data) {
            // 1. 空树---
            // 新插入的节点应该是跟节点
            if (nullptr == root) {
                root = new Node(data);
                return true;
            }

            // 2. 树非空
            // a. 找待插入节点在树中的位置--->按照二叉搜索树的性质
            Node* cur = root;
            Node* parent = nullptr;
            while (cur) {
                parent = cur;
                if (data < cur->data)
                    cur = cur->left;
                else if (data > cur->data)
                    cur = cur->right;
                else
                    return false;
            }

            // 树中不存在值为data的节点
            // b. 插入新节点
            cur = new Node(data);
            if (data < parent->data)
                parent->left = cur;
            else
                parent->right = cur;

            return true;
        }

        Node* Find(const T& data)const {
            Node* cur = root;
            while (cur) {
                if (data == cur->data)
                    return cur;
                else if (data < cur->data)
                    cur = cur->left;
                else
                    cur = cur->right;
            }

            return nullptr;
        }

        bool Erase(const T& data) {
            // 1. 先找data是否存在
            Node* cur = root;
            Node* parent = nullptr;
            while (cur) {
                if (data == cur->data)
                    break;
                else if (data < cur->data) {
                    parent = cur;
                    cur = cur->left;
                } else {
                    parent = cur;
                    cur = cur->right;
                }
            }

            // 值为data的节点不存在
            if (nullptr == cur)
                return false;

            // 2. 找到值为data的节点,即cur--->将该节点删除掉
            // 删除节点并且保存树的关系

            // 有些节点可以直接删除,但是有些节点不能直接删除
            // 需要对cur子树分情况讨论
            // 1. cur是叶子
            // 2. cur只有左孩子
            // 3. cur只有右孩子
            // 4. cur左右孩子均存在
            // 经过图解分析发现:
            // 情况1实际可以和情况2或者情况3合并起来
            Node* pDelNode = cur;
            if (nullptr == cur->left) {
                // 说明cur可能是叶子节点,也可能是只有右孩子
                if (nullptr == parent) {
                    // 删除cur刚好是根节点,且根没有左子树
                    root = cur->right;
                } else {
                    // cur的双亲存在
                    if (cur == parent->left)
                        parent->left = cur->right;
                    else
                        parent->right = cur->right;
                }
            } else if (nullptr == cur->right) {
                // 说明cur只有左孩子
                if (nullptr == parent) {
                    // cur是根节点且根没有右子树,注意:左子树是一定存在的
                    root = cur->left;
                } else {
                    if (cur == parent->left)
                        parent->left = cur->left;
                    else
                        parent->right = cur->left;
                }
            } else {
                // 说明cur左孩孩子均存在
                // 1. 在cur的子树中找替代节点,并保存其双亲
                // 左子树:找最大的即最右侧节点
                // 右子树:找最小的即最左侧节点---即中序遍历的第一个节点--和数据结构书本保持一致
                pDelNode = cur->right;
                parent = cur;
                while (pDelNode->left) {
                    parent = pDelNode;
                    pDelNode = pDelNode->left;
                }

                // 2. 将替代节点中的值交给cur
                cur->data = pDelNode->data;

                // 3. 删除替代节点
                if (pDelNode == parent->left)
                    parent->left = pDelNode->right;
                else
                    parent->right = pDelNode->right;
            }

            delete pDelNode;
            return true;
        }

        void InOrder() {
            _InOrder(root);
            cout << endl;
        }

    private:
        void _InOrder(Node* proot) {
            if (proot) {
                _InOrder(proot->left);
                cout << proot->data << " ";
                _InOrder(proot->right);
            }
        }

        void Destroy(Node*& proot) {
            if (proot) {
                Destroy(proot->left);
                Destroy(proot->right);
                delete proot;
                proot = nullptr;
            }
        }


    private:
        BSTNode<T>* root;
 };


void TestBSTree() {
    BinarySearchTree<int> t;
    int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
    for (auto e : a)
        t.Insert(e);

    t.InOrder();

    BSTNode<int>* cur = t.Find(9);
    if (cur) {
        cout << "9 is in BSTree" << endl;
    } else {
        cout << "9 is not in BSTree" << endl;
    }

    cur = t.Find(13);
    if (cur) {
        cout << "13 is in BSTree" << endl;
    } else {
        cout << "13 is not in BSTree" << endl;
    }

    t.Erase(7);
    t.InOrder();

    t.Erase(0);
    t.InOrder();

    t.Erase(5);
    t.InOrder();
 }

int main() {
    TestBSTree();
 }

代码解析

BSTNode 结构体定义

 
 
template<class T>
struct BSTNode {
    BSTNode(const T& x = T())
        : left(nullptr)
        , right(nullptr)
        , data(x)
    {}

    BSTNode<T>* left;
    BSTNode<T>* right;
    T data;
 };

这段代码定义了一个模板结构体 BSTNode,用于表示二叉搜索树(BST)的节点。

template<class T>:这是一个模板声明,声明了一个模板类型 T,它表示节点中存储的数据类型。

BSTNode(const T& x = T()):这是结构体的构造函数,用于初始化节点对象。它接受一个参数 x,默认值为类型 T 的默认构造值。这样设计使得创建节点对象时,可以不传入参数,默认构造出一个具有默认值的节点。

left(nullptr)right(nullptr):这两行初始化了节点的左右子节点指针,初始值为 nullptr,表示这个节点暂时没有左右子节点。

data(x):这一行初始化了节点中存储的数据 data,其值为构造函数传入的参数 x

BSTNode<T>* left;BSTNode<T>* right;:这两行定义了指向左右子节点的指针,它们的类型为 BSTNode<T>*,即指向 BSTNode 类型的指针。这样的设计使得可以构建二叉树的数据结构,通过这些指针连接各个节点。

T data;:这一行定义了节点中存储的数据成员 data,其类型为模板参数 T,表示节点存储的数据类型。

BinarySearchTree 类定义

 
 
template<class T>
class BinarySearchTree {
    typedef BSTNode<T> Node;
public:
    BinarySearchTree() : root(nullptr) {}
    ~BinarySearchTree() {
        Destroy(root);
    }
    // 其他成员函数...
private:
    BSTNode<T>* root;

};

这段代码定义了一个模板类 BinarySearchTree,用于表示二叉搜索树(BST)。

template<class T>:这是一个模板声明,声明了一个模板类型 T,它表示树中存储的数据类型。

typedef BSTNode<T> Node;:这行代码定义了一个别名 Node,它表示 BSTNode<T> 类型,即树节点的类型。

BinarySearchTree() : root(nullptr) {}:这是类的默认构造函数。在其中,根节点 root 被初始化为 nullptr,表示空树。

~BinarySearchTree() { Destroy(root); }:这是类的析构函数。它调用了私有成员函数Destroy(root),用于销毁整棵树。通过递归的方式,首先销毁根节点的子树,然后销毁根节点本身。这样可以确保释放树中所有节点占用的内存,避免内存泄漏。

BSTNode<T>* root;:这一行定义了一个指针成员 root,用于指向树的根节点。这个指针的类型为BSTNode<T>*,即指向 BSTNode 类型的指针,其中 T 是模板参数,表示节点存储的数据类型。

插入操作 (Insert 函数)

 
 
        bool Insert(const T& data) {
            // 1. 空树---
            // 新插入的节点应该是跟节点
            if (nullptr == root) {
                root = new Node(data);
                return true;
            }

            // 2. 树非空
            // a. 找待插入节点在树中的位置--->按照二叉搜索树的性质
            Node* cur = root;
            Node* parent = nullptr;
            while (cur) {
                parent = cur;
                if (data < cur->data)
                    cur = cur->left;
                else if (data > cur->data)
                    cur = cur->right;
                else
                    return false;
            }

            // 树中不存在值为data的节点
            // b. 插入新节点
            cur = new Node(data);
            if (data < parent->data)
                parent->left = cur;
            else
                parent->right = cur;

            return true;
        }

这是一个公有成员函数 Insert,用于向二叉搜索树中插入新节点。它接收一个参数 data,表示要插入的节点的数据。

首先检查树是否为空。如果树为空(即根节点 rootnullptr),则将新节点直接作为根节点插入,并返回 true

如果树非空,则需要找到新节点在树中的插入位置。根据二叉搜索树的性质,如果新节点的值小于当前节点的值,则应该往左子树方向查找插入位置;如果新节点的值大于当前节点的值,则应该往右子树方向查找插入位置。通过循环遍历直到找到合适的插入位置。

找到插入位置后,根据新节点的值与其父节点的值的比较结果,确定将新节点插入到父节点的左子树还是右子树。然后创建新节点,并将其连接到父节点相应的子树中。最后返回 true,表示插入操作成功。

查找操作 (Find 函数)

 
 
        Node* Find(const T& data)const {
            Node* cur = root;
            while (cur) {
                if (data == cur->data)
                    return cur;
                else if (data < cur->data)
                    cur = cur->left;
                else
                    cur = cur->right;
            }

            return nullptr;
        }

这是一个公有成员函数 Find,用于在二叉搜索树中查找具有特定值的节点。它接收一个参数 data,表示要查找的节点的值,并返回指向该节点的指针。

首先将当前节点指针 cur 初始化为根节点 root。然后开始一个循环,该循环会在树中查找目标节点,直到遇到空节点为止。

在循环中,首先检查当前节点的值是否等于目标值 data。如果相等,则找到了目标节点,直接返回当前节点指针。如果目标值小于当前节点的值,则应该继续在左子树中查找;反之,则在右子树中查找。根据比较结果更新当前节点指针 cur,继续向下搜索。

如果循环结束时仍未找到目标节点,则说明树中不存在值为 data 的节点,此时返回 nullptr,表示未找到目标节点。

删除操作 (Erase 函数)

 
 
        bool Erase(const T& data) {
            // 1. 先找data是否存在
            Node* cur = root;
            Node* parent = nullptr;
            while (cur) {
                if (data == cur->data)
                    break;
                else if (data < cur->data) {
                    parent = cur;
                    cur = cur->left;
                } else {
                    parent = cur;
                    cur = cur->right;
                }
            }

            // 值为data的节点不存在
            if (nullptr == cur)
                return false;

            // 2. 找到值为data的节点,即cur--->将该节点删除掉
            // 删除节点并且保存树的关系

            // 有些节点可以直接删除,但是有些节点不能直接删除
            // 需要对cur子树分情况讨论
            // 1. cur是叶子
            // 2. cur只有左孩子
            // 3. cur只有右孩子
            // 4. cur左右孩子均存在
            // 经过图解分析发现:
            // 情况1实际可以和情况2或者情况3合并起来
            Node* pDelNode = cur;
            if (nullptr == cur->left) {
                // 说明cur可能是叶子节点,也可能是只有右孩子
                if (nullptr == parent) {
                    // 删除cur刚好是根节点,且根没有左子树
                    root = cur->right;
                } else {
                    // cur的双亲存在
                    if (cur == parent->left)
                        parent->left = cur->right;
                    else
                        parent->right = cur->right;
                }
            } else if (nullptr == cur->right) {
                // 说明cur只有左孩子
                if (nullptr == parent) {
                    // cur是根节点且根没有右子树,注意:左子树是一定存在的
                    root = cur->left;
                } else {
                    if (cur == parent->left)
                        parent->left = cur->left;
                    else
                        parent->right = cur->left;
                }
            } else {
                // 说明cur左孩孩子均存在
                // 1. 在cur的子树中找替代节点,并保存其双亲
                // 左子树:找最大的即最右侧节点
                // 右子树:找最小的即最左侧节点---即中序遍历的第一个节点--和数据结构书本保持一致
                pDelNode = cur->right;
                parent = cur;
                while (pDelNode->left) {
                    parent = pDelNode;
                    pDelNode = pDelNode->left;
                }

                // 2. 将替代节点中的值交给cur
                cur->data = pDelNode->data;

                // 3. 删除替代节点
                if (pDelNode == parent->left)
                    parent->left = pDelNode->right;
                else
                    parent->right = pDelNode->right;
            }

            delete pDelNode;
            return true;
        }

这是一个公有成员函数 Erase,用于删除二叉搜索树中值为 data 的节点。它接收一个参数 data,表示要删除的节点的值,并返回一个布尔值,表示删除操作是否成功。

首先在树中找到值为 data 的节点。通过循环遍历,如果当前节点的值等于目标值 data,则找到了目标节点;如果目标值小于当前节点的值,则继续在左子树中查找;反之,则在右子树中查找。如果循环结束时,当前节点为空,则说明树中不存在值为 data 的节点,直接返回 false 表示删除失败。

在找到目标节点后,根据不同情况进行删除操作:

如果目标节点是叶子节点或者只有一个子节点,直接删除该节点并将其子节点连接到其父节点上。

如果目标节点有两个子节点,则需要找到它的后继节点(右子树中的最小节点)或者前驱节点(左子树中的最大节点)来替代它,保持二叉搜索树的有序性。将后继/前驱节点的值复制到目标节点上,然后再删除后继/前驱节点。

最后,释放被删除节点的内存并返回 true 表示删除成功。

中序遍历 (InOrder_InOrder 函数)

 
 
        void InOrder() {
            _InOrder(root);
            cout << endl;
        }

    private:
        void _InOrder(Node* proot) {
            if (proot) {
                _InOrder(proot->left);
                cout << proot->data << " ";
                _InOrder(proot->right);
            }
        }

这段代码实现了二叉搜索树的中序遍历,并提供了一个公有成员函数 InOrder() 用于调用中序遍历,并提供了一个私有辅助函数 _InOrder(Node* proot) 用于实际执行递归的中序遍历操作。

InOrder() 是一个公有成员函数,用于对二叉搜索树进行中序遍历。它调用了私有成员函数_InOrder(Node* proot) 来执行中序遍历操作,传入根节点 root 作为参数,并在遍历结束后输出换行符,以便在打印完所有节点后换行。

_InOrder(Node* proot) 是一个私有成员函数,用于实际执行递归的中序遍历操作。它接收一个参数proot,表示当前子树的根节点。在函数体内,先判断当前节点是否为空,如果不为空,则递归地对左子树进行中序遍历 _InOrder(proot->left),然后输出当前节点的值 cout << proot->data << " ";,最后递归地对右子树进行中序遍历 _InOrder(proot->right)

这样,通过调用 InOrder() 函数,可以完成对整棵二叉搜索树的中序遍历,并按照升序打印出各个节点的值。

销毁树 (Destroy 函数)

 
 
        void Destroy(Node*& proot) {
            if (proot) {
                Destroy(proot->left);
                Destroy(proot->right);
                delete proot;
                proot = nullptr;
            }
        }

这段代码实现了一个递归函数 Destroy,用于销毁二叉搜索树。

这是一个公有成员函数 Destroy,用于销毁二叉搜索树。它接收一个参数 proot,表示当前子树的根节点的引用。这里使用引用是因为在函数中会修改根节点的指针,将其置为 nullptr

首先检查当前节点是否为空,如果为空,则表示当前子树已经被销毁或者是空树,直接返回。如果不为空,则执行销毁操作。

递归调用 Destroy 函数,分别对当前节点的左子树和右子树进行销毁操作。通过递归,可以先销毁左子树,再销毁右子树,最后再销毁当前节点,从而实现对整棵树的销毁。

在递归回溯的过程中,当左右子树都被销毁之后,对当前节点进行销毁操作。首先释放当前节点占用的内存,然后将当前节点指针置为 nullptr,以避免出现悬空指针。这里使用了引用 Node*& proot,使得在函数外部调用 Destroy 函数后,根节点的指针也会被置为 nullptr,从而确保树被正确销毁。

这样,通过调用 Destroy 函数,可以销毁整棵二叉搜索树,释放所有节点占用的内存。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

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

相关文章

bugku3

xxx二手交易市场 进去是这样讴歌乱进的页面 查看了一下源代码也没什么 先随便注册一个账号 然后登录 随便看了看&#xff0c;发现可以修改头像 上传文件 随便上传了一个图片 发现他对图片进行了base64加密 解密后得到是 data:image/jpeg;base64 这里重新修改类型为php&a…

栈的实现及其括号匹配问题

栈的实现及其括号匹配问题 一、栈的基本概念及其实现二、栈的数据结构实现三、oj题括号匹配问题解答 一、栈的基本概念及其实现 1.什么是栈&#xff1f;   栈是一种特殊的线性表&#xff0c;只允许栈顶一段插入和删除数据&#xff0c;栈底就是一个容器的底子&#xff0c;栈中…

ubuntu22.04@Jetson Orin Nano之OpenCV安装

ubuntu22.04Jetson Orin Nano之OpenCV安装 1. 源由2. 分析3. 证实3.1 jtop安装3.2 jtop指令3.3 GPU支持情况 4. 安装OpenCV4.1 修改内容4.2 Python2环境【不需要】4.3 ubuntu22.04环境4.4 国内/本地环境问题4.5 cudnn版本问题 5. 总结6. 参考资料 1. 源由 昨天用Jetson跑demo程…

数据恢复软件推荐!简单几步找回丢失数据!

“我真的非常需要一款靠谱的数据恢复软件&#xff01;在对电脑文件进行移动时有些文件被我误删了&#xff0c;在回收站也无法找到&#xff0c;大家有什么比较好的数据恢复软件推荐吗&#xff1f;” 随着数字时代的来临&#xff0c;数据已成为我们生活和工作中不可或缺的一部分。…

微服务-Alibaba微服务nacos实战

1. Nacos配置中心 1.1 微服务为什么需要配置中心 在微服务架构中&#xff0c;当系统从一个单体应用&#xff0c;被拆分成分布式系统上一个个服务节点后&#xff0c;配置文件也必须跟着迁移&#xff08;分割&#xff09;&#xff0c;这样配置就分散了&#xff0c;不仅如此&…

普中51单片机学习(串口通信)

串口通信 原理 计算机通信是将计算机技术和通信技术的相结合&#xff0c;完成计算机与外部设备或计算机与计算机之间的信息交换 。可以分为两大类&#xff1a;并行通信与串行通信。并行通信通常是将数据字节的各位用多条数据线同时进行传送 。控制简单、传输速度快&#xff1…

自然语言编程系列(二):自然语言处理(NLP)、编程语言处理(PPL)和GitHub Copilot X

编程语言处理的核心是计算机如何理解和执行预定义的人工语言&#xff08;编程语言&#xff09;&#xff0c;而自然语言处理则是研究如何使计算机理解并生成非正式、多样化的自然语言。GPT-4.0作为自然语言处理技术的最新迭代&#xff0c;其编程语言处理能力相较于前代模型有了显…

MT5016A-ASEMI大电流整流桥MT5016A

编辑&#xff1a;ll MT5016A-ASEMI大电流整流桥MT5016A 型号&#xff1a;MT5016A 品牌&#xff1a;ASEMI 封装&#xff1a;D-63 最大重复峰值反向电压&#xff1a;1600V 最大正向平均整流电流(Vdss)&#xff1a;50A 功率(Pd)&#xff1a; 芯片个数&#xff1a;4 引脚数…

Python + Selenium —— 元素定位函数 find_element!

WebDriver 中的 find_element() 方法用来查找元素&#xff0c;并返回 WebElement 对象。是 WebDriver 中最常用的方法。 前面提到的八种定位方式都有对应的方法&#xff0c;如find_element_by_id()。 在 WebDriver 中还有一种用法&#xff0c;就是单纯的find_element()。需要…

8款白嫖党必备的ai写作神器,你都知道吗? #AI写作#科技

这些工具不仅可以快速生成高质量的文本内容&#xff0c;还可以根据用户的需求进行个性化定制。它们可以帮助我们节省大量的时间和精力&#xff0c;让我们更加专注于创意和细节的打磨。本文将为大家详细介绍几个AI写作工具&#xff0c;让你在写作领域更上一层楼。 1.元芳写作 …

ASP.NET-实现图形验证码

ASP.NET 实现图形验证码能够增强网站安全性&#xff0c;防止机器人攻击。通过生成随机验证码并将其绘制成图像&#xff0c;用户在输入验证码时增加了人机交互的难度。本文介绍了如何使用 C# 和 ASP.NET 创建一个简单而有效的图形验证码系统&#xff0c;包括生成随机验证码、绘制…

8*4点LED数显/数码管显示驱动控制芯片-VK1650,兼容替代市面1650,提供FAE技术支持

产品品牌&#xff1a;永嘉微电/VINKA 产品型号&#xff1a;VK1650 封装形式&#xff1a;SOP16 概述&#xff1a; VK1650是一种带键盘扫描电路接口的LED驱动控制专用芯片&#xff0c;内部集成有数据锁存 器、LED驱动、键盘扫描等电路。SEG脚接LED阳极&#xff0c;GRID脚接LED阴…

Outlook 中关闭特殊字符显示

故障现象&#xff1a; 不知道什么原因&#xff0c;Outlook发mail时候&#xff0c;突然显示如下的特殊字符&#xff0c;看起来非常不方便&#xff0c;需要关闭; 解决办法&#xff1a; 在Outlook正文里面&#xff0c;输入CtrlShift*&#xff08; 其中*是键盘数字8旁边的那个*&a…

使用Docker部署Docker-Compose-Ui工具并实现公网访问

文章目录 1. 安装Docker2. 检查本地docker环境3. 安装cpolar内网穿透4. 使用固定二级子域名地址远程访问 Docker Compose UI是Docker Compose的web界面。这个项目的目标是在Docker Compose之上提供一个最小的HTTP API&#xff0c;同时保持与Docker Compose CLI的完全互操作性。…

【Unity3D】ASE制作天空盒

找到官方shader并分析 下载对应资源包找到\DefaultResourcesExtra\Skybox-Cubed.shader找到\CGIncludes\UnityCG.cginc观察变量, 观察tag, 观察代码 需要注意的内容 ASE要处理的内容 核心修改 添加一个Custom Expression节点 code内容为: return DecodeHDR(In0, In1);outp…

第十四章[面向对象]:14.3:实例属性

一,认识实例属性 1,什么是实例属性? 实例属性是与类的每个实例相关联的属性。 每个类的实例都可以拥有自己的一组实例属性。 实例属性通常在类的构造方法(通常是__init__方法)内定义,并使用self关键字来访问。 2,实例属性的一个重要特点是每个实例都有自己独立的一组实…

从0到1的私域流量体系搭建,私域操盘手的底层认知升级

一、教程描述 本套私域操盘手教程&#xff0c;大小4.31G&#xff0c;共有12个文件。 二、教程目录 01第一课、私域能力必修&#xff1a;私域大神熟记于心的高阶私域体系.mp4 02第二课、私域IP打造&#xff1a;那些忍不住靠近的私域IP如何打造的.mp4 03第三课、朋友圈经济&…

openEuler二进制安装MySQL8.0.x

一、环境准备 1、系统&#xff1a;openEuler操作系统 2、MySQL版本&#xff1a;MySQL-8.0.36 3、下载地址&#xff1a;https://dev.mysql.com/get/Downloads/MySQL-8.0 二、安装步骤 1、下载glibc版本的MySQL 2、新建用户以安全方式运行进程 [rootnode4 ~]# groupadd -r -g…

基于STL的演讲比赛流程管理系统(个人学习笔记黑马学习)

1、演讲比赛程序需求 1.1比赛规则 学校举行一场演讲比赛&#xff0c;共有12个人参加。比赛共两轮&#xff0c;第一轮为淘汰赛&#xff0c;第二轮为决赛。每名选手都有对应的编号&#xff0c;如 10001~10012比赛方式:分组比赛&#xff0c;每组6个人;第一轮分为两个小组&#xff…

微服务—RabbitMQ高级(业务在各方面的可靠性)

本博客为个人学习笔记&#xff0c;学习网站&#xff1a;2023黑马程序员RabbitMQ入门到实战教程 高级篇章节 目录 生产者可靠性 生产者重连机制 生产者确认机制 介绍 实现 总结与建议 MQ可靠性 数据持久化 LazyQueue 消费者可靠性 消费者确认机制 失败重试机制 失…