【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——红黑树

目录

1 -> 红黑树

1.1 -> 红黑树的概念

1.2 -> 红黑树的性质

1.3 -> 红黑树节点的定义

1.4 -> 红黑树的结构

1.5 -> 红黑树的插入操作

1.6 -> 红黑树的验证

1.8 -> 红黑树与AVL树的比较

2 -> 红黑树模拟实现STL中的map与set

2.1 -> 红黑树的迭代器

2.2 -> 改造红黑树

2.3 -> map的模拟实现

2.4 -> set的模拟实现


1 -> 红黑树

1.1 -> 红黑树的概念

红黑树,是一种二叉搜索树,但在每个节点上增加了一个存储位表示节点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。

1.2 -> 红黑树的性质

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

1.3 -> 红黑树节点的定义

#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
using namespace std;// 节点的颜色
enum Color 
{ RED, BLACK 
};// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{RBTreeNode(const ValueType& data = ValueType(),Color color = RED): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _color(color){}RBTreeNode<ValueType>* _pLeft;   // 节点的左孩子RBTreeNode<ValueType>* _pRight;  // 节点的右孩子RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)ValueType _data; // 节点的值域Color _color;    // 节点的颜色
};

1.4 -> 红黑树的结构

为了后续实现关联式容器更加简单,红黑树的实现中增加一个头节点,因为根节点必须是黑色的,为了与根节点区分开,将头节点给成黑色,并且让头节点的pParent域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点。

1.5 -> 红黑树的插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可以分为两步:

1. 按照二叉搜索树的树规则插入新节点。

