C++之红黑树剖析

news2025/2/12 4:03:07

在这里插入图片描述


博主:拖拉机厂第一代码手
gitee:拖拉机厂第一代码手
已收录到专栏C++,点击访问


目录

  • 💴红黑树简介
  • 💵红黑树的插入操作
  • 💶红黑树的删除操作
  • 💷红黑树的实现
    • 💸红黑树节点的定义
    • 💸红黑树结构的定义
    • 💸红黑树的插入实现
    • 💸红黑树的删除实现
    • 💸红黑树插入和删除测试
  • 💳总结


💴红黑树简介

红黑树是一种自平衡的二叉搜索树,它是由 Rudolf Bayer 在1972年提出,并由 Leonidas J. Guibas 和 Robert Sedgewick 在1978年进行改进和推广的。红黑树是一种复杂的数据结构,旨在确保在最坏情况下的高效的插入、删除和查找操作。

红黑树之所以被称为红黑树,是因为每个节点有一个存储的二进制值来表示节点的颜色,通常为红色或黑色。除了满足二叉搜索树的特性之外,红黑树还满足以下额外特性:

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点是黑色。
  3. 每个叶子节点(NIL节点,空节点)是黑色。
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的。
  5. 对于每个节点,从该节点到其后代的每个叶子节点的简单路径上,黑色节点的数量相同。

这些特性确保了红黑树的平衡性,使得在最坏情况下,红黑树的插入、删除和查找操作的时间复杂度都是 O(log n),其中 n 是树中节点的数量。

由于红黑树具有自平衡的特性,所以它在各种应用中得到广泛应用,例如在操作系统中的进程调度、文件系统的实现,以及在数据结构库中用于提供高效的数据结构支持等。

在这里插入图片描述

红黑树通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。(ps:因为在满足红黑树条件的情况下,该树的最长路径为红黑相间的路径,而最短的路径为全黑的路径)

💵红黑树的插入操作

红黑树是一种自平衡的二叉搜索树,它在插入操作时通过对节点进行着色和旋转操作来保持平衡。下面是红黑树插入新节点的过程:

  1. 将新节点插入二叉搜索树中,按照二叉搜索树的插入规则进行。
  2. 将新节点着色为红色。这是为了保护红黑树的性质,后续操作中会逐步调整节点的颜色。
  3. 根据红黑树的性质进行调整,有以下几种情况需要考虑:

a. 新节点是根节点:将新节点着色为黑色,以确保性质2(根节点为黑色)被满足。

在这里插入图片描述

b. 新节点的父节点是黑色:由于新节点是红色,所以红黑树的性质没有被破坏。不需要做任何额外的调整。
在这里插入图片描述

c. 新节点的父节点是红色:

  • 新节点的叔叔节点是红色:通过修改颜色调整,将父节点和叔叔节点着色为黑色,父节点的父节点(祖父节点)着色为红色,然后以祖父节点为当前节点递归进行调整。
    在这里插入图片描述
  • 新节点的叔叔节点是黑色或者不存在:通过旋转和颜色调整实现平衡。具体操作分为四种情况:
  1. 新节点是父节点的左子节点,并且父节点是祖父节点的左子节点:进行右旋操作,并交换父节点和祖父节点的颜色。
    在这里插入图片描述
  1. 新节点是父节点的右子节点,并且父节点是祖父节点的右子节点:进行左旋操作,并交换父节点和祖父节点的颜色。
    在这里插入图片描述
  1. 新节点是父节点的右子节点,并且父节点是祖父节点的左子节点:进行左旋操作,然后进行右旋操作,并交换新节点和祖父节点的颜色。
    在这里插入图片描述
  1. 新节点是父节点的左子节点,并且父节点是祖父节点的右子节点:进行右旋操作,然后进行左旋操作,并交换新节点和祖父节点的颜色。
    在这里插入图片描述

说明:cur的情况有两种

  • 如果cur节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同。
  • 如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色也一定是黑色的,现在看到具是红色的原因是因为cur的子树在调整的过程中将cu市点的颜色由黑色改成红色。
  1. 将根节点着色为黑色,以满足性质2。

以上就是红黑树插入操作的过程。通过这些调整,红黑树始终保持平衡,并满足红黑树的五个性质。这些操作保证了红黑树插入的时间复杂度为O(logn)。

