算法与数据结构:二叉排序树与AVL树

    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) 全球极客挚爱的技术成长平台

二叉排序树  

构建二叉排序树:

        数据结构 = 结构定义 + 结构操作

结构定义:

        这个数据结构是靠二叉树来得到的,那么我们就先定义树形结构,而树形结构是由节点构成的,那么最终我们定义一个节点的结构体,以及对节点的初始化操作和清空操作:

//定义节点结构
typedef struct Node {int key;struct Node *lchild, *rchild;
} Node;//初始化
Node *getNewNode(int key) {Node *p = (Node *)malloc(sizeof(Node));p->key = key;p->lchild = p->rchild = NULL;return p;
}//清空
void clear(Node *root) {if (root == NULL) return ;clear(root->lchild);clear(root->rchild);free(root);return ;
}
结构操作:

        在对结构定义完成后,那么就是开始定义结构操作了,那就是对二叉排序的增加节点和删除节点:

        增加节点

        由于增加节点的操作比较简单,就不做图来解释了:

//和之前学习树时的插入节点是差不多的
//只是多了对二叉排序树性质的判断
Node *insert(Node *root, int key) {if (root == NULL) return getNewNode(key);//当插入值等于当前节点值时,返回当前节点,说明不用插入该节点if (key == root->key) return root;//当插入值小于当前节点值时,往左子树递归进行插入if (key < root->key) root->lchild = insert(root->lchild, key);//当插入值大于当前节点值时,往右子树递归进行插入else root->rchild = insert(root->rchild, key);return root;
}
        删除节点

        删除节点时,需要判断当前删除的节点的度:

  • 删除度为0的节点:直接删除
  • 删除度为1的节点:

        如图一个排序二叉树,当前如果删除的节点为3,而它的度为1,可以直接删除,然后接在18的左子树上,也不会影响二叉排序树的性质。

        


         

  • 删除度为2的节点:

        如图下面的二叉树:

        

        如何去删除20号节点,我们需要找20对应中序遍历的前驱节点或者后继节点:

        通过发现二叉树的中序遍历就是一个升序序列,而图中的中序遍历为:

        17 18 19 20 28 29 30 32

        看20这个节点,19是它的前驱,28是他的后继,可以发现前驱就是当前节点左子树的最大值,后继就是当前节点右子树的最小值。

        那么如果通过二叉排序怎么去找到当前节点的前驱和后继呢:

        前驱:先进入当前节点的左子树然后一直往右子树一直找,直到没有右子树,那么当前节点就是它的前驱节点。

        后继:同理先进入当前节点右子树,一直往左子树找,然后没有左子树,那么当前节点就是它的后继节点。

        得到它的前驱和后继有什么用呢,可以把需要删除的节点和他的前驱或者后继进行交换位置,然后就可以变成删除度为1的节点或者删除度为0的节点的问题了。因为它的前驱或者后继它的度一定是小于等于1的,因为前驱和后继一个是没有右子树一个是没有左子树的。

        如图,20和它的前驱节点19发生了交换,并且19在那个节点上满足二叉排序树的性质。

        然后进行对20节点的删除,也就是删除度为1的节点的方法。

 有了思路后,通过思路去实现代码:

//找到删除节点的前驱节点
Node *predecessor(Node *root) {Node *temp = root->lchild;while (temp->rchild) temp = temp->rchild;return temp;
}Node *erase(Node *root, int key) {if (root == NULL) return root;//先通过二叉树的性质,递归找到需要删除的节点if (key < root->key) root->lchild = erase(root->lchild, key);else if (key > root->key) root->rchild = erase(root->rchild, key);//找到需要删除的节点else {//度为0的节点删除if (root->lchild == NULL && root->rchild == NULL) {free(root);return NULL;} //度为1的节点删除else if (root->lchild == NULL || root->rchild == NULL) {//先通过一个变量记录删除节点的左子树或者右子树Node *temp = root->lchild ? root->lchild : root->rchild;//删除节点free(root);//然后将删除节点的左子树或右子树接上return temp;} //度为2的节点删除else {//这里是用的是找前驱节点Node *temp = predecessor(root);//直接把删除节点的值用前驱节点的值覆盖掉root->key = temp->key;//然后通过递归调用去删除前驱节点root->lchild = erase(root->lchild, temp->key);}}return root;
}
完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>#define KEY(n) (n ? n->key : -1)typedef struct Node {int key;struct Node *lchild, *rchild;
} Node;Node *getNewNode(int key) {Node *p = (Node *)malloc(sizeof(Node));p->key = key;p->lchild = p->rchild = NULL;return p;
}//和之前学习树时的插入节点是差不多的
//只是多了对二叉排序树性质的判断
Node *insert(Node *root, int key) {if (root == NULL) return getNewNode(key);//当插入值等于当前节点值时,返回当前节点,说明不用插入该节点if (key == root->key) return root;//当插入值小于当前节点值时,往左子树递归进行插入if (key < root->key) root->lchild = insert(root->lchild, key);//当插入值大于当前节点值时,往右子树递归进行插入else root->rchild = insert(root->rchild, key);return root;
}//找到删除节点的前驱节点
Node *predecessor(Node *root) {Node *temp = root->lchild;while (temp->rchild) temp = temp->rchild;return temp;
}Node *erase(Node *root, int key) {if (root == NULL) return root;//先通过二叉树的性质,递归找到需要删除的节点if (key < root->key) root->lchild = erase(root->lchild, key);else if (key > root->key) root->rchild = erase(root->rchild, key);//找到需要删除的节点else {//度为0的节点删除if (root->lchild == NULL && root->rchild == NULL) {free(root);return NULL;} //度为1的节点删除else if (root->lchild == NULL || root->rchild == NULL) {//先通过一个变量记录删除节点的左子树或者右子树Node *temp = root->lchild ? root->lchild : root->rchild;//删除节点free(root);//然后将删除节点的左子树或右子树接上return temp;} //度为2的节点删除else {//这里是用的是找前驱节点Node *temp = predecessor(root);//直接把删除节点的值用前驱节点的值覆盖掉root->key = temp->key;//然后通过递归调用去删除前驱节点root->lchild = erase(root->lchild, temp->key);}}return root;
}void clear(Node *root) {if (root == NULL) return ;clear(root->lchild);clear(root->rchild);free(root);return ;
}void output(Node *root) {if (root == NULL) return ;printf("(%d ; %d, %d)\n", KEY(root), KEY(root->lchild),KEY(root->rchild));output(root->lchild);output(root->rchild);return ;
}void in_order(Node *root) {if (root == NULL) return ;in_order(root->lchild);printf("%d ", root->key);in_order(root->rchild);return ;
}int main() {srand(time(0));#define MAX_OP 10Node *root = NULL;for (int i = 0; i < MAX_OP; i++) {int key = rand() % 100;printf("insert key %d to BST\n", key);root = insert(root, key);}output(root);printf("in order : ");in_order(root);printf("\n");int x;while (~scanf("%d", &x)) {printf("erase %d from BST\n", x);root = erase(root, x);in_order(root); printf("\n");}return 0;}

AVL树 

        为什么会有AVL树,先来看一个二叉排序树:

        

        通过发现,这颗二叉排序树退化成了链表,然后它的结构操作时间复杂度都降到了O(n)的级别。而二叉排序树的优点又是这些操作的时间复杂度都是O(logn)的,然后为了保证时间复杂度就有人发明了AVL树:

        AVL是通过选择操作来进行将左右子树的高度差控制在小于等于1的范围内的!!!

通过旋转平衡二叉树

左旋(右旋同理)

         如图这是一颗二叉排序树,1,2,3为节点,AB为2的左右子树,CD为3的左右子树。

        现在我通过1节点左旋:

        如何去理解这个过程,现在我拎着节点3,把节点3拎到节点1的位置去,1就该往下沉一层到节点2的位置,2节点下面的子树也应该往下沉一层
 

          那么现在应该是这样一个样子,将3的左子树指向节点1,因为节点1小于节点3,所以节点1作为节点3的左孩子是没有破坏二叉排序树的性质的。

        然后C子树现在没有节点指向了,节点1又没有右子树,那么节点1的右子树就指向C子树。

        然后证明C子树接在节点1的位置上不破坏二叉排序树的性质,通过最开始这张图来进行分析,C子树在1的右子树,那么C子树中的节点一定是大于1节点的值,所以c子树是可以直接接在1节点上,并且不会破坏二叉排序树的性质,最后完成了左旋操作。

        右旋操作和左旋操作差不了太多,也就是换了一个方向,同理可以推断出来,所以不详细的模拟了。

AVL树的平衡操作

        有了左旋和右旋操作的了解,下面通过左旋和右旋来进行对AVL的平衡调整:

        AVL树的失衡类型:
                LL类型和RR类型:
        

        对于图中就是失衡的子树。

        注意是子树,不要把图中的树看为一整颗树,需要看一颗子树,它可能是某一颗大树的子树。

        还需要注意的是节点1是从上到下往上看第一个失衡的节点,对于结点2来看它和它的子树是平衡的,节点3来看他和它的子树是平衡的,ABCD子树都是平衡的,但是从节点1来看他是失衡的。

        失衡的子树,也就是子树高度最高的子树,然后如果失衡子树在失衡节点的左孩子的左子树那么就为LL,右孩子的右子树为RR,左孩子的右子树为LR,右孩子的左子树为RL。

        比如这颗子树,它就是RR类型,从节点25来看它是失衡的,但是从下面任何一个节点来看它都是平衡的,那么40这个节点加上它的子树就为造成失衡的子树。

        图中红色标记的子树是高度最高的子树,也就是造成从节点1来看让这颗子树失衡的子树。也就是说从节点1来看:

        h(3) - h(2) > 1了,造成了失衡。

        那么如何去调整,就需要用到左旋和右旋。

        对于LL的处理:

        直接通过对节点1的一个右旋就可以平衡二叉树:

       h(x)为旋转前高度,H(x)为旋转后的高度

       旋转前:

        h(2) = h(3) + 2,因为失衡可以得到

        h(2) = h(a) + 1,因为a为高一点的子树,所以可以推断得到

        h(b) = h(a) - 1,因为a子树比b子树高一层

        h(3) = max(h(c),h(d)) + 1

        通过上面的式子可以得到

        h(a) = max(h(c),h(d)) + 2

        旋转后:

        通过节点2来看,子树A和节点1的高度差为,A的高度去减去节点1的高度,而节点1的高度要么是B的高度+1或者3的高度+1,通过上面的式子可以得到B的高度+1等于A,所以如果B+1高度为节点1的高度,那么不会失衡,如果节点3的高度+1作为节点1的高度,那么

        h(a) - (h(3) + 1) = max(h(c),h(d)) + 2 - (max(h(c),h(d)) + 1 + 1) = 0

        也是处于平衡状态,所以从节点2来看二叉树是平衡的,

        又从节点1来看,b的高度为h(b) = h(a) - 1 = max(h(c),h(d)) + 1,和h(3)的高度一样,所以从节点1来看二叉树也是平衡的,3节点更不用判断了他从开始没变过那么也是平衡的

        最终LL类型通过右旋可以让失衡状态转为平衡,同理RR类型一样左旋就可以达到平衡。

LR类型和RL类型:

        LR和RL类型如何去处理,例如LR类型:

        

        由于失衡那么 h(2) = h(d) + 2

        h(1) = max(h(b), h(c)) + 1

        h(a) = h(1) - 1

        h(2) = h(1) + 1

        所以h(a) = max(h(b), h(c))

               h(d) = h(2) - 2 = h(1) - 1 = max(h(c), h(b)) = h(a)

        先通过节点2的一个左旋,这样就能得到一个LL类型

        如何得到的

        h(2) = h(a) + 1

        h(1) = h(2) + 1 = h(a) + 2

        又h(a) = h(d)

        所以较高的子树跑到左边去,也就是LR中的R中较高的子树变为了L了,通过节点2的左旋就可以得到LL类型,

        然后再通过节点3的一个右旋,就可以让这课树平衡了:

        

        现在h(2) = h(a) + 1

               h(3) = max(h(c), h(d)) + 1 = max(h(c), h(a)) + 1 = h(a) + 1

               h(2) - h(3) <= 1所以平衡

代码实现

        了解为什么要将二叉排序进行平衡操作和理解了通过左旋和右旋的方式将二叉排序树进行平衡后,下面进行代码实现:

        结构定义

        这里用到了一段代码:

__attribute__((constructor))

        它的作用就是,在main函数之前执行他设置的函数,对应代码段中那就是先执行int_NIL 初始化__NIL节点的操作。

        关于它的说明:


//定义节点
typedef struct Node {int key, h;struct Node *lchild, *rchild;
} Node;//定义一个全局变量__NIL,用来表示空节点
Node __NIL;
#define NIL (&__NIL)
//利用__attribute__((constructor)) 用来设置init_NIL函数先于main主函数执行
//用来初始化NIL节点
//NIL节点用来表示空节点,这是一个编码小技巧
__attribute__((constructor))
void init_NIL() {NIL->key = -1;NIL->h = 0;NIL->lchild = NIL->rchild = NIL;return ;
}//获取新节点的初始化操作
Node *getNewNode(int key) {Node *p = (Node *)malloc(sizeof(Node));p->key = key;p->h = 1;p->lchild = p->rchild = NIL;return p;
}//清除节点操作,采用后续遍历的方式进行清除
void clear(Node *root) {if (root == NIL) return ;clear(root->lchild);clear(root->rchild);free(root);return ;
}
        结构操作
        平衡操作:
#define K(n) (n->key)
#define H(n) (n->h)
#define L(n) (n->lchild)
#define R(n) (n->rchild)//更新高度
void update_height(Node *root) {//当前节点的高度等于左右子树最高的高度+1H(root) = (H(L(root)) > H(R(root)) ? H(L(root)) : H(R(root))) + 1;return ;
}//左旋操作
Node *left_rotate(Node *root) {//左旋操作后//当前节点的右孩子应该在当前节点的位置//让后当前节点变为了,他右孩子的左子树//然后当前节点的右孩子的左子树指向了当前节点//所以当前节点的右子树应该去指向它之前的右孩子的左子树printf("left rotate : %d\n", root->key);//创建一个节点用来记录当前节点的右孩子Node *new_node = root->rchild;//那么当前节点的右子树就可以指向它之前的右孩子的左子树root->rchild = new_node->lchild;//右孩子的左子树指向了当前节点,那么右孩子就通过左旋操作//到了当前节点的位置上了//也就完成了左旋操作new_node->lchild = root;//然后更新当前节点的高度update_height(root);//更新右孩子的高度update_height(new_node);//其他节点的高度是没有改变的//因为右孩子替代了当前节点的位置//那么就返回右孩子return new_node;
}//右旋操作
Node *right_rotate(Node *root) {//右旋操作同理反过来思考就可以了printf("right rotate : %d\n", root->key);Node *new_node = root->lchild;root->lchild = new_node->rchild;new_node->rchild = root;update_height(root);update_height(new_node);return new_node;
}const char *type_str[5] = {"","maintain type : LL","maintain type : LR","maintain type : RR","maintain type : RL"
};//平衡操作,也就是判断是LL,RR或者LR和RL类型
//然后再进行左右旋后平衡二叉树
Node *maintain(Node *root) {//判断当前子树是否左右子树平衡if (abs(H(L(root)) - H(R(root))) <= 1) return root;//这里的type只是起了最终演示的效果,用来进行debug和演示的int type = 0;//判断前面的字母,例如LR中的Lif (H(L(root)) > H(R(root))) {//进入到这里说明是L?类型//下面if判断就是是否是LR类型,如果是先进行左旋if (H(R(L(root))) > H(L(L(root)))) {//说明是LR类型,那么先对当前节点的左子树进行左旋变为LL类型//下面会一起对LL类型进行处理root->lchild = left_rotate(root->lchild);type += 1;}//因为通过上面的if判断后,如果是LR类型,已经通过左旋变为LL类型//如果是LL类型那么上面的if里面的代码段也不会执行//所以这里直接右旋处理LL类型,将当前节点进行平衡root = right_rotate(root);type += 1;} //这里就是前面字母是R类型了else {type = 2;//if判断是否是RL类型if (H(L(R(root))) > H(R(R(root)))) {//说明是RL类型,那么先将当前节点的右子树进行右旋root->rchild = right_rotate(root->rchild);type += 1;}//对于RR类型处理root = left_rotate(root);type += 1;}printf("%s\n", type_str[type]);//完成当前节点的平衡操作,返回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);//这是一个回溯的过程,所以是从下往上节点进行的下面的操作//更新每个节点的高度update_height(root);//返回平衡后的节点return maintain(root);
}Node *predecessor(Node *root) {Node *temp = root->lchild;while (temp->rchild != NIL) temp = temp->rchild;return temp;
}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) {Node *temp = root->lchild != NIL ? root->lchild : root->rchild;free(root);return temp;} else {Node *temp = predecessor(root);root->key = temp->key;root->lchild = erase(root->lchild, temp->key);}}//然后主要的就是更新高度和平衡操作update_height(root);return maintain(root);
}Node *find(Node *root, int key) {if (root == NIL) return NIL;if (root->key == key) return root;if (key < root->key) return find(root->lchild, key);return find(root->rchild, key);
}
        最终代码:

        

