算法与数据结构:红黑树

news2024/12/23 17:00:53

     ACM大牛带你玩转算法与数据结构-课程资料

 本笔记属于船说系列课程之一,课程链接:

哔哩哔哩_bilibiliicon-default.png?t=N7T8https://www.bilibili.com/cheese/play/ep66799?csource=private_space_class_null&spm_id_from=333.999.0.0

你也可以选择购买『船说系列课程-年度会员』产品『船票』,畅享一年内无限制学习已上线的所有船说系列课程:船票购买入口icon-default.png?t=N7T8https://www.bilibili.com/cheese/pages/packageCourseDetail?productId=598

做题网站OJ:HZOJ - Online Judge

Leetcode :力扣 (LeetCode) 全球极客挚爱的技术成长平台

 红黑树

建议看这篇文章时看看二叉排序树和AVL,不然理解不了其中的东西:二叉排序树和AVL树

        红黑树是如何构建的,并且他是如何进行像AVL树可以将左右子树的节点进行平衡的,就是基于它的5条性质:

  1. 每个节点是红色或黑色。
  2. 根节点是黑色。
  3. 所有叶子节点都是黑色(这里的叶子节点是指树中所有的空节点)。
  4. 每个红色节点的两个子节点都是黑色(从每个红色节点到叶子节点的路径上不能有两个连续的红色节点)。
  5. 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点。

性质1:他对于平衡二叉树种的每个节点多了一个属性,也就是颜色每个节点非黑及红;

性质2:红黑树的最顶端的根节点一定是黑色的,因为如果根节点是红色会将后续的插入和删除操作变得更复杂。

性质3:注意:这里的空节点是上一篇文章中创建AVL树用到的一个虚拟空节点NIL节点,为什么需要用到NIL这个虚拟空节点是一个编程技巧,让我们编程的时候方便进行一些判断和操作,在后续的删除操作会用到对NIL节点进行一些操作为了编程时进行更方便的进行平衡操作。

性质4:红色节点只能接黑色节点,这个性质就是为了保证红黑树的平衡

性质5:这条性质也是为了保证红黑树的平衡,并且在计算时需要加上那个虚拟空节点,虚拟空节点的颜色是黑色

        在实现红黑树时,一定要心里铭记这5条性质!!!!

红黑的难点:

        红黑树的难点,就是基于二叉排序树的删除和插入操作后的平衡操作。

        我的老师教给我的平衡终极大法:

        插入调整时站在祖父节点看,什么意思就是当前插入节点如果违反了性质4,因为插入的节点是红色节点,为什么是红色节点在后续会说,那么插入的节点是红色节点,违反了性质4,那么它的父节点那么一定也是红色,然后我们还需要往上找到它父节点的父节点也就是祖父节点,然后进行来调整操作;

        删除调整站在父节点看,同理插入调整怎么来进行看的。

        插入操作:

        问题:插入节点应该是什么颜色?

        答:红色,为什么呢?因为如果插入的节点是黑色,那么一定为违反性质5因为在没有插入黑色节点之前每条路径的黑色节点的数量是相同的,插入一个黑色节点那么这条路径就一定会多一个黑色节点,那么就需要进行平衡操作,而插入红色节点如果它的父节点是黑色,那么没有违反性质4性质5那么就不需要平衡,如果父节点是红色就需要平衡操作,那么平衡的概率是50%,而插入黑色是100%需要调整,所以选择插入红色节点。

