二叉搜索树再升级——红黑树

二叉搜索树再升级——红黑树

  • 红黑树的概念
  • 红黑树的插入
  • uncle为granfather的右孩子
    • uncle结点为红色
    • uncle结点为空或黑色
  • uncle为granfather的左孩子

红黑树的概念

之前我们学习了AVL树,这是一种判断左右子树高度来严格控制总体树的高度的树。条件相对来说比较苛刻。我们今天要学习的红黑树,它不是通过左右子树高度来控制总体高度,而是通过结点的颜色来控制树的高度。

从字面意思上来说,红黑树,就是树的结点要么是黑的,要么是红的。
在这里插入图片描述但是这个红黑树的红色结点和黑色结点不是想怎么排就怎么排,红黑结点的插入顺序是有讲究的:

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上均包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

其中2,3是控制树的高度的手段。我们来看第2条,第2条说白了就是:不能出现连续的红色结点。
在这里插入图片描述
对于第3条,我们研究两种极端情况:
在这里插入图片描述对于我们来说,左边这种全部是黑色结点的路径为最短路径,右边这种一黑一红的路径我们称为最长路径。如果我们以每条路径上的黑色结点作为我们的路径长度,那么我们我们一棵树每一条的路径长度都应该在N~2N之间。这样保证了树的高度。
这里注意第5点,这里的叶子结点指的是空节点,空节点是黑色。
在这里插入图片描述

红黑树的插入

了解完红黑树的基本概念之后,我们来上手写一下红黑树,我们先把红黑树的结点封装好:

//红黑树的颜色因子
enum Color
{RED,BLACK
};//红黑树结点封装
template <class K,class V>
struct RBTreeNode
{RBTreeNode<K, V>* _leftchild;RBTreeNode<K, V>* _rightchild;RBTreeNode<K, V>* _parent;Color _col; //颜色因子pair<K, V> _kv;RBTreeNode(const pair<K,V>& kv):_leftchild(nullptr),_rightchild(nullptr),_parent(nullptr),_col(RED),_kv(kv){}
};

我们也来看看红黑树的插入:首先红黑树是搜索二叉树,插入的逻辑和之前的搜索二叉树的逻辑是一样的。

//红黑树
template <class K,class V>
class RBTree
{typedef RBTreeNode<K, V> _Node;
private:bool Insert(const pair<K,V>& kv){if (_root == nullptr){_root = new _Node(kv);return true;}_Node* parent = nullptr;_Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_rightchild;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_leftchild;}else{return false;}}cur = new _Node(kv);if (cur->_kv.first < parent->_kv.first){parent->_leftchild = cur;cur->_parent = parent;}else if (cur->_kv.first > parent->_kv.first){parent->_rightchild = cur;cur->_parent = parent;}//失衡,调整while (){}}private:_Node* _root = nullptr;
};

那么问题来了,我们是插入黑色结点好呢?还是红色结点好呢?
这里注意一下,如果直接插入黑色结点,可能会直接导致某一条路径上的黑色结点直接加一,直接导致整棵树失衡。所以这是一种不明智的做法。
所以明智的做法就是,我们插入红色结点,之后如果有失衡,还有回旋的余地

我们有两种情况要考虑:
第一种:插入结点的父亲结点是黑色
在这里插入图片描述

这个时候我们可以完全不用管,因为我们并没有改变任何一条黑色路径上的黑色结点的数量,整棵树在整体上还是平衡的。

第二种:插入结点的父亲结点是红色
在这里插入图片描述

这个时候,违反了规则3,不能出现连续的红色结点,这个时候该怎么办呢?此时我们知道7这个结点是一定要变成黑色的:
在这里插入图片描述
这个时候7这条路径上的黑色结点莫名奇妙多了一个,为了使各路黑色结点均衡,我们将6这个结点变成红色:
在这里插入图片描述这个时候5这条的黑色结点又会莫名其妙少一个,这个时候我们可以将5这个结点变成黑色:
在这里插入图片描述
这样是不是就好啦?
我们上面只是做了一个简单的分析,我们现在来认真分析一下:

