KD-Tree

游戏中常对物体进行空间划分,对于均匀分布的划分一般用四叉树(八叉树),动态不均匀的分布可以采用kd-tree

构建kd-tree

构建思路:

  • 1.对节点进行各维度的方差分析,选取方差最大(即离散程度最高)的维度进行排序。取中值节点作为分割点。并将其放入构建树的节点中
  • 2.被排序的节点按不同维度分割后,划分为力左空间(划分维度下小于分割节点的值)与右空间(划分维度下大于分割节点的值)。对两个空间重复步骤1,并且两个空间的分割点分别作为上一个分割点的左右子树加入构建树中。
    (设置维度除了分析方差,有时会选择所有维度循环设置)

树的节点的结构

struct TreeNode {int id; //节点唯一idVec2 pos;int split; //分割维度struct TreeNode* left;struct TreeNode* right;struct TreeNode* parent;
};

获取分割维度

//计算所有节点各维度的方差,确定分割维度
int KDTreeScene::chooseSplit(vector<Vec2> posData) {float xEx1 = 0;float xEx2 = 0;float xDx = 0;float size = posData.size();for (auto v : posData) {xEx1 += 1.0 / size * v.x * v.x;xEx2 += 1.0 / size * v.x;}xDx = xEx1 - xEx2 * xEx2;float yEx1 = 0;float yEx2 = 0;float yDx = 0;for (auto v : posData) {yEx1 += 1.0 / size * v.y * v.y;yEx2 += 1.0 / size * v.y;}yDx = yEx1 - yEx2 * yEx2;return xDx >= yDx ? 0 : 1;
}

构建树

//rect是画分割线的辅助参数,实际应用可无视
TreeNode* KDTreeScene::buildKdTree(vector<Vec2> posData, Rect rect, int splitIdx) {if (posData.size() == 0) return nullptr;int split = chooseSplit(posData); //根据方差设置维度//split = splitIdx; //循环设置维度if (posData.size() == 1) {//空间只剩一个节点,无需再划分TreeNode* TNode = new TreeNode();TNode->id = _id;_id++;TNode->pos = posData[0];TNode->left = nullptr;TNode->right = nullptr;TNode->split = split;//根据维度画分割线drawClipLine(rect, split, posData[0]);return TNode;}int mid = posData.size() / 2;vector<Vec2> leftPosData;vector<Vec2> rightPosData;//根据设置的维度进行排序if (split == 0) sort(posData.begin(), posData.end(), cmpX); //垂直x轴分割,则根据x值排序else sort(posData.begin(), posData.end(), cmpY); //垂直y轴分割,则根据y值排序//获取中值作为分割点Vec2 midPos = posData[mid];//小于中值的放入左空间for (int i = 0; i < mid; i++) {leftPosData.push_back(posData[i]);}//大于中值的放入右空间for (int j = mid + 1; j < posData.size(); j++) {rightPosData.push_back(posData[j]);}//创建树的节点TreeNode* TNode = new TreeNode();TNode->id = _id;_id++;TNode->pos = midPos;drawClipLine(rect, split, midPos);// ---------------------//用来画分割线的辅助参数,实际应用可无视Rect r1, r2;if (split == 0) {r1 = Rect(rect.getMinX(), rect.getMinY(), midPos.x - rect.getMinX(), rect.getMaxY() - rect.getMinY());r2 = Rect(midPos.x, rect.getMinY(), rect.getMaxX() - midPos.x, rect.getMaxY() - rect.getMinY());}else {r1 = Rect(rect.getMinX(), rect.getMinY(), rect.getMaxX() - rect.getMinX(), midPos.y - rect.getMinY());r2 = Rect(rect.getMinX(), midPos.y, rect.getMaxX() - rect.getMinX(), rect.getMaxY() - midPos.y);}// ---------------------//splitIdx++;  //循环遍历设置维度的方法//对左空间进行kd树的构建步骤,并把顶点作为当前节点的左子树TNode->left = buildKdTree(leftPosData, r1, splitIdx % 2);if (TNode->left != nullptr) TNode->left->parent = TNode;//对右空间进行kd树的构建步骤,并把顶点作为当前节点的右子树TNode->right = buildKdTree(rightPosData, r2, splitIdx % 2);if (TNode->right != nullptr) TNode->right->parent = TNode;TNode->split = split;//返回的节点为当前空间构造的kd树的顶点return TNode;
}