插入调整分为两种情况:

        情况1:

        下面白色就当黑色节点

        插入节点是x,它的父节点20是红色的,然后口诀是什么,找到它的祖父节点来看,那么它的祖父节点是18,那么它的叔叔节点是15并且也是红色的。

        那么如何进行调整,记住调整一定是围绕的那5条性质并且主要是性质4性质5,那么直接将祖父节点变为红色,叔叔节点和父节点变为黑色。

     通过调整发现,这颗子树已经变得平衡。

        情况2:

        发生失衡节点是节点10,并它的叔叔节点是黑色的情况,那么找到它的祖父节点,往下看去它和AVL树种LL类型是否很像,那么我们就称这种类型为LL类型:

        然后通过确定图中节点的颜色

        黑色:18,20,5,16,14

        红色:15,10

        每条路径的黑色节点都为2个

        17节点的颜色是不确定的,因为图中的子树是没有画完全的,它是一颗子树,它现在的祖父节点上面还可能会有节点,17节点下面也可能会有节点,所以17节点的颜色是无法确定的,为什么图中17是红色的,是为了让各位读者看起来这颗子树没有违反性质5

        然后确定了每个节点的颜色以后,那么就需要进行平衡操作,如何进行平衡操作:

        先对祖父节点进行一个右旋:

        然后进行调整颜色,为了每条路劲的黑色节点都为2个,而且让这个子树满足性质4性质5,将10节点和18节点变为红色,15节点也就是当前的根节点变为黑色,或者将现在的根节点变为红色,10和18节点变为黑色都可以,并且不影响。这里我用的是红色下沉,也就是根为黑,子树为红:

        通过调整颜色后,可以发现目前的子树满足性质4性质5

        那么处理完LL类型,那么LR类型如何去处理呢,那么就是通过AVL中学到的技巧,通过父节点的一个左旋将LR类型变为LL类型来处理就可以了:

        那么这里通过25节点一个左旋,就变为了LL类型,就可以通过LL类型方式相同处理:

        RR和RL处理,对称过来就可以,就不详细展开说了。

        删除操作:

        问题:删除节点的颜色会引发红黑树失衡?

        答:那么就有6种情况:

  •  红,度为0,直接删除没有任何影响,删除后不会违反性质4和性质5,不会影响平衡;
  •  黑,度为0,删除度为0的黑色节点就会用到上面提到的NIL虚拟空节点了,删除度为0的黑色节点后,会用一个NIL去代替它的位置对吧,然后将NIL节点的颜色变为双重黑,这里是不是违反了第1条性质节点非黑及红,那么删除调整的难点就在这里,如何进行调整这个双重黑
  •  红,度为1,由于性质5,每条路上的黑色节点数量相同,并且红色节点只能接黑色节点,那么红色节点要么不接节点,要么必须接2个黑色节点,所以红色节点并且度为1的情况不存在;
  •  黑,度为1,红色度为1的情况,如果当前节点度为1那么为了维护性质4和5,那么它的子孩子一定是红色,然后直接删除当前节点把它唯一子孩子放在当前节点的位置,并且把它的唯一子孩子改为黑色,这样就删除了度为1的黑色节点;
  •  度为2的节点,通过二叉排序树的删除操作,可以将删除度为2的节点变为删除度为0和1的节点;

        那么下面的情况就是对于双重黑的处理:

        蓝色表示双重黑节点,白色表示黑色节点

        注意:但是图中有些节点的颜色是不确定的

        情况1:

        它的兄弟节点是黑色,并且兄弟节点的子孩子都是黑色,这里的子树是没有违反性质4和性质5的,现在的目的就是为了去除这个双重黑,并且让这颗子树平衡。

        那么图中节点的颜色:

        双重黑:33

        黑色:27,25,28

        父节点30的颜色是不确定的。

        那么如何进行调整呢,33节点双重黑,就去一重黑,变为黑色节点

        那么父节点增加一重黑,如果为红色变为黑色,如果为黑色变为双重黑

        兄弟节点去一重黑,变为红色

        这里就有人会有疑问那如果当前子树根节点为黑色,那么调整后他为双重黑了,也没有去掉双重黑呀违反了性质1啊。

        二叉排序树删除节点是不是递归进行删除的,现在只是在递归到这个子树进行平衡,那么在后续的回溯过程中,当前的父节点他也可能会变为其他节点孩子,那么就进行下次的平衡处理就可以了,最后如果到了整棵树的根节点处了,那么直接给他减去一重黑即可。

        情况2:

        这里处理的就是RR和LL类型,下面详细解释的是RR类型:

        当前情况中,双重黑节点的兄弟节点是黑色,并且他兄弟节点的右节点是红色,那么就称这种情况叫做RR类型;

        然后如果兄弟节点的左节点也是红色,那么优先考虑RR类型,如果右节点为黑色那么就是RL类型。同理如果对称过来优先考虑LL类型;

        图中现在每条路径的中的黑色节点数量都为2个

        然后确定一下当前图中节点的颜色

        双重黑:27

        黑色:40,56,80

        红色:72

        不确定:30,36,56,80

        RR类型,那么就父节点直接一个左旋:

        然后为了满足性质5:

        36,30节点的颜色是不确定的,如果36为红色30也为红色就违反了性质4,所以必须把30号节点变为黑色;

        27减少一重黑,变为黑色;

        那么现在40号节点右子树路劲上每条路都少一个黑色节点来满足性质5,那么就将72变为黑色,那么每条路径上的黑色节点都为3个了:

        这里红黑树平衡了,但是最开始每条路径的中的黑色节点数量都为2个,因为要考虑的是整棵树,如果这颗子树平衡后他目前的每条路多了一个黑色节点,那么它上面还有节点的话往下来看,这边子树路径中多了一个黑色节点,而另外的子树路径中刚好少了一个节点,那么就违反了性质5,所以需要将现在的节点40改为红色,保持最开始子树中每条路径的黑色节点数。

        然后如果最开始图中的父节点30颜色为黑色,那么每条路劲都有3个黑色节点,那么这里兄弟节点40就变为黑色;

        所以这里兄弟变的颜色为之前父节点的颜色。

        

        总结:

        当前情况RR类型:

        通过父节点的左旋,

        将双重黑节点去一重黑,

        兄弟节点通过旋转到达父节点的位置,那么新根节点的颜色变为之前根节点的颜色

        最后旋转后的根节点的左右孩子都变为黑色。

        LL类型同理对称后一样操作。

        情况3:

        这种情况,就是兄弟节点为黑色节点,并且兄弟节点的左节点为红色节点,那么这种类型就是RL类型:

        每条路劲黑色节点数量为2

        确定的节点颜色

        双重黑:27

        黑色:40,35,37,72(如果72是红色那么它一定是RR类型,RR类型优先级高于RL类型)

        红色:36

        那么RL类型,想办法给他变为RR类型,先对兄弟节点进行一个右旋:

        然后为了维护性质5:

        36变为黑色

        40变为红色

        也就是之前的兄弟节点变红,新兄弟节点变黑

        OK变为了RR类型情况2的类型,那么直接用情况2进行处理。

        然后会发现,36和40这两个节点在情况2中也进行颜色的改变,所以在编程的时候,我们不会在RL变RR类型时进行对颜色的改变,对于上面兄弟节点旋转后的颜色改变,只是为了理解这个过程。

        总结情况3:

        通过一个兄弟节点的右旋,然后之前的兄弟节点变红,新兄弟节点变黑,就可以得到RR类型,也就是情况2。

        情况4:

      兄弟节点为红色,那么兄弟节点一定为红色,那么父节点一定为黑色,35,40也一定为黑色。

        所以确定颜色的节点:

        双重黑:27

        黑色:30,35,40

        红色:39

        每条路劲黑色节点为3个

        那么通过父节点一个左旋,变为图中如下情况

        然后维护性质5,并且之前每条路劲黑色节点为3个,那么进行调色:

        39变为黑色,30变为红色:

        然后通过发现,27双重黑节点的兄弟节点为黑色节点了,那么就可以进行情况1,情况2,情况3进行操作判断了。

        总结情况4:

        也就是最开始的兄弟节点变黑,最开始的父节点变红,然后进行一个父节点的左旋,然后通过新父节点的往下看可以得到情况1,2,3。