uncle为granfather的右孩子

uncle结点为红色

我们这里把每个结点标好名字:约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
在这里插入图片描述当uncle结点为红色,parent结点为红色时,这时候我们这时候有连续的红色结点,这个时候我们将一层一层的变色,直到结点为根结点,根节点为黑色截止。

我们再来解释一下为什么直到根节点变色才结束:
如果此时的g为根节点:
在这里插入图片描述
这个时候虽然个没有变色,但是g为根节点,就代表每一条路径都加了一个黑色结点,每条路径上的黑色结点数量都是一样的。

如果此时的g不为根结点,而是一棵子树,就没法保证每一条路径上都增加一个黑色结点,所以这个时候变成红色结点是最保险的。
在这里插入图片描述
为了让情况变简单,我们在变色结束的时候,直接对根节点进行变黑处理:

		//出现连续红色结点,失衡,调整while (parent && parent->_col == RED){//记录祖父//    g//  p   u//c_Node* grandfather = parent->_parent;//找叔叔结点if (grandfather->_leftchild == parent){_Node* uncle = grandfather->_rightchild;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//向上迭代cur = grandfather;parent = cur->_parent;}}}_root->_col = BLACK;return true;

uncle结点为空或黑色

此时还有第二种情况:如果uncle结点为空,或者uncle结点为黑色。
我们先来看uncle结点为空的情况:
在这里插入图片描述

这个时候,我们发现单纯的变色已经解决不了问题了,这个时候,我们的想办法让这棵树平衡一点。诶?我们之前学习AVL树的时候,学习过如何让一棵树平衡,以上面的树为例,以g的视角来看,我是左子树高,所以我要进行左左旋:
在这里插入图片描述
这个时候高度平衡一些了,这个时候我们要处理的就是颜色问题,因为这个时候就只有颜色不平衡了,很简单,变色就是。
在这里插入图片描述
第一种情况我们已经讨论了,我们现在来看第二种情况,uncle结点为黑。

在这里插入图片描述
这个也是,树不平衡,首先进行旋转:
在这里插入图片描述这个时候进变色处理:
在这里插入图片描述可能这个图还是有点抽象,我们看第一棵子树,我们考虑一下:这棵树原本就是这样的吗?
在这里插入图片描述如果这棵树原本就是这样的,那么,左边的黑色结点树比右边的黑色结点树要少1,肯定是不平衡的,那说明一个问题,这棵树的状态肯定还在变化之中。那么说明这棵树一开始的状态是这样的:

在这里插入图片描述
然后变色变上去:
在这里插入图片描述
这里注意一下,如果这棵树为AVL树其实从p开始就要开始调整了,因为这时候树的高度已经失衡了。但是红黑树是依据每条路径上黑色结点的数量来判定的。这时候黑色结点的数量是满足要求,但是因为不能出现连续的红色结点,所以我们以g开始旋转:

在这里插入图片描述
这个时候变色:
在这里插入图片描述其中注意,为了保持上面的图是平衡的红黑树,所以c为带一个黑结点的红黑树,d和c要么为空,要么为一个红色结点

了解完基本的思路之后,我们开始来写代码:

			//找叔叔结点if (grandfather->_leftchild == parent){_Node* uncle = grandfather->_rightchild;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//向上迭代cur = grandfather;parent = cur->_parent;}else if (!uncle || uncle->_col == BLACK){//旋转+变色//    g//  p//cif (cur == parent->_leftchild){//进行左左旋RoateLL(grandfather); //AVL树中的左左旋parent->_col = BLACK;grandfather->_col = RED;}}