效果演示

请添加图片描述

近邻搜寻

搜寻思路

维护一个最近邻居的优先级队列,以及一个记录访问过节点的表

  • 1.从根节点开始,将目标点递归往下查询。与插入思路一样,当前维度小于当前节点值的查询左子树,大于当前节点值的查询右子树。直到移动到叶子节点,将叶子节点进行访问。
  • 2.访问叶子节点。如果最近邻居优先级队列里的值小于所需邻居值,或者该叶子节点到目标点的距离小于队列中最远的邻居节点到目标点的距离。则将该叶子节点更新到最近邻居的队列,记录访问过的当前节点
  • 3.从叶子节点往上查询,(获取该叶子节点的父节点或着解开递归等方式)。对父节点进行访问处理(步骤2)。如果父节点已经访问过,则继续往上(重复步骤3)
  • 4.对该父节点对应维度的分割边进行距离判定:
    1.如果最近邻居优先级队列里的值小于所需邻居值或者目标点到分割边的垂直距离小于队列中最远的邻居节点到目标点的距离,则对该父节点的另一半空间进行从步骤1开始往下的查询
    2.否则,直接忽略该父节点的另一半空间,继续往上查询(回到步骤3)

往上的查询直到树的顶点即停止

void KDTreeScene::searchNearestPoint(Vec2 pos, TreeNode* tNode) {while (!_nearestQueue.empty()) {_nearestQueue.pop();}_searchDrawNode->clear();_exploredMap.clear();_searchDrawNode->drawDot(pos, 5, Color4F(0, 1, 0, 1));//-----------------//进入搜寻TreeNode* leaf = searchLeafNode(pos, _kdTree);backCheck(pos, leaf);
//-----------------//下面是画出搜寻结果while (!_nearestQueue.empty()) {_searchDrawNode->drawDot(_nearestQueue.top().second->pos, 5, Color4F(0, 0, 0, 1));if (_model) {delete _nearestQueue.top().second;}_nearestQueue.pop();}
}

访问对应节点

上述步骤2

bool KDTreeScene::checkInsertQueue(Vec2 pos, TreeNode* tNode) {//记录以访问过的节点_exploredMap[tNode->id] = true;//_searchNode是需要获取的最近邻居节点数//近邻居优先级队列里的值小于所需邻居值,则直接插入队列if (_nearestQueue.size() < _searchNode) {_nearestQueue.emplace(pos.getDistance(tNode->pos), tNode);return true;}else {auto data = _nearestQueue.top();float dist = pos.getDistance(tNode->pos);//该叶子节点到目标点的距离小于队列中最远的邻居节点到目标点的距离,则更新队列if (dist < data.first) {_nearestQueue.pop();_nearestQueue.emplace(dist, tNode);return true;}}return false;
}

递归查询到叶节点

上述步骤1

TreeNode* KDTreeScene::searchLeafNode(Vec2 pos, TreeNode* tNode) {if (tNode->split == 0) {if (pos.x < tNode->pos.x) {if (tNode->left == nullptr) {checkInsertQueue(pos, tNode);return tNode;}else return searchLeafNode(pos, tNode->left);}else {if (tNode->right == nullptr) {checkInsertQueue(pos, tNode);return tNode;}else return searchLeafNode(pos, tNode->right);}}else {if (pos.y < tNode->pos.y) {if (tNode->left == nullptr) {checkInsertQueue(pos, tNode);return tNode;}else return searchLeafNode(pos, tNode->left);}else {if (tNode->right == nullptr) {checkInsertQueue(pos, tNode);return tNode;}else return searchLeafNode(pos, tNode->right);}}
}