💶红黑树的删除操作

红黑树的删除操作相较于插入操作要复杂一些。下面是红黑树删除节点的详细过程:

首先,按照二叉搜索树的规则找到需要删除的节点,并标记为要删除的节点。

判断要删除的节点是否有子节点:

a. 若没有子节点,则直接删除该节点,并将其父节点指向它的指针置为null。
b. 若只有一个子节点,将其子节点代替要删除的节点的位置,并更新子节点的父节点指针。
c. 若有两个子节点,需要找到要删除节点的后继节点(右子树最小值节点)或者前驱节点(左子树最大值节点),将后继节点(或前驱节点)的值复制到要删除的节点中,并将要删除的节点指向后继节点(或前驱节点),然后将要删除的节点更新为后继节点(或前驱节点)。

接下来需要处理删除后的情况,根据被删除节点的性质以及其子节点的颜色进行相应的调整,有以下几种情况:

  • a. 被删除节点为红色节点:

由于红色节点的删除不会破坏红黑树的性质,所以直接删除即可,无需其他调整。

  • b. 被删除节点为黑色节点:

被删除节点的子节点为红色节点:将子节点改为黑色,以保持红黑树的性质。这种情况下,被删除节点是根节点的话,只需要将子节点改为黑色即可。

被删除节点的子节点为黑色节点:

  • (1)被删除节点为根节点:无需额外处理;
  • (2)被删除节点的兄弟节点为红色节点:进行旋转和颜色调整,使被删除节点的兄弟节点变为黑色节点,然后继续处理;
  • (3)被删除节点的兄弟节点为黑色节点:
  • i. 如果兄弟节点的两个子节点都为黑色:将兄弟节点改为红色,将被删除节点的父节点作为新的被删除节点进行递归调整。
  • ii. 如果兄弟节点左子节点为红色,右子节点为黑色:进行旋转和颜色调整,使兄弟节点的左子节点变为黑色,兄弟节点变为红色,并对兄弟节点进行右旋操作;
  • iii. 如果兄弟节点右子节点为红色:进行旋转和颜色调整,将兄弟节点的颜色设置为被删除节点父节点的颜色,被删除节点的父节点设置为黑色,兄弟节点的右子节点设置为黑色,然后对被删除节点的父节点进行左旋操作。

将根节点设置为黑色,以满足红黑树的性质2。

通过上述步骤,可以在删除节点后重新调整红黑树,以保持平衡并满足红黑树的所有性质。这些操作确保了红黑树删除操作的时间复杂度为O(logn)。

💷红黑树的实现

💸红黑树节点的定义

// 定义红黑树节点
template <typename Key, typename Value>
struct RBTreeNode 
{
    enum Color { RED, BLACK };

    Key key;                // 节点的键值
    Value value;            // 节点的值
    RBTreeNode* left;       // 左子节点指针
    RBTreeNode* right;      // 右子节点指针
    RBTreeNode* parent;     // 父节点指针
    Color color;            // 节点的颜色

    // 构造函数
    RBTreeNode(Key k, Value v, Color c = RED)
        : key(k), value(v), left(nullptr), right(nullptr), parent(nullptr), color(c) {}
};

在以上的代码中,每个红黑树节点包含以下属性:

  • key:节点的键值,用于对节点进行排序。
  • value:节点的值,可以是任意类型的数据。
  • left:指向左子节点的指针。
  • right:指向右子节点的指针。
  • parent:指向父节点的指针,根节点的父节点为空。
  • color:指示节点的颜色,默认为红色。使用枚举类型 Color 表示节点的颜色,包括红色(RED)和黑色(BLACK)。

这是一个简单的红黑树节点的定义示例,可以根据具体需求进行适当修改和扩展。

💸红黑树结构的定义

// 定义红黑树结构
template <typename Key, typename Value>
struct RBTree 
{
    RBTreeNode<Key, Value>* header;    // 头结点

    // 构造函数
    RBTree() 
    {
        header = new RBTreeNode<Key, Value>(Key(), Value(), RBTreeNode<Key, Value>::BLACK);
        header->left = header;
        header->right = header;
        header->parent = nullptr;
    }
};