那我们在AVL树中有双旋,那么在红黑树中有双旋吗?答案是有的,这就是我们的第三种情况:
第三种情况:
在这里插入图片描述这种情况我们先进行一个右右旋,在进行一个左左旋就好啦!
在这里插入图片描述
在这里插入图片描述

			//找叔叔结点if (grandfather->_leftchild == parent){_Node* uncle = grandfather->_rightchild;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//向上迭代cur = grandfather;parent = cur->_parent;}else if (!uncle || uncle->_col == BLACK){//旋转+变色//    g//  p//cif (cur == parent->_leftchild){//进行左左旋RoateLL(grandfather); //AVL树中的左左旋parent->_col = BLACK;grandfather->_col = RED;}else if (cur == parent->_leftchild)//    g//  p//     c{RoateRR(parent);RoateLL(grandfather);cur->_col = BLACK;grandfather = RED;}break; //黑色为终结态,变色完之后跳出}

到目前为止,我们只分析了parent为grandfather的左孩子的情况,现在我们来讨论一下parent为grandfather右孩子的情况。

uncle为granfather的左孩子

我们来看具体的图片:

在这里插入图片描述
这个我们对照着uncle为右孩子的方式来看,上图的话就单纯的变色就好了:
在这里插入图片描述当uncle结点为空或者为黑的时候。
在这里插入图片描述在这里插入图片描述

			else if (grandfather->_rightchild == parent){//   g// u   p//       c_Node* uncle = grandfather->_leftchild;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = RED;grandfather->_col = BLACK;cur = grandfather;parent = cur ->_parent;}else if (!uncle || uncle->_col == BLACK){//  g//    p//      cif (cur == parent->_rightchild){//进行右右旋RoateRR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//   g         //  g//     p       //    c//   c         //      pelse if (cur == parent->_leftchild){//进行左左旋RoateLL(parent);//进行右右旋RoateRR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}	

附上完整的插入代码:

	bool Insert(const pair<K,V>& kv){if (_root == nullptr){_root = new _Node(kv);_root->_col = BLACK;return true;}_Node* parent = nullptr;_Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_rightchild;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_leftchild;}else{return false;}}cur = new _Node(kv);if (cur->_kv.first < parent->_kv.first){parent->_leftchild = cur;cur->_parent = parent;}else if (cur->_kv.first > parent->_kv.first){parent->_rightchild = cur;cur->_parent = parent;}//出现连续红色结点,失衡,调整while (parent && parent->_col == RED){//记录祖父//    g//  p   u//c_Node* grandfather = parent->_parent;//找叔叔结点if (grandfather->_leftchild == parent){_Node* uncle = grandfather->_rightchild;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//向上迭代cur = grandfather;parent = cur->_parent;}else if (!uncle || uncle->_col == BLACK){//旋转+变色//    g//  p//cif (cur == parent->_leftchild){//进行左左旋RoateLL(grandfather); //AVL树中的左左旋parent->_col = BLACK;grandfather->_col = RED;}else if (cur == parent->_rightchild)//    g//  p//     c{RoateRR(parent);RoateLL(grandfather);cur->_col = BLACK;grandfather ->_col= RED;}break; //黑色为终结态,变色完之后跳出}}else if (grandfather->_rightchild == parent){//   g// u   p//       c_Node* uncle = grandfather->_leftchild;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = RED;grandfather->_col = BLACK;cur = grandfather;parent = cur ->_parent;}else if (!uncle || uncle->_col == BLACK){//  g//    p//      cif (cur == parent->_rightchild){//进行右右旋RoateRR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//   g         //  g//     p       //    c//   c         //      pelse if (cur == parent->_leftchild){//进行左左旋RoateLL(parent);//进行右右旋RoateRR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}	}_root->_col = BLACK;return true;}void RoateLL(_Node* parent) //左左旋//   p// subL//cur{_Node* subL = parent->_leftchild;_Node* subLR = subL->_rightchild;//改变指向subL->_rightchild = parent;parent->_leftchild = subLR;if (subLR)subLR->_parent = parent;//记录爷爷结点_Node* grandfather = parent->_parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (grandfather->_leftchild == parent){grandfather->_leftchild = subL;}else if (grandfather->_rightchild == parent){grandfather->_rightchild = subL;}subL->_parent = grandfather;}}void RoateRR(_Node* parent) //右右旋{_Node* subR = parent->_rightchild;_Node* subRL = subR->_leftchild;//改变指向subR->_leftchild = parent;parent->_rightchild = subRL;if (subRL)subRL->_parent = parent;//记录祖父_Node* grandfather = parent->_parent;parent->_parent = subR;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (grandfather->_leftchild == parent){grandfather->_leftchild = subR;}else if (grandfather->_rightchild == parent){grandfather->_rightchild = subR;}subR->_parent = grandfather;}}