template<class ValueType>
struct RBTree
{bool Insert(const ValueType& data){PNode& pRoot = GetRoot();if (nullptr == pRoot){pRoot = new Node(data, BLACK);// 根的双亲为头节点pRoot->_pParent = _pHead;_pHead->_pParent = pRoot;}else{// 1. 按照二叉搜索的树方式插入新节点// 2. 检测新节点插入后,红黑树的性质是否造到破坏,//    若满足直接退出,否则对红黑树进行旋转着色处理}// 根节点的颜色可能被修改,将其改回黑色pRoot->_color = BLACK;_pHead->_pLeft = LeftMost();_pHead->_pRight = RightMost();return true;}
private:PNode& GetRoot(){return _pHead->_pParent;}// 获取红黑树中最小节点,即最左侧节点PNode LeftMost();// 获取红黑树中最大节点,即最右侧节点PNode RightMost();private:PNode _pHead;
}

2. 检测新节点插入后,红黑树的性质是否遭到破坏。

因为新节点的默认颜色为红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树的任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三,即不能有连在一起的红色节点,此时需要对红黑树分情况来讨论: 

  • 情况一:cur为红,p为红,g为黑,u存在且为红。

注意:此处看到的树可能是一棵完整的树,也可能是一棵子树。

如果g是根节点,调整完成后,需要将g改为黑色。

如果g是子树,g一定有双亲,且g的双亲如果是红色,就需要继续向上调整。 

cur和p均为红,违反了性质三。

解决方法:将p、u改为黑,g改为红,然后把g当成cur,继续向上调整。 

  • 情况二:cur为红,p为红,g为黑,u不存在/u存在且为黑。

 说明:

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

p为g的左孩子,cur为p的左孩子,则进行右单旋转。

p为g的右孩子,cur为p的右孩子,则进行左单旋转。

p、g变色——p变黑,g变红。

  • 情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑。

 

p为g的左孩子,cur为p的右孩子,则针对p进行左单旋转。

p为g的右孩子,cur为p的左孩子,则针对p进行右单旋转。

则转换成情况二。

针对每种情况进行相应的处理即可。

bool Insert(const ValueType& data)
{// ...// 新节点插入后,如果其双亲节点的颜色为空色,则违反性质3:不能有连在一起的红色结点while (pParent && RED == pParent->_color){// 注意:grandFather一定存在// 因为pParent存在,且不是黑色节点,则pParent一定不是根,则其一定有双亲PNode grandFather = pParent->_pParent;// 先讨论左侧情况if (pParent == grandFather->_pLeft){PNode unclue = grandFather->_pRight;// 情况三:叔叔节点存在,且为红if (unclue && RED == unclue->_color){pParent->_color = BLACK;unclue->_color = BLACK;grandFather->_color = RED;pCur = grandFather;pParent = pCur->_pParent;}else{// 情况五:叔叔节点不存在,或者叔叔节点存在且为黑if (pCur == pParent->_pRight){_RotateLeft(pParent);swap(pParent, pCur);}// 情况五最后转化成情况四grandFather->_color = RED;pParent->_color = BLACK;_RotateRight(grandFather);}}else{// …}}// ...
}

1.6 -> 红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)。
  2. 检测其是否满足红黑树的性质。
bool IsValidRBTree(){PNode pRoot = GetRoot();// 空树也是红黑树if (nullptr == pRoot)return true;// 检测根节点是否满足情况if (BLACK != pRoot->_color){cout << "违反红黑树性质二:根节点必须为黑色" << endl;return false;}// 获取任意一条路径中黑色节点的个数size_t blackCount = 0;PNode pCur = pRoot;while (pCur){if (BLACK == pCur->_color)blackCount++;pCur = pCur->_pLeft;}// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数size_t k = 0;return _IsValidRBTree(pRoot, k, blackCount);}bool _IsValidRBTree(PNode pRoot, size_t k, const size_t blackCount){//走到null之后,判断k和black是否相等if (nullptr == pRoot){if (k != blackCount){cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;return false;}return true;}// 统计黑色节点的个数if (BLACK == pRoot->_color)k++;// 检测当前节点与其双亲是否都为红色PNode pParent = pRoot->_pParent;if (pParent && RED == pParent->_color && RED == pRoot->_color){cout << "违反性质三:没有连在一起的红色节点" << endl;return false;}return _IsValidRBTree(pRoot->_pLeft, k, blackCount) &&_IsValidRBTree(pRoot->_pRight, k, blackCount);}

1.8 -> 红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(n),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以在实际运用中红黑树更多。

2 -> 红黑树模拟实现STL中的map与set

2.1 -> 红黑树的迭代器

迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代器,需要考虑以下问题:

  • begin()和end()

STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪里呢?能否给成nullptr呢?

答案是行不通的,因为对end()位置的迭代器进行--操作,必须要能找到最后一个元素,此处就不行,因此最好的方式是将end()放在头节点的位置

  • operator++()与operator--() 
// 找迭代器的下一个节点,下一个节点肯定比其大void Increasement(){//分两种情况讨论:_pNode的右子树存在和不存在// 右子树存在if (_pNode->_pRight){// 右子树中最小的节点,即右子树中最左侧节点_pNode = _pNode->_pRight;while (_pNode->_pLeft)_pNode = _pNode->_pLeft;}else{// 右子树不存在,向上查找,直到_pNode != pParent->rightPNode pParent = _pNode->_pParent;while (pParent->_pRight == _pNode){_pNode = pParent;pParent = _pNode->_pParent;}// 特殊情况:根节点没有右子树if (_pNode->_pRight != pParent)_pNode = pParent;}}// 获取迭代器指向节点的前一个节点void Decreasement(){//分三种情况讨论:_pNode 在head的位置,_pNode 左子树存在,_pNode 左子树不存在// 1. _pNode 在head的位置,--应该将_pNode放在红黑树中最大节点的位置if (_pNode->_pParent->_pParent == _pNode && _pNode->_color == RED)_pNode = _pNode->_pRight;else if (_pNode->_pLeft){// 2. _pNode的左子树存在,在左子树中找最大的节点,即左子树中最右侧节点_pNode = _pNode->_pLeft;while (_pNode->_pRight)_pNode = _pNode->_pRight;}else{// _pNode的左子树不存在,只能向上找PNode pParent = _pNode->_pParent;while (_pNode == pParent->_pLeft){_pNode = pParent;pParent = _pNode->_pParent;}_pNode = pParent;}}

2.2 -> 改造红黑树

#pragma once// set ->key
// map ->key/valueenum Colour
{RED,BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};template<class T>
struct __TreeIterator
{typedef RBTreeNode<T> Node;typedef __TreeIterator<T> Self;Node* _node;__TreeIterator(Node* node):_node(node){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}Self& operator--();Self& operator++(){if (_node->_right){// 下一个就是右子树的最左节点Node* cur = _node->_right;while (cur->_left){cur = cur->_left;}_node = cur;}else{// 左子树 根 右子树// 右为空,找孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
};// set->RBTree<K, K, SetKeyOfT> _t;
// map->RBTree<K, pair<K, T>, MapKeyOfT> _t;
template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef __TreeIterator<T> iterator;iterator begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){return iterator(nullptr);}pair<iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(iterator(_root), true);}Node* parent = nullptr;Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(cur), false);}}// 新增节点给红色cur = new Node(data);Node* newnode = cur;cur->_col = RED;if (kot(parent->_data) < kot(data)){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){//     g//   p   u// cNode* uncle = grandfather->_right;if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上更新处理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_left){// 单旋//     g//   p// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// 双旋//     g//   p//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else  // parent == grandfather->_right{//     g//   u   p //          c//Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//   u   p //     c//RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(iterator(newnode), true);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;Node* parentParent = parent->_parent;parent->_parent = subR;if (subRL)subRL->_parent = parent;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}// 根节点->当前节点这条路径的黑色节点的数量bool Check(Node* root, int blacknum, const int refVal){if (root == nullptr){//cout << balcknum << endl;if (blacknum != refVal){cout << "存在黑色节点数量不相等的路径" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "有连续的红色节点" << endl;return false;}if (root->_col == BLACK){++blacknum;}return Check(root->_left, blacknum, refVal)&& Check(root->_right, blacknum, refVal);}bool IsBalance(){if (_root == nullptr)return true;if (_root->_col == RED)return false;//参考值int refVal = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refVal;}cur = cur->_left;}int blacknum = 0;return Check(_root, blacknum, refVal);}int Height(){return _Height(_root);}int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}size_t Size(){return _Size(_root);}size_t _Size(Node* root){if (root == NULL)return 0;return _Size(root->_left)+ _Size(root->_right) + 1;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return NULL;}private:Node* _root = nullptr;
};