代码实现: 

        通过对于插入和删除的每种情况的分析:

        对于这个总结需要自己去捋捋,每个人分析的方式是不一样的。

        然后最后是代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define RED    0
#define BLACK  1
#define DBLACK 2
#define NIL (&__NIL)
#define K(n) (n->key)
#define L(n) (n->lchild)
#define R(n) (n->rchild)
#define C(n) (n->color)

typedef struct Node {
    int key, color; // 0 red, 1 black, 2 double black
    struct Node *lchild, *rchild;
} Node;

Node __NIL;

__attribute__((constructor))
void init_NIL() {
    NIL->key   = -1;
    NIL->color = BLACK;
    NIL->lchild = NIL->rchild = NIL;
    return ;
}

Node *getNewNode(int key) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->key   = key;
    p->color = RED;
    p->lchild = p->rchild = NIL;
    return p;
}

//判断当前节点的左右孩子是否有红色
bool has_red_node(Node *root) {
    return root->lchild->color == RED || root->rchild->color == RED;
}

//左旋操作
Node *left_rotate(Node *root) {
    Node *new_root = root->rchild;
    root->rchild = new_root->lchild;
    new_root->lchild = root;
    return new_root;
}

//右旋操作
Node *right_rotate(Node *root) {
    Node *new_root = root->lchild;
    root->lchild = new_root->rchild;
    new_root->rchild = root;
    return new_root;
}