我们可以用大量的数据简单测试一下:

int main()
{const int N = 10000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand());//cout << v.back() << endl;}RBTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));//cout << "Insert:" << e << "->" << t.IsBalance() << endl;}t.InOrder();
}

在这里插入图片描述但是这样只能保证这棵树是二叉搜索树,但如何证明这棵树是一棵平衡的红黑树呢?我么可以统计一条路径上的黑色结点数量,然后对比每一条路径的黑色结点是否相等。

	bool Check(_Node* node,int blacknum,const int refVal){if (node == nullptr){if (refVal != blacknum){return false;}cout << "黑色结点的数量:" << blacknum << endl;return true;}//检查是否有连续的红色结点if (node->_col == RED){if (node->_parent->_col == RED){cout << "有连续的红色结点" << endl;return false;}}//黑色结点if (node->_col == BLACK){++blacknum;}return Check(node->_leftchild, blacknum, refVal) &&Check(node->_rightchild, blacknum, refVal);}bool isTrue(){if (_root == nullptr)return true;if (_root->_col == RED)return false;记录容器//vector<int> v;//Check(_root,0,v);//int first = v[0];//int total = 0;//for (auto e : v)//{//	total += e;//}//if (total != first * v.size())//{//	cout << "此树不平衡" << endl;//	return false;//}//else//{//	return true;//}int blacknum = 0; //先统计一条路径上的黑色结点数量int refVal = 0;_Node* cur = _root;while (cur){if (cur->_col == BLACK)refVal++;cur = cur->_leftchild;}return Check(_root, blacknum,refVal);}

在这里插入图片描述
附上源码:

#pragma once
#include<iostream>
#include<vector>
using namespace std;//红黑树的颜色因子
enum Color
{RED,BLACK
};//红黑树结点封装
template <class K,class V>
struct RBTreeNode
{RBTreeNode<K, V>* _leftchild;RBTreeNode<K, V>* _rightchild;RBTreeNode<K, V>* _parent;Color _col; //颜色因子pair<K, V> _kv;RBTreeNode(const pair<K,V>& kv):_leftchild(nullptr),_rightchild(nullptr),_parent(nullptr),_col(RED),_kv(kv){}
};//红黑树
template <class K,class V>
class RBTree
{typedef RBTreeNode<K, V> _Node;
public:bool Insert(const pair<K,V>& kv){if (_root == nullptr){_root = new _Node(kv);_root->_col = BLACK;return true;}_Node* parent = nullptr;_Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_rightchild;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_leftchild;}else{return false;}}cur = new _Node(kv);if (cur->_kv.first < parent->_kv.first){parent->_leftchild = cur;cur->_parent = parent;}else if (cur->_kv.first > parent->_kv.first){parent->_rightchild = cur;cur->_parent = parent;}//if (cur == parent->_leftchild)//{//	parent->_leftchild = cur;//	cur->_parent = parent;//}//else if (cur == parent->_rightchild)//{//	parent->_rightchild = cur;//	cur->_parent = parent;//}//出现连续红色结点,失衡,调整while (parent && parent->_col == RED){//记录祖父//    g//  p   u//c_Node* grandfather = parent->_parent;//找叔叔结点if (grandfather->_leftchild == parent){_Node* uncle = grandfather->_rightchild;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//向上迭代cur = grandfather;parent = cur->_parent;}else if (!uncle || uncle->_col == BLACK){//旋转+变色//    g//  p//cif (cur == parent->_leftchild){//进行左左旋RoateLL(grandfather); //AVL树中的左左旋parent->_col = BLACK;grandfather->_col = RED;}else if (cur == parent->_rightchild)//    g//  p//     c{RoateRR(parent);RoateLL(grandfather);cur->_col = BLACK;grandfather ->_col= RED;}break; //黑色为终结态,变色完之后跳出}}else if (grandfather->_rightchild == parent){//   g// u   p//       c_Node* uncle = grandfather->_leftchild;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = RED;grandfather->_col = BLACK;cur = grandfather;parent = cur ->_parent;}else if (!uncle || uncle->_col == BLACK){//  g//    p//      cif (cur == parent->_rightchild){//进行右右旋RoateRR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//   g         //  g//     p       //    c//   c         //      pelse if (cur == parent->_leftchild){//进行左左旋RoateLL(parent);//进行右右旋RoateRR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}	}_root->_col = BLACK;return true;}void RoateLL(_Node* parent) //左左旋//   p// subL//cur{_Node* subL = parent->_leftchild;_Node* subLR = subL->_rightchild;//改变指向subL->_rightchild = parent;parent->_leftchild = subLR;if (subLR)subLR->_parent = parent;//记录爷爷结点_Node* grandfather = parent->_parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (grandfather->_leftchild == parent){grandfather->_leftchild = subL;}else if (grandfather->_rightchild == parent){grandfather->_rightchild = subL;}subL->_parent = grandfather;}}void RoateRR(_Node* parent) //右右旋{_Node* subR = parent->_rightchild;_Node* subRL = subR->_leftchild;//改变指向subR->_leftchild = parent;parent->_rightchild = subRL;if (subRL)subRL->_parent = parent;//记录祖父_Node* grandfather = parent->_parent;parent->_parent = subR;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (grandfather->_leftchild == parent){grandfather->_leftchild = subR;}else if (grandfather->_rightchild == parent){grandfather->_rightchild = subR;}subR->_parent = grandfather;}}void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(_Node* root){if (root == nullptr)return;_InOrder(root->_leftchild);cout << root->_kv.first << endl;_InOrder(root->_rightchild);}bool Check(_Node* node, int blacknum, const int refVal){if (node == nullptr){if (refVal != blacknum){return false;}cout << "黑色结点的数量:" << blacknum << endl;return true;}//检查是否有连续的红色结点if (node->_col == RED){if (node->_parent->_col == RED){cout << "有连续的红色结点" << endl;return false;}}//黑色结点if (node->_col == BLACK){++blacknum;}return Check(node->_leftchild, blacknum, refVal) &&Check(node->_rightchild, blacknum, refVal);}bool isTrue(){if (_root == nullptr)return true;if (_root->_col == RED)return false;记录容器//vector<int> v;//Check(_root,0,v);//int first = v[0];//int total = 0;//for (auto e : v)//{//	total += e;//}//if (total != first * v.size())//{//	cout << "此树不平衡" << endl;//	return false;//}//else//{//	return true;//}int blacknum = 0;int refVal = 0;_Node* cur = _root;while (cur){if (cur->_col == BLACK)refVal++;cur = cur->_leftchild;}return Check(_root, blacknum, refVal);}private:_Node* _root = nullptr;
};
#define _CRT_SECURE_NO_WARNINGS 1
#include"RBTree.h"int main()
{const int N = 10000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand());//cout << v.back() << endl;}RBTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));//cout << "Insert:" << e << "->" << t.IsBalance() << endl;}t.InOrder();t.isTrue();
}

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

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