#include<stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>//定义节点
typedef struct Node {int key, h;struct Node *lchild, *rchild;
} Node;//定义一个全局变量__NIL,用来表示空节点
Node __NIL;
#define NIL (&__NIL)
#define K(n) (n->key)
#define H(n) (n->h)
#define L(n) (n->lchild)
#define R(n) (n->rchild)
//利用__attribute__((constructor)) 用来设置init_NIL函数先于main主函数执行
//用来初始化NIL节点
//NIL节点用来表示空节点,这是一个编码小技巧
__attribute__((constructor))
void init_NIL() {NIL->key = -1;NIL->h = 0;NIL->lchild = NIL->rchild = NIL;return ;
}//获取新节点的初始化操作
Node *getNewNode(int key) {Node *p = (Node *)malloc(sizeof(Node));p->key = key;p->h = 1;p->lchild = p->rchild = NIL;return p;
}//更新高度
void update_height(Node *root) {//当前节点的高度等于左右子树最高的高度+1H(root) = (H(L(root)) > H(R(root)) ? H(L(root)) : H(R(root))) + 1;return ;
}//左旋操作
Node *left_rotate(Node *root) {//左旋操作后//当前节点的右孩子应该在当前节点的位置//让后当前节点变为了,他右孩子的左子树//然后当前节点的右孩子的左子树指向了当前节点//所以当前节点的右子树应该去指向它之前的右孩子的左子树printf("left rotate : %d\n", root->key);//创建一个节点用来记录当前节点的右孩子Node *new_node = root->rchild;//那么当前节点的右子树就可以指向它之前的右孩子的左子树root->rchild = new_node->lchild;//右孩子的左子树指向了当前节点,那么右孩子就通过左旋操作//到了当前节点的位置上了//也就完成了左旋操作new_node->lchild = root;//然后更新当前节点的高度update_height(root);//更新右孩子的高度update_height(new_node);//其他节点的高度是没有改变的//因为右孩子替代了当前节点的位置//那么就返回右孩子return new_node;
}//右旋操作
Node *right_rotate(Node *root) {//右旋操作同理反过来思考就可以了printf("right rotate : %d\n", root->key);Node *new_node = root->lchild;root->lchild = new_node->rchild;new_node->rchild = root;update_height(root);update_height(new_node);return new_node;
}const char *type_str[5] = {"","maintain type : LL","maintain type : LR","maintain type : RR","maintain type : RL"
};//平衡操作,也就是判断是LL,RR或者LR和RL类型
//然后再进行左右旋后平衡二叉树
Node *maintain(Node *root) {//判断当前子树是否左右子树平衡if (abs(H(L(root)) - H(R(root))) <= 1) return root;//这里的type只是起了最终演示的效果,用来进行debug和演示的int type = 0;//判断前面的字母,例如LR中的Lif (H(L(root)) > H(R(root))) {//进入到这里说明是L?类型//下面if判断就是是否是LR类型,如果是先进行左旋if (H(R(L(root))) > H(L(L(root)))) {//说明是LR类型,那么先对当前节点的左子树进行左旋变为LL类型//下面会一起对LL类型进行处理root->lchild = left_rotate(root->lchild);type += 1;}//因为通过上面的if判断后,如果是LR类型,已经通过左旋变为LL类型//如果是LL类型那么上面的if里面的代码段也不会执行//所以这里直接右旋处理LL类型,将当前节点进行平衡root = right_rotate(root);type += 1;} //这里就是前面字母是R类型了else {type = 2;//if判断是否是RL类型if (H(L(R(root))) > H(R(R(root)))) {//说明是RL类型,那么先将当前节点的右子树进行右旋root->rchild = right_rotate(root->rchild);type += 1;}//对于RR类型处理root = left_rotate(root);type += 1;}printf("%s\n", type_str[type]);//完成当前节点的平衡操作,返回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);//这是一个回溯的过程,所以是从下往上节点进行的下面的操作//更新每个节点的高度update_height(root);//返回平衡后的节点return maintain(root);
}Node *predecessor(Node *root) {Node *temp = root->lchild;while (temp->rchild != NIL) temp = temp->rchild;return temp;
}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) {Node *temp = root->lchild != NIL ? root->lchild : root->rchild;free(root);return temp;} else {Node *temp = predecessor(root);root->key = temp->key;root->lchild = erase(root->lchild, temp->key);}}//然后主要的就是更新高度和平衡操作update_height(root);return maintain(root);
}Node *find(Node *root, int key) {if (root == NIL) return NIL;if (root->key == key) return root;if (key < root->key) return find(root->lchild, key);return find(root->rchild, key);
}//清除操作
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", K(root), H(root), K(L(root)), K(R(root)));output(root->lchild);output(root->rchild);return ;
}int main() {srand(time(0));Node *root = NIL;int x;// insertwhile (~scanf("%d", &x)) {if (x == -1) break;printf("insert %d to avl tree\n", x);root = insert(root, x);output(root);}// erasewhile (~scanf("%d", &x)) {if (x == -1) break;printf("erase %d from avl tree\n", x);root = erase(root, x);output(root);}// findwhile (~scanf("%d", &x)) {if (x == -1) break;printf("find %d in avl : %d\n", x, find(root, x) != NIL);}return 0;
}

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

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