//平衡操作
Node *insert_maintain(Node *root) {
    int flag = 0;
    //站在祖父节点来看
    //判断是在左子树还是右子树失衡
    //左子树失衡flag = 1
    if (C(L(root)) == RED && has_red_node(L(root))) flag = 1;
    //右子树失衡flag = 2
    if (C(R(root)) == RED && has_red_node(R(root))) flag = 2;
    if (flag == 0) return root;
    //如果当前祖父节点的左右孩子都为红色
    //那么就是情况1,也就是父节点和叔叔节点都是红色的情况
    if (C(L(root)) == RED && C(R(root)) == RED) goto red_up_maintain;
    //这里就是L类型
    if (flag == 1) {
        //判断是否是LR类型
        if (C(R(L(root))) == RED) {
            L(root) = left_rotate(L(root));
        }
        root = right_rotate(root);
    } 
    //这里就是R类型
    else {
        //判断是否是RL类型
        if (C(L(R(root))) == RED) {
            R(root) = right_rotate(R(root));
        }
        root = left_rotate(root);
    }
red_up_maintain:
    //通过旋转或者判断之后
    //现在子树最顶上的三个节点进行修改颜色
    C(root) = RED;
    C(L(root)) = C(R(root)) = BLACK;
    return root;
}

//二叉树排序树的基本插入方法
Node *__insert(Node *root, int key) {
    if (root == NIL) return getNewNode(key);
    if (root->key == key) return root;
    if (key < root->key) root->lchild = __insert(root->lchild, key);
    else root->rchild = __insert(root->rchild, key);
    //最后返回平衡后的根节点
    return insert_maintain(root);
}

//红黑树的插入方法
Node *insert(Node *root, int key) {
    //封装
    root = __insert(root, key);
    //性质2根节点一定是黑色
    root->color = BLACK;
    return root;
}

//找到当前节点的前驱节点
Node *predecessor(Node *root) {
    Node *temp = root->lchild;
    while (temp->rchild != NIL) temp = temp->rchild;
    return temp;
}