在以上的代码中,我们在红黑树结构中添加了一个头结点(哨兵节点),用于表示红黑树的边界。头结点的key和value值可以设为默认值,而且颜色为黑色(BLACK)以满足红黑树性质。

通过添加头结点,我们可以很方便地访问红黑树的最小节点和最大节点,可以使用 header->left 来访问最小节点,使用 header->right 来访问最大节点。

这种修改可以方便地实现关联式容器,并在插入、删除操作时简化边界条件的处理。

💸红黑树的插入实现

// 红黑树的插入操作
template<typename Key, typename Value>
void RBTree<Key, Value>::insert(Key key, Value value) {
    RBTreeNode<Key, Value>* newNode = new RBTreeNode<Key, Value>(key, value);
    RBTreeNode<Key, Value>* parent = nullptr;
    RBTreeNode<Key, Value>* current = header->parent;

    // 找到插入位置
    while (current != nullptr) {
        parent = current;
        if (key < current->key)
            current = current->left;
        else if (key > current->key)
            current = current->right;
        else {
            // 如果已存在相同的键值,则更新节点的值
            current->value = value;
            delete newNode;
            return;
        }
    }

    newNode->parent = parent;

    // 空树,插入为根节点
    if (parent == nullptr) {
        newNode->color = RBTreeNode<Key, Value>::BLACK;
        header->parent = newNode;
        header->left = newNode;
        header->right = newNode;
    }
    else if (key < parent->key)
        parent->left = newNode;
    else
        parent->right = newNode;

    // 插入后进行调整
    insertFixup(newNode);
}

// 红黑树插入后的调整操作
template <typename Key, typename Value>
void RBTree<Key, Value>::insertFixup(RBTreeNode<Key, Value>* node) {
    while (node->parent != nullptr && node->parent->color == RBTreeNode<Key, Value>::RED) {
        if (node->parent == node->parent->parent->left) {
            RBTreeNode<Key, Value>* uncle = node->parent->parent->right;
            if (uncle != nullptr && uncle->color == RBTreeNode<Key, Value>::RED) {
                // Case 1: 叔叔节点是红色
                node->parent->color = RBTreeNode<Key, Value>::BLACK;
                uncle->color = RBTreeNode<Key, Value>::BLACK;
                node->parent->parent->color = RBTreeNode<Key, Value>::RED;
                node = node->parent->parent;
            }
            else {
                if (node == node->parent->right) {
                    // Case 2: 插入节点是父节点的右子节点
                    node = node->parent;
                    rotateLeft(node);
                }
                // Case 3: 插入节点是父节点的左子节点
                node->parent->color = RBTreeNode<Key, Value>::BLACK;
                node->parent->parent->color = RBTreeNode<Key, Value>::RED;
                rotateRight(node->parent->parent);
            }
        }
        else {
            // 与上述情况对称
            RBTreeNode<Key, Value>* uncle = node->parent->parent->left;
            if (uncle != nullptr && uncle->color == RBTreeNode<Key, Value>::RED) {
                node->parent->color = RBTreeNode<Key, Value>::BLACK;
                uncle->color = RBTreeNode<Key, Value>::BLACK;
                node->parent->parent->color = RBTreeNode<Key, Value>::RED;
                node = node->parent->parent;
            }
            else {
                if (node == node->parent->left) {
                    node = node->parent;
                    rotateRight(node);
                }
                node->parent->color = RBTreeNode<Key, Value>::BLACK;
                node->parent->parent->color = RBTreeNode<Key, Value>::RED;
                rotateLeft(node->parent->parent);
            }
        }
    }
    header->parent->color = RBTreeNode<Key, Value>::BLACK;  // 根节点始终为黑色
}

以上代码是一个红黑树的实现,包括插入操作insert()、插入修复操作insertFixup(),以及左旋和右旋操作rotateLeft()和rotateRight()。

在插入操作中,首先创建一个新的节点,并根据键的大小将其插入到适当的位置。如果已存在相同的键值,则会更新节点的值。然后,根据插入的节点进行插入修复操作insertFixup(),以恢复红黑树的性质。

插入修复操作insertFixup()是用来保持红黑树的性质。它根据插入节点的父节点、祖父节点和叔叔节点的颜色进行不同的操作。具体来说,如果叔叔节点是红色(Case 1),则通过改变颜色来修复;如果插入节点是父节点的右子节点(Case 2),则进行左旋操作;如果插入节点是父节点的左子节点(Case 3),则进行右旋操作。