相关文章

力扣OJ题讲解——循环队列

今天我们一起来做一道关于队列的OJ题目&#xff0c;这是力扣题目622题&#xff0c;点击题目链接可以直接跳转&#xff0c;https://leetcode.cn/problems/design-circular-queue/ 首先&#xff0c;我们看到要求&#xff0c;需要我们实现哪些功能&#xff1f; 我们需要设置队列长…

一起学docker系列之七docker容器卷技术

目录 1 为什么使用容器数据卷&#xff1f;2 数据卷的特点和优势3 使用数据卷的方法3.1 创建容器并挂载数据卷3.2 容器间数据卷的共享与继承 4 数据卷的权限设置5 注意事项5.1 解决权限问题5.2 路径自动创建 结语 对于容器化应用程序的数据管理和持久化&#xff0c;Docker 数据卷…

酷开科技 | 大胆解锁酷开系统,收获绝佳观影体验

在当今数字化时代&#xff0c;家庭娱乐的方式越来越多样化。其中&#xff0c;看电影无疑是很受欢迎的一种。而酷开科技&#xff0c;作为一家专注于智能电视领域的科技公司&#xff0c;其自主研发的智能电视操作系统——酷开系统&#xff0c;就在看电影方面为消费者带来了更精彩…

Linux反弹SHell与检测思路