相关文章

# AI作画原理:生成对抗网络(GAN)的原理与应用

AI作画原理&#xff1a;生成对抗网络&#xff08;GAN&#xff09;的原理与应用 现在&#xff0c;AI已经可以画出非常逼真的图像了。那么&#xff0c;AI是怎么做到这一点的呢&#xff1f; 深度学习与生成对抗网络&#xff08;GAN&#xff09; AI画画的核心技术之一是深度学习&…

关于MySQL数据库和数据表的基础

目录 一. 数据库的基础SQL 1. 创建数据库 2. 查看当前有哪些数据库 3. 选中数据库 4. 删除数据库 5. 小结 二. 数据表的基础SQL 1. 创建数据表 2. 查看当前数据库中有哪些表 3. 查看指定表的详细情况(查看表的结构) 4. 删除表 5. 小结 一. 数据库的基础SQL 1. 创建…

史上最全排序算法整理!(1)

1.排序的概念及其应用 1.1排序的概念 排序是计算机内经常进行的一种操作&#xff0c;其目的是将一组“无序”的记录序列调整为“有序”的记录序列。分内部排序和外部排序&#xff0c;若整个排序过程不需要访问外存便能完成&#xff0c;则称此类排序问题为内部排序。反之&#…

实战解析:爬取音乐每日推荐歌单并自动分享

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、准备阶段 三、实战步骤 四、总结与展望 一、引言 在数字化时代&#xff0c…