左旋操作rotateLeft()将某个节点的右子节点上移,同时该节点成为其右子节点的左子节点。右旋操作rotateRight()将某个节点的左子节点上移,同时该节点成为其左子节点的右子节点。这些操作用于调整红黑树的平衡。

总的来说,这些代码实现了红黑树的插入操作和相应的修复操作,以及左旋和右旋操作。这些是保持红黑树性质的重要操作。

💸红黑树的删除实现

// 红黑树的删除操作
template<typename Key, typename Value>
void RBTree<Key, Value>::remove(Key key) {
    RBTreeNode<Key, Value>* node = findNode(key);
    if (node == nullptr)
        return;

    RBTreeNode<Key, Value>* child;
    RBTreeNode<Key, Value>* parent;
    bool isRed = (node->color == RBTreeNode<Key, Value>::RED);

    if (node->left != nullptr && node->right != nullptr) {
        // Case 1: 被删除节点有两个子节点
        RBTreeNode<Key, Value>* replace = node->right;
        while (replace->left != nullptr)
            replace = replace->left;

        child = replace->right;
        parent = replace->parent;
        isRed = (replace->color == RBTreeNode<Key, Value>::RED);

        if (child != nullptr)
            child->parent = parent;

        if (parent != nullptr) {
            if (parent->left == replace)
                parent->left = child;
            else
                parent->right = child;
        }
        else {
            header->parent = child;
        }

        if (replace->parent == node)
            parent = replace;

        replace->parent = node->parent;
        replace->color = node->color;
        replace->left = node->left;
        replace->right = node->right;

        if (node->left != nullptr)
            node->left->parent = replace;
        if (node->right != nullptr)
            node->right->parent = replace;

        if (node->parent != nullptr) {
            if (node->parent->left == node)
                node->parent->left = replace;
            else
                node->parent->right = replace;
        }
        else {
            header->parent = replace;
        }

        delete node;
        node = replace;
    }
    else {
        // Case 2: 被删除节点无或只有一个子节点
        if (node->left != nullptr)
            child = node->left;
        else
            child = node->right;

        parent = node->parent;
        isRed = (node->color == RBTreeNode<Key, Value>::RED);

        if (child != nullptr)
            child->parent = parent;

        if (parent != nullptr) {
            if (parent->left == node)
                parent->left = child;
            else
                parent->right = child;
        }
        else {
            header->parent = child;
        }

        delete node;
    }

    if (!isRed) {
        // 删除后进行调整
        removeFixup(child, parent);
    }
}