Node *erase_maintain(Node *root) {
    //站在父节点来看
    //如果当前节点的左右孩子没有双重黑说明平衡
    if (C(L(root)) != DBLACK && C(R(root)) != DBLACK) return root;
    //如果兄弟节点为红色, 情况4
    if (has_red_node(root)) {
        //最开始的父节点变红色
        root->color = RED;
        if (root->lchild->color == RED) {
            //如果兄弟节点在左,就右旋
            root = right_rotate(root);
            //然后对旋转后双重黑节点的父节点,进行调整
            root->rchild = erase_maintain(root->rchild);
        } else {
            //对称同理
            root = left_rotate(root);
            root->lchild = erase_maintain(root->lchild);
        }
        //最开始的兄弟节点变黑色
        root->color = BLACK;
        return root;
    }
    //情况1,兄弟节点的子孩子都为黑
    //直接将父节点加一重黑
    //父节点的左右孩子减一重黑
    if ((root->lchild->color == DBLACK && !has_red_node(root->rchild)) 
        || (root->rchild->color == DBLACK && !has_red_node(root->lchild))) {
        root->color += 1;
        root->lchild->color -= 1;
        root->rchild->color -= 1;
        return root;
    }
    //判断双重黑在那个子树
    if (root->rchild->color == DBLACK) {
        //直接减一重黑
        root->rchild->color = BLACK;
        //情况3
        //如果不是LL类型,那么就需要进行对兄弟节点的左旋
        //然后不进行节点颜色处理
        if (root->lchild->lchild->color != RED) {
            root->lchild = left_rotate(root->lchild);
        }
        //因为旋转后兄弟节点的颜色要变为父节点的颜色
        root->lchild->color = root->color;
        //然后对于父节点的右旋
        root = right_rotate(root);
    } else {
        //对称同理
        root->lchild->color = BLACK;
        if (root->rchild->rchild->color != RED) {
            root->rchild = right_rotate(root->rchild);
        }
        root->rchild->color = root->color;
        root = left_rotate(root);
    }
    //情况2的统一处理
    //通过旋转后那,当前根节点的左右孩子都要变为黑色
    root->lchild->color = root->rchild->color = BLACK;
    return root;
}

//二叉树排序树的删除操作
Node *__erase(Node *root, int key) {
    if (root == NIL) return root;
    if (key < root->key) {
        root->lchild = __erase(root->lchild, key);
    } else if (key > root->key) {
        root->rchild = __erase(root->rchild, key);
    } else {
        //在这里需要加黑
        if (root->lchild == NIL || root->rchild == NIL) {
            //度为0和1的节点一起处理
            //获取到当前节点的子孩子,如果没有子孩子,那么这里就体现处理NIL虚拟空节点的用处了
            Node *temp = root->lchild == NIL ? root->rchild : root->lchild;
            temp->color += root->color;
            free(root);
            return temp;
        }
        //将删除度为2的节点,变为删除度为0或1的节点进行处理
        Node *temp = predecessor(root);
        root->key = temp->key;
        root->lchild = __erase(root->lchild, temp->key);
    }
    //删除调整平衡
    return erase_maintain(root);
}

//红黑树的删除操作
Node *erase(Node *root, int key) {
    //封装
    root = __erase(root, key);
    //根节点一定是黑色,为了防止双重黑跑到根节点来
    root->color = BLACK;
    return root;
}

void clear(Node *root) {
    if (root == NIL) return ;
    clear(root->lchild);
    clear(root->rchild);
    free(root);
    return ;
}

void output(Node *root) {
    if (root == NIL) return ;
    printf("(%d| %d; %d, %d)\n", 
        C(root), K(root),
        K(L(root)), K(R(root))
    );
    output(root->lchild);
    output(root->rchild);
    return ;
}

int main() {
    srand(time(0));
    #define MAX_N 10
    Node *root = NIL;
    for (int i = 0; i < MAX_N; i++) {
        int x = rand() % 100;
        printf("\ninsert %d to red black tree : \n", x);
        root = insert(root, x);
        output(root);
    }
    int x;
    while (~scanf("%d", &x)) {
        printf("\nerase %d from red black tree\n", x);
        root = erase(root, x);
        output(root);
    }
    return 0;
}

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

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

相关文章

计算机图形学入门04:视图变换