往上查询

上述步骤3和4

void KDTreeScene::backCheck(Vec2 pos, TreeNode* tNode) {//到顶点停止if (tNode->parent == nullptr) {return;}else {//步骤3tNode = tNode->parent;//已经访问过的节点直接跳过,往上查询if (!_exploredMap[tNode->id]) {//访问父节点checkInsertQueue(pos, tNode);if (tNode->split == 0) {//步骤4if (_nearestQueue.size() < _searchNode || abs(pos.x - tNode->pos.x) < _nearestQueue.top().first) {//步骤4 情况1//----------------------if (pos.x < tNode->pos.x) {if(tNode->right) tNode = searchLeafNode(pos, tNode->right);}else {if (tNode->left) tNode = searchLeafNode(pos, tNode->left);}//----------------------}}else {if (_nearestQueue.size() < _searchNode || abs(pos.y - tNode->pos.y) < _nearestQueue.top().first) {if (pos.y < tNode->pos.y) {if (tNode->right) tNode = searchLeafNode(pos, tNode->right);}else {if (tNode->left) tNode = searchLeafNode(pos, tNode->left);}}}}backCheck(pos, tNode);}
}

效果演示

请添加图片描述

完整效果

以下是对2000个物体查询最近的6个邻居的效果:
请添加图片描述

对比了下对所有物体进行遍历找寻最近值的方式
搜寻3个最近物体时,在1000个时差不多开始出现1ms的延迟,在5000个3到4ms内的延迟,在10000个时7ms延迟, 在100000时70ms延迟, 就是成正比的延迟,
而kd树的查询到10000000仍是0,因为是类似二叉树的log的复杂度
主要耗时在构造树,1000个物体差不多5ms,5000个物体30ms,10000个物体50ms,100000个物体550ms,1000000个物体6400ms,5000000个物体37000ms,10000000个物体76000ms,差不多也是正比
不同机器效率不同,但是趋势一样

代码

KDTreeScene.h

#ifndef __KDTREE_SCENE_H__
#define __KDTREE_SCENE_H__#include "cocos2d.h"
USING_NS_CC;
using namespace std;struct TreeNode {int id;Vec2 pos;int split;struct TreeNode* left;struct TreeNode* right;struct TreeNode* parent;
};struct cmp {bool operator()(pair<float, TreeNode*>& a, pair<float, TreeNode*>& b) {return a.first < b.first;}
};class KDTreeScene : public Scene
{
public:static Scene* createScene();virtual bool init();virtual bool onTouchBegan(Touch* touch, Event* unused_event);TreeNode* buildKdTree(vector<Vec2> posData, Rect rect, int splitIdx);int chooseSplit(vector<Vec2> posData);void drawClipLine(Rect rect, int split, Vec2 pos);void searchNearestPoint(Vec2 pos, TreeNode* tNode);TreeNode* searchLeafNode(Vec2 pos, TreeNode* tNode);void backCheck(Vec2 pos, TreeNode* tNode);bool checkInsertQueue(Vec2 pos, TreeNode* tNode);// implement the "static create()" method manuallyCREATE_FUNC(KDTreeScene);void update(float dt);protected:EventListenerTouchOneByOne* _touchListener;Vec2 _touchBeganPosition;DrawNode* _mapDrawNode;DrawNode* _clipDrawNode;vector<Vec2> _posData;TreeNode* _kdTree;bool _isDrawSplitLine = false;DrawNode* _searchDrawNode;int _searchNode = 6;priority_queue<pair<float, TreeNode*>,  vector<pair<float, TreeNode*>>, cmp> _nearestQueue;int _id;unordered_map<int, bool> _exploredMap;bool _model = false;
};#endif