// 红黑树删除后的调整操作
template <typename Key, typename Value>
void RBTree<Key, Value>::removeFixup(RBTreeNode<Key, Value>* node, RBTreeNode<Key, Value>* parent) {

    while (node != header->parent && (node == nullptr || node->color == RBTreeNode<Key, Value>::BLACK)) {
        if (node == parent->left) {
            RBTreeNode<Key, Value>* sibling = parent->right;
            if (sibling->color == RBTreeNode<Key, Value>::RED) {
                // Case 1: 兄弟节点是红色
                sibling->color = RBTreeNode<Key, Value>::BLACK;
                parent->color = RBTreeNode<Key, Value>::RED;
                rotateLeft(parent);
                sibling = parent->right;
            }
            if ((sibling->left == nullptr || sibling->left->color == RBTreeNode<Key, Value>::BLACK)
                && (sibling->right == nullptr || sibling->right->color == RBTreeNode<Key, Value>::BLACK)) {
                // Case 2: 兄弟节点和兄弟节点的子节点都是黑色
                sibling->color = RBTreeNode<Key, Value>::RED;
                node = parent;
                parent = node->parent;
            }
            else {
                if (sibling->right == nullptr || sibling->right->color == RBTreeNode<Key, Value>::BLACK) {
                    // Case 3: 兄弟节点的右子节点是黑色
                    if (sibling->left != nullptr)
                        sibling->left->color = RBTreeNode<Key, Value>::BLACK;
                    sibling->color = RBTreeNode<Key, Value>::RED;
                    rotateRight(sibling);
                    sibling = parent->right;
                }
                // Case 4: 兄弟节点的右子节点是红色
                sibling->color = parent->color;
                parent->color = RBTreeNode<Key, Value>::BLACK;
                if (sibling->right != nullptr)
                    sibling->right->color = RBTreeNode<Key, Value>::BLACK;
                rotateLeft(parent);
                node = header->parent;
                break;
            }
        }
        else {
            // 与上述情况对称
            RBTreeNode<Key, Value>* sibling = parent->left;
            if (sibling->color == RBTreeNode<Key, Value>::RED) {
                sibling->color = RBTreeNode<Key, Value>::BLACK;
                parent->color = RBTreeNode<Key, Value>::RED;
                rotateRight(parent);
                sibling = parent->left;
            }
            if ((sibling->left == nullptr || sibling->left->color == RBTreeNode<Key, Value>::BLACK)
                && (sibling->right == nullptr || sibling->right->color == RBTreeNode<Key, Value>::BLACK)) {
                sibling->color = RBTreeNode<Key, Value>::RED;
                node = parent;
                parent = node->parent;
            }
            else {
                if (sibling->left == nullptr || sibling->left->color == RBTreeNode<Key, Value>::BLACK) {
                    if (sibling->right != nullptr)
                        sibling->right->color = RBTreeNode<Key, Value>::BLACK;
                    sibling->color = RBTreeNode<Key, Value>::RED;
                    rotateLeft(sibling);
                    sibling = parent->left;
                }
                sibling->color = parent->color;
                parent->color = RBTreeNode<Key, Value>::BLACK;
                if (sibling->left != nullptr)
                    sibling->left->color = RBTreeNode<Key, Value>::BLACK;
                rotateRight(parent);
                node = header->parent;
                break;
            }
        }
    }

    if (node != nullptr)
        node->color = RBTreeNode<Key, Value>::BLACK;
}

这段代码是红黑树的删除操作和删除修复操作的实现。

在删除操作中,首先找到要删除的节点,如果节点不存在则直接返回。然后根据节点的情况进行处理:

  • Case 1: 当被删除节点有两个子节点时,找到后继节点(右子树的最左节点),并用后继节点替换被删除节点。
  • Case 2: 当被删除节点只有一个子节点或者没有子节点时,直接用子节点来替换被删除节点。

然后,检查被删除节点的颜色,如果是红色则不需要进行调整,直接删除即可。如果是黑色,则需要进行删除修复操作removeFixup(),以确保红黑树的性质。删除修复操作分为四种情况,根据被删除节点的兄弟节点的颜色和子节点的颜色来进行相应的旋转操作和颜色调整,最终保持红黑树的性质。

在实现中,调用了左旋rotateLeft()和右旋rotateRight()操作来进行树的旋转调整。

💸红黑树插入和删除测试

int main() {
    RBTree<int, std::string> tree;

    // 插入测试
    tree.insert(10, "Value 10");
    tree.insert(5, "Value 5");
    tree.insert(15, "Value 15");
    tree.insert(3, "Value 3");
    tree.insert(8, "Value 8");
    tree.insert(12, "Value 12");
    tree.insert(18, "Value 18");
    tree.insert(2, "Value 2");
    tree.insert(4, "Value 4");
    tree.insert(7, "Value 7");
    tree.insert(9, "Value 9");
    tree.insert(11, "Value 11");
    tree.insert(14, "Value 14");
    tree.insert(17, "Value 17");
    tree.insert(20, "Value 20");

    // 遍历测试
    std::cout << "begin In-order traversal:\n";
    tree.inOrderTraversal();

    // 删除测试
    tree.remove(9);
    tree.remove(12);
    tree.remove(18);

    // 遍历测试
    std::cout << "end In-order traversal:\n";
    tree.inOrderTraversal();

    return 0;
}

这段代码创建了一个整型键和字符串值的红黑树。首先,通过insert()函数插入了一系列键值对,然后通过inOrderTraversal()函数进行中序遍历,输出红黑树的节点信息。接着,通过remove()函数进行删除操作,删除了键为9、12和18的节点。最后,再次进行中序遍历测试,输出删除后的红黑树节点信息。

在这里插入图片描述

💳总结