Gartner发布评估威胁情报计划有效性指南:评估威胁情报有效性的四个步骤

许多组织都在努力实施 TI 并评估其价值。安全和风险管理领导者必须使用优先情报要求来评估其 TI 计划的有效性&#xff0c;并根据其组织战略完善该计划。 主要发现 尽管许多组织已将威胁情报 (TI) 纳入其安全计划&#xff0c;但他们很难评估其性能、成熟度以及在相关产品和服务…

糖尿病视网膜病变分级新方法:卷积网络做分割和诊断 + 大模型生成详细的测试和治疗建议

糖尿病视网膜病变分级新方法&#xff1a;卷积网络做分割和诊断 大模型生成详细的测试和治疗建议 提出背景相关工作3.1 数据集3.1.1 病变分割 3.1.2 图像分级3.1.3 大型语言模型&#xff08;LLMs&#xff09; 解法 数据预处理 数据增强 网络架构 训练过程 测试过程子解法1…

【408真题】2009-25

“接”是针对题目进行必要的分析&#xff0c;比较简略&#xff1b; “化”是对题目中所涉及到的知识点进行详细解释&#xff1b; “发”是对此题型的解题套路总结&#xff0c;并结合历年真题或者典型例题进行运用。 涉及到的知识全部来源于王道各科教材&#xff08;2025版&…