免责声明 文章仅做经验分享用途,利用本文章所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任,一旦造成后果请自行承担!!! 反弹shell payload在线生成 https://www.chinabaiker.com/Hack-Tools/ Online - Reverse Shell G…

Qt 软件开发框架(主要几个部分)

目录 1、 一个软件基本要素 &#xff08;1&#xff09;UI模块 &#xff08;2&#xff09;网络模块 &#xff08;3&#xff09;业务逻辑模块 &#xff08;4&#xff09;中间层 &#xff08;5&#xff09;独立模块&#xff08;守护进程、更新模块、日志收集模块…&#xff…

梦开始的地方——Adobe Premiere Pro

今天&#xff0c;我们来说说一款老生常谈的相信也是很多人都经常迫切需要的软件。Adobe Premiere Pro&#xff0c;简称Pr&#xff0c;是由Adobe公司开发的一款视频编辑软件。 Premiere Pro是视频编辑爱好者和专业人士必不可少的视频编辑工具。它可以提升您的创作能力和创作自由…

企业软件定制开发有哪些优势?|app小程序网站搭建

企业软件定制开发有哪些优势&#xff1f;|app小程序网站搭建 企业软件定制开发是一种根据企业特定需求开发定制化软件的服务。相比于购买现成的软件产品&#xff0c;企业软件定制开发具有许多优势。 首先&#xff0c;企业软件定制开发可以满足企业独特需求。每个企业都有自己独…

virtualList 封装使用 虚拟列表 列表优化

虚拟列表 列表优化 virtualList 组件封装 virtualList 组件封装 本虚拟列表 要求一次性加载完所有数据 不适合分页 新建一个select.vue 组件页面 <template><div> <el-select transfer"true" :popper-append-to-body"true"popper-class…

第十七章:数据库操作

数据库基础 SQL语言 1、select 语句 select 语句用于从数据中检索数据。语法如下&#xff1a; SELECT 搜选字段列表 FROM 数据表名 WHERE 条件表达式 GROUP BY 字段名 HAVING 条件表达式(指定分组的条件) ORDER BY 字段名[ASC|DESC] 2、insert 语句 insert 语句用于向表中插入…

[UE4][C++]基于UUserWidget的一种序列图播放方法

最近在做一个大项目&#xff0c;鸽了几个月了....... 一、传统方法Flipbook 这种方法适合序列图较少的情况下、可以一个一个添加进来然后调整顺序。蓝图也比较友好可以直接设置很多属性和功能。这里简单了解一下即可&#xff0c;想要深入了解的同学可以自行搜索。 1.1创建Fli…

数据库数据恢复—SQLserver数据库中勒索病毒被加密的数据恢复案例

SQLserver数据库数据恢复环境&故障&#xff1a; 一台服务器上的SQLserver数据库被勒索病毒加密&#xff0c;无法正常使用。该服务器上部署有多个SQLserver数据库&#xff0c;其中有2个数据库及备份文件被加密&#xff0c;文件名被篡改&#xff0c;数据库无法使用。 SQL se…

如何将音频添加到视频并替换视频中的音轨

随着视频流媒体网站的流行和便携式设备的发展&#xff0c;你可能越来越倾向于自己制作视频并在互联网上分享。有时&#xff0c;你可能还需要编辑视频并为其添加背景音乐&#xff0c;因为音乐总是对视频的感知起着神奇的作用。 那如何给视频添加音频呢&#xff1f;或者如何用新…