文章对红黑树的特性进行了详细介绍,对红黑树的插入和删除的具体步骤进行分析并用代码实现出来,完整代码放到了gitee仓库,有需要自取。

最后,如果觉得文章对你有帮助的话,就来一个小小的👍吧。

在这里插入图片描述

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

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

相关文章

autoware 之 op_behavior_selector行为选择器状态机代码分析

autoware 1 op_behavior_selector行为选择器状态机代码分析 /这里是整个状态机运行时的结构&#xff1a;/ //停止状态:[#停止状态]//任务完成状态:[#任务完成状态]//转向状态:[*前进状态,#转向状态]//停止信号停止状态:[*停止信号等待状态,#停止信号停止状态]//前进状态 :[*目…

C语言学习系列-->看淡指针(1)

文章目录 一、概述二、指针变量和地址2.1 取地址操作符2.2 指针变量和解引用操作符2.2.1 指针变量2.2.2 拆解指针类型2.2.4 解引用操作符 2.3 指针变量的大小 三、指针变量的意义3.1 指针的解引用指针-整数 四、 const修饰指针五、指针运算5.1 指针- 整数5.2 指针-指针5.3 指针…

OpenText 企业安全 调查 产品简介

关于OpenText OpenText是一家信息软件公司&#xff0c;使企业能够通过市场领先的信息管理解决方案&#xff08;内部或云中&#xff09;获得洞察力。 全球面临的数字风险 市场合力驱动的信息管理 处于风暴中心的信息 →安全漏洞和数据保护 • 防止威胁并将破坏影响降至最低 …

通过PostMan监视提交文件,验证web文件传输

切换文件流,传输文件 找到图片地址 发送请求然后接受 再来一张 哈&#xff0c;谢谢各位同志的阅读&#xff0c;然后呢如果觉得本文对您有所帮助的话&#xff0c;还给个免费的赞捏 Thanks♪(&#xff65;ω&#xff65;)&#xff89;

【二】数据库系统

数据库系统的分层抽象DBMS 数据的三个层次从 数据 到 数据的结构----模式数据库系统的三级模式&#xff08;三级视图&#xff09;数据库系统的两层映像数据库系统的两个独立性数据库系统的标准结构 数据模型从 模式 到 模式的结构----数据模型三大经典数据模型 数据库的演变与发…

给大家推荐9个Linux高效运维命令技巧!

文章目录 前言一、实用的 xargs 命令二、命令或脚本后台运行三、找出当前系统内存使用量较高的进程四、找出当前系统CPU使用量较高的进程五、同时查看多个日志或数据文件六、持续ping并将结果记录到日志七、查看tcp连接状态八、查找80端口请求数最高的前20个IP九、ssh实现端口转…

构建高性能的MongoDB数据迁移工具:Java的开发实践

随着大数据时代的到来&#xff0c;数据迁移成为许多企业和组织必须面对的挑战之一。作为一种非关系型数据库&#xff0c;MongoDB在应用开发中得到了广泛的应用。为了满足数据迁移的需求&#xff0c;我们需要一个高性能、稳定可靠的MongoDB数据迁移工具。下面将分享使用Java开发…

【华秋推荐】新能源汽车中的T-BOX系统,你了解多少?

近几年&#xff0c;新能源汽车产业进入了加速发展的阶段。我国的新能源汽车产业&#xff0c;经过多年的持续努力&#xff0c;技术水平显著提升、产业体系日趋完善、企业竞争力大幅增强&#xff0c;呈现市场规模、发展质量“双提升”的良好局面。同时&#xff0c;通过国家多年来…

【碎碎念】在CSDN 512天创作纪念日

‍‍&#x1f3e1;博客主页&#xff1a; virobotics的CSDN博客&#xff1a;LabVIEW深度学习、人工智能博主 &#x1f384;所属专栏&#xff1a;『碎碎念』 &#x1f37b;上篇纪念文&#xff1a; 我的创作纪念日 文章目录 &#x1f4e9;&#x1f4e9;&#x1f4e9;前言&#x1f…

thinkphp中分页paginate和group by一起使用时 代码异常的解决办法

1. paginate和group by报错&#xff0c;代码如下&#xff0c;月份分组 $page intval($where[page]);$limit intval($where[limit]);$start_time $where[start_time];$end_time $where[end_time];$query Db::table(eb_bonuslistlog)->field(DATE_FORMAT(create_time,&qu…