1.MVP变换 将虚拟场景中的模型投影到屏幕上&#xff0c;也就是二维平面上&#xff0c;需要分三个变换。 1.首先需要知道模型的位置&#xff0c;也就是前面提到的基本变换&#xff0c;像缩放、平移&#xff0c;旋转&#xff0c;也称为模型(Model)变换。 2.然后需要知道从…

精选免费在线工具与资源推荐20240531

精选免费在线工具与资源推荐 引言 在互联网高速发展的今天&#xff0c;我们身处一个信息爆炸的时代。为了更好地应对工作和学习中的挑战&#xff0c;我们时常需要借助各种工具和资源来提高效率。幸运的是&#xff0c;网络上存在着大量免费且高效的在线工具和资源&#xff0c;…

告别低效提问:掌握BARD技巧,让AI成为你的智能助手!

今天只聊一个主题&#xff1a;提示词 Prompt。 说到提示词&#xff0c;大家可能都看过GPT的高级示例&#xff0c;那些几百字的提示词&#xff0c;写起来确实不容易。 那么&#xff0c;如何写出同样效果的提示词呢&#xff1f; 有没有什么公式或者系统学习的方法&#xff1f;…

HackTheBox-Machines--Nibbles

Nibbles 测试过程 1 信息收集 NMAP 80 端口 网站出了打印出“Hello world&#xff01;”外&#xff0c;无其他可利用信息&#xff0c;但是查看网页源代码时&#xff0c;发现存在一个 /nibbleblog 文件夹 检查了 http://10.129.140.63/nibbleblog/ &#xff0c;发现了 /index.p…

windows系统配置dns加快访问github 实用教程一(图文保姆级教程)

第一步、打开网页 https://tool.lu/ip IP地址查询 - 在线工具 输入www.github.com 或者github.com 点击网页查询按钮, 获取对应github网站对应的ip 完整操作步骤如上图所示,可以很清晰的看到github网站的ip显示地区是美国也就是说该网站服务器是在国外, 这也就是为什么我们在…

JUC总结2

synchronized锁 synchronized底层原理 当使用synchronized时&#xff0c;不需要自己编写代码进行上锁和上锁的操作&#xff0c;因为JVM帮我们把相关操作完成了。 JVM采用了monitorenter和monitorexit指令进行同步的&#xff0c;前者指向同步代码开始的位置&#xff0c;后者指…

java——网络原理初识

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 小比特 大梦想 目录 1.网络通信概念初识1.1 IP地址1.2端口号1.3协议1.3.1协议分层协议分层带来的好处主要有两个方面 1.3.2 TCP/IP五层 (或四层模型)1.3.3 协议的层和层之间是怎么配合工作的 1.网络通信概念初识…

探索气象数据的多维度三维可视化:PM2.5、风速与高度分析

探索气象数据的多维度可视化&#xff1a;PM2.5、风速与高度分析 摘要 在现代气象学中&#xff0c;数据可视化是理解复杂气象模式和趋势的关键工具。本文将介绍一种先进的数据可视化技术&#xff0c;它能够将PM2.5浓度、风速和高度等多维度数据以直观和动态的方式展现出来。 …

国产身份域管架构图集合(信创政策AD域替换必看)

几类典型架构 双机架构 单点单机房 集群架构 多点单机房 两地三中心架构 多点多机房 多地分布式架构 多点多机房 全栈信创方案架构&#xff0c;欢迎探讨交流~

emp.dll文件丢失要怎么解决?荒野大镖客emp.dll修复方法分享

软件运行过程中经常遇到各种技术问题&#xff0c;其中之一就是动态链接库&#xff08;DLL&#xff09;文件丢失的现象。DLL文件是Windows操作系统中一个重要的组件&#xff0c;它包含运行多个应用程序所需要的代码和数据。因此&#xff0c;一个丢失的DLL文件&#xff0c;如“em…

同城活动报名系统源码活动组局找搭子小程序Java源码全开源