KDTreeScene.cpp

#include "KDTreeScene.h"
#include <chrono>
using namespace std::chrono;Scene* KDTreeScene::createScene()
{return KDTreeScene::create();
}static void problemLoading(const char* filename)
{printf("Error while loading: %s\n", filename);printf("Depending on how you compiled you might have to add 'Resources/' in front of filenames in KDTreeScene.cpp\n");
}// on "init" you need to initialize your instance
bool KDTreeScene::init()
{//// 1. super init firstif (!Scene::init()){return false;}auto visibleSize = Director::getInstance()->getVisibleSize();Vec2 origin = Director::getInstance()->getVisibleOrigin();auto layer = LayerColor::create(Color4B(255, 255, 255, 255));
layer:setContentSize(visibleSize);this->addChild(layer);_clipDrawNode = DrawNode::create();this->addChild(_clipDrawNode);_mapDrawNode = DrawNode::create();this->addChild(_mapDrawNode);_searchDrawNode = DrawNode::create();this->addChild(_searchDrawNode);auto drawNode = DrawNode::create();this->addChild(drawNode);auto vecData = vector<Vec2>{ Vec2::ZERO, Vec2(100,0), Vec2(100,640), Vec2(0,640) };Vec2* ptr = reinterpret_cast<Vec2*>(vecData.data());drawNode->drawPolygon(ptr, vecData.size(), Color4F(0, 0, 0, 1), 0, Color4F(0, 0, 0, 0));auto vecData1 = vector<Vec2>{ Vec2(1300,0), Vec2(1400,0), Vec2(1400,640), Vec2(1300,640) };Vec2* ptr1 = reinterpret_cast<Vec2*>(vecData1.data());drawNode->drawPolygon(ptr1, vecData1.size(), Color4F(0, 0, 0, 1), 0, Color4F(0, 0, 0, 0));_touchListener = EventListenerTouchOneByOne::create();_touchListener->setSwallowTouches(true);_touchListener->onTouchBegan = CC_CALLBACK_2(KDTreeScene::onTouchBegan, this);this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(_touchListener, layer);this->scheduleUpdate();return true;
}bool KDTreeScene::onTouchBegan(Touch* touch, Event* event)
{_touchBeganPosition = touch->getLocation();CCLOG("xxxxxxxxx==========>> %f? %f", _touchBeganPosition.x, _touchBeganPosition.y);if (_touchBeganPosition.x < 100) {_posData.clear();_mapDrawNode->clear();_clipDrawNode->clear();_searchDrawNode->clear();_id = 1;for (int i = 0; i < 5000000; i++) {float x = RandomHelper::random_real<float>(100, 1300);float y = RandomHelper::random_real<float>(0, 640);_posData.push_back(Vec2(x, y));
//            _mapDrawNode->drawDot(Vec2(x, y), 3, Color4F(0, 1, 1, 1));}auto last = std::chrono::system_clock::now();auto timestamp = std::chrono::time_point_cast<std::chrono::milliseconds>(last).time_since_epoch().count();_kdTree = buildKdTree(_posData, Rect(0, 0, 1400, 640), 0);_kdTree->parent = nullptr;auto now = std::chrono::system_clock::now();auto timestamp1 = std::chrono::time_point_cast<std::chrono::milliseconds>(now).time_since_epoch().count();CCLOG("build timestamp dif      %d", timestamp1 - timestamp);}else if (_touchBeganPosition.x > 1300) {_model = !_model;}else {if (_posData.empty()) {_id = 1;for (int i = 0; i < 2000; i++) {float x = RandomHelper::random_real<float>(100, 1300);float y = RandomHelper::random_real<float>(0, 640);_posData.push_back(Vec2(x, y));
//                _mapDrawNode->drawDot(Vec2(x, y), 3, Color4F(0, 1, 1, 1));}auto last = std::chrono::system_clock::now();auto timestamp = std::chrono::time_point_cast<std::chrono::milliseconds>(last).time_since_epoch().count();_kdTree = buildKdTree(_posData, Rect(0, 0, 1400, 640), 0);_kdTree->parent = nullptr;auto now = std::chrono::system_clock::now();auto timestamp1 = std::chrono::time_point_cast<std::chrono::milliseconds>(now).time_since_epoch().count();CCLOG("build timestamp dif       %d", timestamp1 - timestamp);return true;}searchNearestPoint(_touchBeganPosition, _kdTree);}return true;
}void KDTreeScene::update(float dt) {}bool cmpX(Vec2 a, Vec2 b) {return a.x < b.x;
}bool cmpY(Vec2 a, Vec2 b) {return a.y < b.y;
}int KDTreeScene::chooseSplit(vector<Vec2> posData) {float xEx1 = 0;float xEx2 = 0;float xDx = 0;float size = posData.size();for (auto v : posData) {xEx1 += 1.0 / size * v.x * v.x;xEx2 += 1.0 / size * v.x;}xDx = xEx1 - xEx2 * xEx2;float yEx1 = 0;float yEx2 = 0;float yDx = 0;for (auto v : posData) {yEx1 += 1.0 / size * v.y * v.y;yEx2 += 1.0 / size * v.y;}yDx = yEx1 - yEx2 * yEx2;return xDx >= yDx ? 0 : 1;
}TreeNode* KDTreeScene::buildKdTree(vector<Vec2> posData, Rect rect, int splitIdx) {if (posData.size() == 0) return nullptr;int split = chooseSplit(posData);//split = splitIdx;if (posData.size() == 1) {TreeNode* TNode = new TreeNode();TNode->id = _id;_id++;TNode->pos = posData[0];TNode->left = nullptr;TNode->right = nullptr;TNode->split = split;drawClipLine(rect, split, posData[0]);return TNode;}int mid = posData.size() / 2;vector<Vec2> leftPosData;vector<Vec2> rightPosData;if (split == 0) sort(posData.begin(), posData.end(), cmpX);else sort(posData.begin(), posData.end(), cmpY);Vec2 midPos = posData[mid];for (int i = 0; i < mid; i++) {leftPosData.push_back(posData[i]);}for (int j = mid + 1; j < posData.size(); j++) {rightPosData.push_back(posData[j]);}TreeNode* TNode = new TreeNode();TNode->id = _id;_id++;TNode->pos = midPos;drawClipLine(rect, split, midPos);Rect r1, r2;if (split == 0) {r1 = Rect(rect.getMinX(), rect.getMinY(), midPos.x - rect.getMinX(), rect.getMaxY() - rect.getMinY());r2 = Rect(midPos.x, rect.getMinY(), rect.getMaxX() - midPos.x, rect.getMaxY() - rect.getMinY());}else {r1 = Rect(rect.getMinX(), rect.getMinY(), rect.getMaxX() - rect.getMinX(), midPos.y - rect.getMinY());r2 = Rect(rect.getMinX(), midPos.y, rect.getMaxX() - rect.getMinX(), rect.getMaxY() - midPos.y);}splitIdx++;TNode->left = buildKdTree(leftPosData, r1, splitIdx % 2);if (TNode->left != nullptr) TNode->left->parent = TNode;TNode->right = buildKdTree(rightPosData, r2, splitIdx % 2);if (TNode->right != nullptr) TNode->right->parent = TNode;TNode->split = split;return TNode;
}void KDTreeScene::drawClipLine(Rect rect, int split, Vec2 pos) {
//    if (split == 0) {
//        _clipDrawNode->drawSegment(Vec2(pos.x, rect.getMinY()), Vec2(pos.x, rect.getMaxY()), 1, Color4F(1, 0, 0, 0.5));
//    }
//    else {
//        _clipDrawNode->drawSegment(Vec2(rect.getMinX(), pos.y), Vec2(rect.getMaxX(), pos.y), 1, Color4F(0, 0, 1, 0.5));
//    }
}void KDTreeScene::searchNearestPoint(Vec2 pos, TreeNode* tNode) {while (!_nearestQueue.empty()) {_nearestQueue.pop();}_searchDrawNode->clear();_exploredMap.clear();_searchDrawNode->drawDot(pos, 3, Color4F(1, 0, 0, 1));auto last = std::chrono::system_clock::now();auto timestamp = std::chrono::time_point_cast<std::chrono::milliseconds>(last).time_since_epoch().count();if (_model) {for (auto p : _posData) {TreeNode* t = new TreeNode();t->id = _id;_id++;t->pos = p;checkInsertQueue(pos, t);}}else {TreeNode* leaf = searchLeafNode(pos, _kdTree);backCheck(pos, leaf);}auto now = std::chrono::system_clock::now();auto timestamp1 = std::chrono::time_point_cast<std::chrono::milliseconds>(now).time_since_epoch().count();CCLOG("search timestamp dif       %d", timestamp1 - timestamp);while (!_nearestQueue.empty()) {_searchDrawNode->drawDot(_nearestQueue.top().second->pos, 3, Color4F(0, 0, 0, 1));if (_model) {delete _nearestQueue.top().second;}_nearestQueue.pop();}
}bool KDTreeScene::checkInsertQueue(Vec2 pos, TreeNode* tNode) {_exploredMap[tNode->id] = true;if (_nearestQueue.size() < _searchNode) {_nearestQueue.emplace(pos.getDistance(tNode->pos), tNode);return true;}else {auto data = _nearestQueue.top();float dist = pos.getDistance(tNode->pos);if (dist < data.first) {_nearestQueue.pop();_nearestQueue.emplace(dist, tNode);return true;}}return false;
}TreeNode* KDTreeScene::searchLeafNode(Vec2 pos, TreeNode* tNode) {if (tNode->split == 0) {if (pos.x < tNode->pos.x) {if (tNode->left == nullptr) {checkInsertQueue(pos, tNode);return tNode;}else return searchLeafNode(pos, tNode->left);}else {if (tNode->right == nullptr) {checkInsertQueue(pos, tNode);return tNode;}else return searchLeafNode(pos, tNode->right);}}else {if (pos.y < tNode->pos.y) {if (tNode->left == nullptr) {checkInsertQueue(pos, tNode);return tNode;}else return searchLeafNode(pos, tNode->left);}else {if (tNode->right == nullptr) {checkInsertQueue(pos, tNode);return tNode;}else return searchLeafNode(pos, tNode->right);}}
}void KDTreeScene::backCheck(Vec2 pos, TreeNode* tNode) {if (tNode->parent == nullptr) {return;}else {tNode = tNode->parent;if (!_exploredMap[tNode->id]) {checkInsertQueue(pos, tNode);if (tNode->split == 0) {if (_nearestQueue.size() < _searchNode || abs(pos.x - tNode->pos.x) < _nearestQueue.top().first) {if (pos.x < tNode->pos.x) {if(tNode->right) tNode = searchLeafNode(pos, tNode->right);}else {if (tNode->left) tNode = searchLeafNode(pos, tNode->left);}}}else {if (_nearestQueue.size() < _searchNode || abs(pos.y - tNode->pos.y) < _nearestQueue.top().first) {if (pos.y < tNode->pos.y) {if (tNode->right) tNode = searchLeafNode(pos, tNode->right);}else {if (tNode->left) tNode = searchLeafNode(pos, tNode->left);}}}}backCheck(pos, tNode);}
}

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

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

相关文章

多平台展示预约的服装小程序效果如何

线下实体服装店非常多&#xff0c;主要以同城生意为主&#xff0c;但随着电商经济增长&#xff0c;传统线下自然流量变少&#xff0c;商家们会选择线上入驻平台开店获得更多线上用户&#xff0c;包括自建私域小程序等。 而除了直接卖货外&#xff0c;线上展示预约在服装行业也…

Java 将word转为PDF的三种方式和处理在服务器上下载后乱码的格式

我这边是因为业务需要将之前导出的word文档转换为PDF文件&#xff0c;然后页面预览下载这样的情况。之前导出word文档又不是我做的&#xff0c;所以为了不影响业务&#xff0c;只是将最后在输出流时转换成了PDF&#xff0c;当时本地调用没什么问题&#xff0c;一切正常&#xf…

HarmonyOS(十一)——初识状态管理

前言 在前文的描述中&#xff0c;我们构建的页面多为静态界面。如果希望构建一个动态的、有交互的界面&#xff0c;就需要引入“状态”的概念。 假设我们要实现如下一个动态的交互界面&#xff1a; 上面的示例中&#xff0c;用户与应用程序的交互触发了文本状态变更&#x…

SQL server 根据已有数据库创建相同的数据库

文章目录 用导出的脚本创建相同的数据库导出建表脚本再次建表 一些sql语句 用导出的脚本创建相同的数据库 导出建表脚本 首先&#xff0c;右击要导出的数据库名&#xff0c;依次选择任务-生成脚本。 简介&#xff08;第一页&#xff09;处选择下一步&#xff0c;然后来到选择…

uniapp 打包H5页面时候清除手机缓存问题

最近遇到一个情况&#xff1a; uniapp 写了一个H5 页面&#xff0c;挂在一个小程序上面&#xff0c;但是每次更新代码&#xff0c;新增新功能&#xff0c;总是有的用户看到的还是上一个版本的样式&#xff0c;前端打包的时候&#xff0c;已经在Uniapp项目的根目录下面新建了一个…

Python替代Adobe从PDF提取数据

大家好&#xff0c;PDF文件是官方报告、发票和数据表的通用格式&#xff0c;然而从PDF文件中提取表格数据是一项挑战。尽管Adobe Acrobat等工具提供了解决方案&#xff0c;但它们并不总是易于获取或可自动化运行&#xff0c;而Python则是编程语言中的瑞士军刀。本文将探讨如何利…

使用 MITRE ATTCK® 框架缓解网络安全威胁

什么是MITRE ATT&CK框架 MITRE Adversarial Tactics&#xff0c; Techniques&#xff0c; and Common Knowledge&#xff08;ATT&CK&#xff09;是一个威胁建模框架&#xff0c;用于对攻击者用来入侵企业、云和工业控制系统&#xff08;ICS&#xff09;并发起网络攻击…

AI伦理专题报告:2023年全球人工智能伦理治理报告

今天分享的是人工智能系列深度研究报告&#xff1a;《AI伦理专题报告&#xff1a;2023年全球人工智能伦理治理报告》。 &#xff08;报告出品方&#xff1a;钛媒体&#xff09; 报告共计&#xff1a;239页 摘要 人工智能(ArtificialIntelligence)作为新一轮科技革命和产业变…

在 Node-RED 中引入 ECharts 实现数据可视化

Node-RED 提供了强大的可视化工具&#xff0c;而通过引入 ECharts 图表库&#xff0c;您可以更直观地呈现和分析数据。在这篇博客中&#xff0c;我们将介绍两种在 Node-RED 中实现数据可视化的方法&#xff1a;一种是引入本地 ECharts 库&#xff0c;另一种是直接使用 CDN&…

网络和Linux网络_11(数据链路层)以太网(MAC帧)协议+局域网转发+ARP协议

目录 1. 以太网协议 1.1 MAC地址 1.2 以太网帧格式 2. 局域网转发原理 2.1 数据碰撞和交换机 2.2 最大传输单元MTU 3. ARP协议 3.1 ARP协议格式 3.2 模拟APR协议工作过程 3.3 ARP缓存表 4. 重看TCP/IP四层模型 本篇完。 1. 以太网(MAC帧)协议 网络层的IP协议并不是…

什么是数据清洗、特征工程、数据可视化、数据挖掘与建模?

1.1什么是数据清洗、特征工程、数据可视化、数据挖掘与建模&#xff1f; 视频为《Python数据科学应用从入门到精通》张甜 杨维忠 清华大学出版社一书的随书赠送视频讲解1.1节内容。本书已正式出版上市&#xff0c;当当、京东、淘宝等平台热销中&#xff0c;搜索书名即可。内容涵…

python HTML文件标题解析问题的挑战

引言 在网络爬虫中&#xff0c;HTML文件标题解析扮演着至关重要的角色。正确地解析HTML文件标题可以帮助爬虫准确地获取所需信息&#xff0c;但是在实际操作中&#xff0c;我们常常会面临一些挑战和问题。本文将探讨在Scrapy中解析HTML文件标题时可能遇到的问题&#xff0c;并…

微软 Power Platform 零基础 Power Pages 网页搭建高阶实际案例实践(四)

微软 Power Platform 零基础 Power Pages 网页搭建教程之高阶案例实践学习&#xff08;四&#xff09; Power Pages 实际案例学习进阶 微软 Power Platform 零基础 Power Pages 网页搭建教程之高阶案例实践学习&#xff08;四&#xff09;1、新增视图&#xff0c;添加List页面2…

Java集合进阶(上)

集合 集合在Java开发中应用极为广泛&#xff0c;它其实就是一些常用的数据结构的包装类&#xff0c;分为单列集合&#xff08;Collecton接口类&#xff0c;例如LinkdeList集合&#xff09;和双列集合&#xff08;Map接口类&#xff0c;例如HashMap集合)两种 Collection Coll…

网络层之IP数据报格式、数据报分片、IPv4、子网划分和子网掩码

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

Kubernetes Service控制器详解以及切换为ipvs代理模式

文章目录 一、Service 存在的意义二、Pod与Service的关系三、Service定义与创建四、Service三种常用类型五、Service代理模式六、切换Service代理模式七、service总体工作流程八、kube-proxy ipvs和iptables的异同九、Service DNS名称 一、Service 存在的意义 service的引入主…

【从零开始学习JVM | 第四篇】类加载器的分类以及双亲委派机制

前言&#xff1a; 在Java编程中&#xff0c;类加载器(Class Loader)扮演着重要的角色。类加载器负责加载Java字节码并将其转换为可执行对象&#xff0c;使得我们能够在应用程序中使用各种类和资源。Java类加载器的设计和实现旨在支持动态扩展和模块化编程&#xff0c;为Java语…

管理和监控CentOS上的HTTP服务

CentOS作为一款稳定的开源服务器操作系统&#xff0c;为各种网络服务提供了优秀的支持。其中&#xff0c;HTTP服务是互联网上最常用的服务之一&#xff0c;它为人们提供了便捷的信息访问和交互方式。在CentOS上管理和监控HTTP服务是一项重要的任务&#xff0c;下面我们将介绍一…

【改进YOLOv8】融合感受野注意力卷积RFCBAMConv的杂草分割系统

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 随着计算机视觉技术的不断发展&#xff0c;图像分割成为了一个重要的研究领域。图像分割可以将图像中的不同对象或区域进行有效的分离&#xff0c;对于许多应用领…

elk+kafka+filebeat

elk1 cd /opt 把filebeat投进去 tar -xf filebeat-6.7.2-linux-x86_64.tar.gz mv filebeat-6.7.2-linux-x86_64 filebeat cd filebeat/ yum -y install nginx systemctl restart nginx vim /usr/share/nginx/html/index.html this is nginx cp filebeat.yml filebeat.yml.…