Cookie的详解

Cookie Cookie为什么要用Cookie&#xff1f;Cookie是什么&#xff1f;Cookie怎么用&#xff1f;Cookie常用属性修改与删除&#xff1a;在浏览器查看cookie前端页面读取CookieJava后端读写cookie最典型的cookie--JESSIONID是什么&#xff1f;什么时候种下JSESSIONID&#xff1f;…

2023年京东按摩仪行业数据分析(京东销售数据分析)

近年来&#xff0c;小家电行业凭借功能与颜值&#xff0c;取代黑电和白电&#xff0c;成为家电市场的主要增长点。在这一市场背景下&#xff0c;颜值更高、功能更丰富、品种更齐全的各类按摩仪&#xff0c;借助新消费和电子商务的风潮&#xff0c;陆续被推上市场。今年&#xf…

通达信波段选股公式,使用钱德动量摆动指标(CMO)

钱德动量摆动指标(CMO)是由图莎尔钱德发明的&#xff0c;取值范围在-100到100之间&#xff0c;是捕捉价格动量的技术指标。该指标计算近期涨幅之和与近期跌幅之和的差值&#xff0c;然后将计算结果除以同期所有价格波动的总和。本文的波段选股公式使用均线识别趋势&#xff0c;…

微信小程序上传图片和文件

1.从微信里选择图片或文件上传 使用的vant的上传组件 原生用 wx.chooseMessageFile() html <!-- 从微信上面选择文件 --><van-uploader file-list"{{ file }}" bind:after-read"afterRead" max-count"{{3}}" deletable"{{ true…

GrapeCity Documents for PDF (GcPdf) 6.2 Crack

GrapeCity PDF 文档 (GcPdf) 改进了对由 GcPdf 以外的软件生成的现有 PDF 文档的处理 在新的 v6.2 版本中&#xff0c;GcPdf 增强了 PDF 文档的加载和保存&#xff0c;并提供以下优势&#xff1a; GcPdf 现在可以加载和保存可能不严格符合 PDF 规范的 PDF 文档。GcPdf 现在将…

典籍研读+书法精进 暄桐「见道明心的笔墨」课程开课啦

8月12日&#xff0c;《林曦老师的线上直播书法课》之「见道明心的笔墨」就要开课啦。林曦老师将带我们去往中国文人精神世界的后花园&#xff0c;一起阅读《金刚经》《老子》等典籍。是不是很期待&#xff1f; 在2011年&#xff0c;暄桐成立的最初&#xff0c;课程便是面向零基…

Opencv项目实战:24 手势识别的石头剪刀布

目录 0、项目介绍 1、效果展示 2、项目搭建 3、项目代码展示与部分讲解 pyzjr库 游戏实现思路 4、项目资源 5、项目总结 0、项目介绍 简单的自娱自乐的计算机视觉互动游戏&#xff0c;石头剪刀布&#xff0c;使用random生成随机数&#xff0c;用于模拟AI窗口随机出拳&…

SecureCRT密码破解(实验环境:win10,SecureCRT Version 9.1.0 (x64 build 2579))

实验环境&#xff1a;win10&#xff0c; SecureCRT&#xff1a;Version 9.1.0 (x64 build 2579) 1. SecureCRTCipher.py 文件 #!/usr/bin/env python3 import os from Crypto.Hash import SHA256 from Crypto.Cipher import AES, Blowfishclass SecureCRTCrypto:def __init_…

如何选择适合自己的文件传输工具

随着互联网的发展&#xff0c;人们处理文件的需求也随之增加。不管是工作还是生活中&#xff0c;文件传输都是一个非常常见的问题。因此&#xff0c;如何选择适合自己的文件传输工具也越来越重要。在本文中&#xff0c;我将从以下几个方面进行分析和总结&#xff0c;希望能为大…

springboot文件上传和下载接口的简单思路

springboot文件上传和下载的简单思路 文件上传文件下载 文件上传 在springboot中&#xff0c;上传文件只需要在接口中通过 MultipartFile 对象来获取前端传递的数据&#xff0c;然后将数据存储&#xff0c;并且返回一个对外访问路径即可。一般对于上传文件的文件名&#xff0c…