2.3 -> map的模拟实现

map的底层结构就是红黑树,因此在map中直接封装一棵红黑树,然后将其接口包装下即可。

#pragma once
#include"RBTree.h"namespace fyd
{template<class K, class V>class map{public:struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};// 对类模板取内嵌类型,加typename告诉编译器这里是类型typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}private:RBTree<K, pair<K, V>, MapKeyOfT> _t;};
}

2.4 -> set的模拟实现

set的底层为红黑树,因此只需在set内部封装一棵红黑树,即可将该容器实现出来。

#pragma once
#include"RBTree.h"namespace fyd
{template<class K>class set{public:struct SetKeyOfT{const K& operator()(const K& key){return key;}};typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}pair<iterator, bool> insert(const K& key){return _t.Insert(key);}private:RBTree<K, K, SetKeyOfT> _t;};
}

感谢各位大佬支持!!!

互三啦!!!

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

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

相关文章

【堆 优先队列】1354. 多次求和构造目标数组

本文涉及知识点 堆 优先队列 LeetCode1354. 多次求和构造目标数组 给你一个整数数组 target 。一开始&#xff0c;你有一个数组 A &#xff0c;它的所有元素均为 1 &#xff0c;你可以执行以下操作&#xff1a; 令 x 为你数组里所有元素的和 选择满足 0 < i < target.…