【数据结构与算法 | 队列篇】力扣102, 107

1. 力扣102 : 二叉树的层序遍历 (1). 题 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3]…

STM32FLASH闪存

文章目录 前言首先来回顾一下存储器映像FLASH简介闪存模块组织Flash基本结构&#xff08;关系&#xff09;图Flash解锁使用指针访问存储器FLASH操作Flash全擦除Flash页擦除Flash写入 选项字节选项字节操作选项字节擦除选项字节写入 器件电子签名注意闪存控制寄存器一览 前言 本…

[leetcode hot 150]第一百九十一题,位1的个数

题目&#xff1a; 编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中设置位的个数&#xff08;也被称为汉明重量&#xff09;。 这道题比较简单&#xff0c;直接对最后一位进行与1的与操作&#xff0c;然…

RTPS协议之Messages Module

目录 Messages ModuleType定义RTPS消息结构RTPS消息头子消息结构 RTPS消息接收者SubmessageElementsRTPS HeaderRTPS Submessages Messages Module RTPS Writer和RTPS Reader之间的交换数据的消息。 Type定义 TypePurposeProtocolId_tSubmessageFlagsub msg flagSubmessageK…

第16章-超声波跟随功能 基于STM32的三路超声波自动跟随小车 毕业设计 课程设计