活动流程图 管理端设置 1.系统操作 2.活动类型 可添加线上和线下活动,线上活动,比如游戏等,需要可以进入游戏,需要签到等; 线下活动,比如线下交友等, 3.活动管理 可给用户添加活动,给活动设置报名时间,活动开始时间等; 也可查看报名列表和签到列表 4.进行中的活动 等发起…

校园导航系统C++

制作一个简单的大学城导航系统&#xff0c;根据用户指定的起点和终点&#xff0c;求出最短路径长度以及具体路径。 项目要求&#xff1a; 1&#xff09;程序与数据相分离&#xff0c;地图中的所有数据都是从文件读入&#xff0c;而不是写在代码中 2&#xff09;最短路径算法…

热敏电阻的设计

热敏电阻(NTC)的作用&#xff1a;抑制开机时的浪涌电流。防止开机瞬间产生的浪涌电流损坏后面的元件。 取值依据:根据对开机的脉冲电流&#xff08;浪涌电流&#xff09;小于多少A&#xff1f; 由,这个U是指最大输入电压&#xff0c;I为要求的浪涌电流。 NTC是负温度系数的热…

设计模式23——状态模式

写文章的初心主要是用来帮助自己快速的回忆这个模式该怎么用&#xff0c;主要是下面的UML图可以起到大作用&#xff0c;在你学习过一遍以后可能会遗忘&#xff0c;忘记了不要紧&#xff0c;只要看一眼UML图就能想起来了。同时也请大家多多指教。 状态模式&#xff08;State&am…

打造高效上传体验:基于Kotlin的Android快速上传框架

1. 引言 在Android开发中&#xff0c;文件上传操作常常面临各种挑战&#xff0c;为此我开源了一个高效、易用的快速上传框架&#xff0c;助力开发者轻松实现文件上传功能。 GitHub项目地址: 点我 2. 框架特点概述 纯Kotlin编写&#xff1a;简洁、现代的编程语言。MVVM架构&a…

动态分配函数参数用二级指针的作用

文章目录 前言一、案例 前言 在一些情况下&#xff0c;我们需要在函数内部动态地分配内存来存储结构体&#xff0c;并且需要在函数外部访问该结构体。在这种情况下&#xff0c;可以使用二级指针作为函数参数来实现动态内存分配&#xff0c;并且在函数外部使用指针访问结构体。…

py黑帽子学习笔记_web攻击

python网络库 py2的urllib2 py3好像把urllib2继承到了标准库urllib&#xff0c;直接用urllib就行&#xff0c;urllib2在urllib里都有对应的接口 py3的urllib get请求 post请求&#xff0c;和get不同的是&#xff0c;先把post请求数据和请求封装到request对象&#xff0c;再…

数字化转型对企业来说意味着什么?

数字化转型是当今社会不可避免的趋势&#xff0c;它的发展其实是多方面因素影响导致的。首先&#xff0c;随着科技的迅速发展&#xff0c;人们对于信息获取和处理的需求越来越强烈&#xff0c;这促使了各行各业都要朝着数字化方向发展。其次&#xff0c;全球化的潮流让企业需要…

制作U启动装win10系统

一、背景 在我们日常上班过程中经常会遇到以下问题&#xff1a; 1、c盘快满了 2、新电脑用着好卡 3、买新电脑不会装系统 二、实现方式 通常我们遇到这种情况 都会选择去电脑专卖店找专人装系统 但是这样又需要花费自己的money&#xff0c;这样显得自己不够专业像个菜鸟&#…

【成品设计】基于STM32单片机的语音远程运算器

《基于STM32单片机的语音远程运算器》 输入端 所需器件&#xff1a; STM32最小系统板。语音识别模块&#xff1a;用于检测语音命令。蓝牙模块&#xff1a;作为主机用于与输出端蓝牙模块进行连接&#xff0c;发送数据。蜂鸣器红色LED&#xff1a;用于语音命令检测错误提示。 …