linux信息收集与提权

目录 版本信息收集 kali得一些exp网站 kali自带的searchsploit工具 脏牛提权漏洞&#xff08;改写没有写权限的文件&#xff09; 测试靶场下载链接 sudo提权 上传恶意C脚本进行编译生成dirty的elf文件&#xff0c;也可以在攻击机编译好上传 启动&#xff0c;123456是设…

体验完这款售价29999元起苹果新品,我大受震撼

讲道理&#xff0c;数码圈已经很久没有出现过让人耳目一新的产品了。 整个圈子近些年各家新品逻辑给我的一种感觉是普遍主打循规循距&#xff0c;用高情商话来说那叫稳扎稳打不易出错&#xff0c;而低情商嘛&#xff0c;说白了叫创新精神严重缺失。 「科技最后以换皮为准」这…

【CUDA】 Trust基本特性介绍及性能分析

Trust简介 Thrust 是一个实现了众多基本并行算法的 C 模板库,类似于 C 的标准模板库(standard template library, STL)。该库自动包含在 CUDA 工具箱中。这是一个模板库,仅仅由一些头文件组成。在使用该库的某个功能时,包含需要的头文件即可。该库中的所有类型与函数都在命名空…

【linux】 sudo apt update报错——‘由于没有公钥,无法验证下列签名: NO_PUBKEY 3B4FE6ACC0B21F32’

【linux】 sudo apt update报错——‘由于没有公钥&#xff0c;无法验证下列签名&#xff1a; NO_PUBKEY 3B4FE6ACC0B21F32’ 在运行sudo apt update时遇到报错&#xff0c;由于没有公钥&#xff0c;无法验证下列签名&#xff1a; NO_PUBKEY 3B4FE6ACC0B21F32 解决方法&#x…

Qt:11.输入类控件(QLineEdit-单行文本输入控件、QTextEdit-多行文本输入控件、QComboBox-下拉列表的控件)

一、QLineEdit-单行文本输入控件&#xff1a; 1.1QLineEdit介绍&#xff1a; QLineEdit 是 Qt 库中的一个单行文本输入控件&#xff0c;不能换行。允许用户输入和编辑单行文本。 1.2属性介绍&#xff1a; inputMask 设置输入掩码&#xff0c;以限定输入格式。setInputMask(con…

react学习——25redux实现求和案例(完整版)

1、目录结构 2、count/index.js import React, {Component} from "react"; //引入store,用于获取数据 import store from ../../redux/store //引入actionCreator 专门创建action对象 import {createDecrementAction,createIncrementAction} from ../../redux/coun…

CSS【详解】边框 border,边框-圆角 border-radius,边框-填充 border-image,轮廓 outline

边框 border border 是以下三种边框样式的简写&#xff1a; border-width 边框宽度 —— 数值 px&#xff08;像素&#xff09;,thin&#xff08;细&#xff09;,medium&#xff08;中等&#xff09;,thick&#xff08;粗&#xff09;border-style 边框线型 —— none【默认值…

78. UE5 RPG 创建技能数据并初始化技能ui

在上一篇文章里&#xff0c;我们创建了技能的UI&#xff0c;接下来&#xff0c;我们要考虑如何实现对技能UI的填充&#xff0c;肯定不能直接写死&#xff0c;需要有一些方法去实现技能的更新。我们期望能够创建一个技能数据&#xff0c;然后根据数据通过回调的方式实现数据的更…

一键掌握天气动态 - 基于Vue和高德API的实时天气查询

前言 本文将学习如何使用Vue.js快速搭建天气预报界面,了解如何调用高德地图API获取所需的天气数据,并掌握如何将两者有机结合,实现一个功能丰富、体验出色的天气预报应用 无论您是前端新手还是有一定经验,相信这篇教程都能为您带来收获。让我们一起开始这段精彩的Vue.js 高德…

桌面悬浮备忘录哪个好?能在桌面悬浮使用的备忘app