第16章-超声波跟随功能 无PID跟随功能 //超声波跟随if(HC_SR04_Read() > 25){motorForward();//前进HAL_Delay(100);}if(HC_SR04_Read() < 20){motorBackward();//后退HAL_Delay(100);}PID跟随功能 在pid.c中定义一组PID参数 tPid pidFollow; //定距离跟随PIDpidFol…

越来越多的连锁企业选择开源连锁收银系统

连锁企业的收银系统作为其信息化的基础&#xff0c;随着运营的复杂化&#xff0c;越来越多的连锁企业选择开源连锁收银系统来满足其日常经营需要。商淘云为大家分享连锁企业选择开源连锁收银系统的三大原因&#xff0c;大家点赞收藏。 首先是灵活性和定制性强&#xff0c;连锁企…

网络故障与排除(一)

一、Router-ID冲突导致OSPF路由环路 路由器收到相同Router-ID的两台设备发送的LSA&#xff0c;所以查看路由表看到的OSPF缺省路由信息就会不断变动。而当C1的缺省路由从C2中学到&#xff0c;C2的缺省路由又从C1中学到时&#xff0c;就形成了路由环路&#xff0c;因此出现路由不…

登录安全分析报告:小米官网注册

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …

重学java 51.Collections集合工具类、泛型

"我已不在地坛&#xff0c;地坛在我" —— 《想念地坛》 24.5.28 一、Collections集合工具类 1.概述:集合工具类 2.特点: a.构造私有 b.方法都是静态的 3.使用:类名直接调用 4.方法: static <T> boolean addAll(collection<? super T>c,T... el…

Nginx教程(持续更新中~)

浏览器优先查看host文件中的映射&#xff0c;如果host中没有就会从网上CDN找该域名对应的ip,但是目前使用的www.123.com是外卖假设的&#xff0c;CDN中并没有&#xff0c;所以就采用host中填写 第二种weight: 第三种 ip_hash: 第四种 fair: ​​​​​​

常见webshell工具及特征分析

前言 在工作中经常会遇到各种websehll&#xff0c;黑客通常要通过各种方式获取 webshell&#xff0c;从而获得企业网站的控制权&#xff0c;识别出webshell文件或通信流量可以有效地阻止黑客进一步的攻击行为&#xff0c;下面以常见的四款webshell进行分析&#xff0c;对工具连…

检测头篇 | YOLOv8改进之添加小目标检测头 / 添加大目标检测头 / 减少检测头

前言:Hello大家好,我是小哥谈。本文首先给大家展示原始YOLOv8的网络结构图,然后再对其进行改变,即增加小目标检测头、增加大目标检测头和减少检测头。🌈 目录 🚀1.网络结构图

金融行业专题|超融合对国密卡和国产加密技术的支持能力如何?

目前&#xff0c;不少金融机构都使用国密卡&#xff08;满足国密算法要求的加密卡&#xff09;和国产密码解决方案保障金融信息安全。而在传统虚拟化架构下&#xff0c;单块加密卡通常只能服务一个系统&#xff0c;经常会出现资源利用率低、加密处理性能不足等问题&#xff0c;…