备忘录是我们日常工作和生活中的常用工具&#xff0c;它帮助我们记录重要信息&#xff0c;提醒我们完成各项任务。而将备忘录悬浮在桌面上使用&#xff0c;无疑能进一步提高我们的工作效率。想象一下&#xff0c;在处理复杂的工作任务时&#xff0c;你能够随时在桌面上查看提醒…

JS获取本机ip地址方法

前端获取本机ip地址&#xff1b;使用第三方免费API <script>function ipJson(ipJson) {console.log(获取到的网络IP,ipJson);//可以把结果存在window上&#xff0c;方便调用window.ipJson ipJson;} </script> <script src"https://whois.pconline.com.cn/…

产品使用手册深度剖析:五步快速敲定产品手册策划思路

引言 在这个信息爆炸的时代&#xff0c;产品使用手册不仅是产品的“说明书”&#xff0c;更是品牌与用户之间建立情感连接的桥梁。一份优秀的手册&#xff0c;能够迅速吸引用户的注意力&#xff0c;引导他们轻松上手&#xff0c;并深入体验产品的魅力。那么&#xff0c;如何撰…

ruoyi项目swagger文档升级knife4j文档

注释admin模块中的swagger依赖加入knife4j依赖 <!-- swagger3--> <!-- <dependency>--> <!-- <groupId>io.springfox</groupId>--> <!-- <artifactId>springfox-boot-starter</artifactId>--…

IDEA常用技巧荟萃:精通开发利器的艺术

1 概述 在现代软件开发的快节奏环境中&#xff0c;掌握一款高效且功能全面的集成开发环境&#xff08;IDE&#xff09;是提升个人和团队生产力的关键。IntelliJ IDEA&#xff0c;作为Java开发者的首选工具之一&#xff0c;不仅提供了丰富的编码辅助功能&#xff0c;还拥有高度…

预算有限?如何挑选经济适用的安全管理系统?

如今&#xff0c;无论是信息安全、生产安全还是人员安全&#xff0c;都直接关系到企业的稳定运营和长远发展。然而&#xff0c;对于许多中小企业而言&#xff0c;高昂的安全管理系统投入往往成为一大难题。那么&#xff0c;在预算有限的情况下&#xff0c;如何挑选一款既经济适…

Github 2024-07-07php开源项目日报 Top9

根据Github Trendings的统计,今日(2024-07-07统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目9Blade项目2JavaScript项目1Laravel:表达力和优雅的 Web 应用程序框架 创建周期:4631 天开发语言:PHP, BladeStar数量:75969 个Fork数…

如何整合生成的人工智能?(GenAI)为你未来的工作增加动力

生成人工智能(GenAI)它发展迅速&#xff0c;以前所未有的速度取得了突破。人工智能将继续改变各行各业&#xff0c;预计2023年至2030年的年增长率将达到37.3%。由于一种新的知识工作者现在面临被取代的风险&#xff0c;生成式人工智能的惊人崛起进一步加剧了这种紧迫性。据《未…

嘎嘎详细的三维变换详细讲解,包括视图变换、投影变换等,超级通俗易懂!

前置二维空间的各种变换笔记&#xff1a;二维变换 三维空间中的齐次坐标 从二维变换开始引申&#xff0c;可得到三维中的一个点的表达方式为 ( x , y , z , 1 ) ⊤ (\mathbf{x}, \mathbf{y}, \mathbf{z}, 1)^{\top} (x,y,z,1)⊤&#xff0c;也就是w1&#xff0c;而三维的向量…

插入排序算法(C语言版)

直接插入排序 插入排序&#xff08;insert sort&#xff09;是一种简单的排序算法&#xff0c;它的工作原理与手动整理一副牌的过程非常相似。 具体来说&#xff0c;我们在未排序区间选择一个基准元素&#xff0c;将该元素与其左侧已排序区间的元素逐一比较大小&#xff0c;并…