<C++>哈希

news2024/9/22 11:36:47

文章目录

  • 1. unordered系列容器
    • 1.1 unordered_map
      • 1.1.1 unordered_map的文档介绍
      • 1.1.2 unordered_map的接口说明
    • 1.2 unordered_set
  • 2. 哈希概念
  • 3. 哈希冲突
  • 4. 哈希函数
  • 5. 哈希冲突解决
    • 5.1 闭散列
      • 5.1.1 线性探测
      • 5.1.2 二次探测
    • 5.2 开散列
    • 5.3 开散列与闭散列比较
  • 6. 模拟实现

1. unordered系列容器

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2 N log2N,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。

最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,本文中只对unordered_map和unordered_set进行介绍,unordered_multimap和unordered_multiset可查看文档介绍

1.1 unordered_map

1.1.1 unordered_map的文档介绍

unordered_map在线文档说明

  1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
  2. 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
  4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
  5. unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
  6. 它的迭代器至少是前向迭代器

1.1.2 unordered_map的接口说明

  1. unordered_map的构造
函数声明功能介绍
unordered_map构造不同格式的unordered_map对象
  1. unordered_map的容量
函数声明功能介绍
bool empty() const检测unordered_map是否为空
size_t size() const获取unordered_map的有效元素个数
  1. unordered_map的迭代器
函数声明功能介绍
begin返回unordered_map第一个元素的迭代器
end返回unordered_map最后一个元素下一个位置的迭代器
cbegin返回unordered_map第一个元素的const迭代器
cend返回unordered_map最后一个元素下一个位置的const迭代器
  1. unordered_map的元素访问
函数声明功能介绍
operator[]返回与key对应的value,没有一个默认值

注意:该函数中实际调用哈希桶的插入操作,用参数key与V()构造一个默认值往底层哈希桶中插入,如果key不在哈希桶中,插入成功,返回V(),插入失败,说明key已经在哈希桶中,将key对应的value返回。 (跟map的operator[] 用法一样)

  1. unordered_map的查询
函数声明功能介绍
iterator find(const K& key)返回key在哈希桶中的位置
size_t count(const K& key)返回哈希桶中关键码为key的键值对的个数

注意:unordered_map中key是不能重复的,因此count函数的返回值最大为 1

  1. unordered_map的修改操作
函数声明功能介绍
insert向容器中插入键值对
erase删除容器中的键值对
void clear()清空容器中有效元素个数
void swap(unordered_map&)交换两个容器中的元素
  1. unordered_map的桶操作
函数声明功能介绍
size_t bucket_count()const返回哈希桶中桶的总个数
size_t bucket_size(size_t n)const返回n号桶中有效元素的总个数
size_t bucket(const K& key)返回元素key所在的桶号

1.2 unordered_set

详情看unordered_set在线文档说明

2. 哈希概念

unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构。

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O( l o g 2 N log_2 N log2N),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
当向该结构中:

  • 插入元素
    根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
  • 搜索元素
    对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)
例如:数据集合{1,7,6,4,5,9};
哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小

哈希/散列 ------ 映射-》按映射关系查找

直接建立映射关系的问题:

  1. 数据范围分布广
  2. key的数据不是整数,是字符串,是自定义类型怎么映射?

image-20230121170948176

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快
问题:按照上述哈希方式,向集合中插入元素44,会出现什么问题?

3. 哈希冲突

对于两个数据元素的关键字 k i k_i ki k j k_j kj(i != j),有 k i k_i ki != k j k_j kj,但有:Hash( k i k_i ki) ==Hash( k j k_j kj),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。

把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。
发生哈希冲突该如何处理呢?

4. 哈希函数

引起哈希冲突的一个原因可能是:哈希函数设计不够合理。
哈希函数设计原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应该比较简单

常见哈希函数

  1. 直接定址法–(常用)
    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
    优点:简单、均匀
    缺点:需要事先知道关键字的分布情况
    使用场景:适合查找比较小且连续的情况
    面试题:字符串中第一个只出现一次字符

  2. 除留余数法–(常用)
    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,
    按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址

  3. 平方取中法–(了解)
    假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址;
    再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址
    平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况

注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突

5. 哈希冲突解决

解决哈希冲突两种常见的方法是:闭散列和开散列

5.1 闭散列

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢?

5.1.1 线性探测

比如2.1中的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,hashAddr为4,因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

  • 插入

    • 通过哈希函数获取待插入元素在哈希表中的位置
    • 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素

    image-20230121171314742

  • 删除
    采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。

    // 哈希表每个空间给个标记
    // EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
    enum State{EMPTY, EXIST, DELETE};
    

线性探测的实现

其中扩容部分的处理:

image-20220919215610346

namespace CloseHash
{
	enum State
	{
		EMPTY,
		EXITS,
		DELETE
	};

	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state;
	};

	template<class K, class V>
	class HashTable
	{
		typedef HashData<K, V> Data;
	public:
		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first)) //已经有了
				return false;

			// 负载因子到0.7及以上,就扩容
			if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
			{
				size_t newSize = _tables.size() == 0 ? 10 : 2 * _tables.size();
				// 扩容以后,需要重新映射
				HashTable<K, V, HashFunc> newHT;
				newHT._tables.resize(newSize);
				for (auto& e : _tables)
				{
					if (e._state == EXITS)
						newHT.Insert(e._kv);
				}
				_tables.swap(newHT._tables);
			}

			size_t hashi = kv.first;
			hashi %= _tables.size();
			int i = 1;
			// 线性探测/二次探测
			while (_tables[hashi]._state == EXITS)
			{
				hashi += i;
				i++;
				hashi %= _tables.size();// 为了防止hashi越界
			}
			_tables[hashi]._kv = kv;
			_tables[hashi]._state = EXITS;
			_n++;
			return true;
		}

		Data* Find(const K& key)
		{
			if (_tables.size() == 0)
				return nullptr;

			size_t hashi = key;
			hashi %= _tables.size();
			int i = 1;
			// 线性探测/二次探测
			while (_tables[hashi]._state != EMPTY)
			{
				if (_tables[hashi]._state != DELETE && _tables[hashi]._kv.first == key)
				{
					return &_tables[hashi];
				}
				hashi += i;
				i++;
				hashi %= _tables.size();// 为了防止hashi越界
			}
			return nullptr;
		}

		bool Erase(const K& key)
		{
			Data* ret = Find(key);
			if (ret)
			{
				ret->_state = DELETE;
				_n--;
				return true;
			}
			else
			{
				return false;
			}
		}
	private:
		vector<Data> _tables;
		size_t _n = 0;// 存在表里的数据个数
	};

线性探测优点:实现非常简单,
线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。如何缓解呢?

5.1.2 二次探测

线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为: H i H_i Hi = ( H 0 H_0 H0 + i 2 i^2 i2 )% m, 或者: H i H_i Hi = ( H 0 H_0 H0 - i 2 i^2 i2 )% m。其中:i =1,2,3…, H 0 H_0 H0是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小。

简单来讲就是:以平方的跨越跳跃着找空位子(隔1,隔4,隔9……)

研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。
因此:闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷

5.2 开散列

开散列概念
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

image-20220919215853771

image-20220919215858573

从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素

开散列实现

// 哈希桶
namespace Bucket
{
	template<class K, class V>
	struct HashNode
	{
		pair<K, V> _kv;
		HashNode<K, V>* _next;

		HashNode(const pair<K, V>& kv)
			: _kv(kv)
			, _next(nullptr)
		{}
	};

	template<class K, class V>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}

				_tables[i] = nullptr;
			}
		}

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
			{
				return false;
			}

			// 负载因子==1(每个位置挂一个节点)就扩容,下面就会讲扩容

			size_t hashi = kv.first;
			hashi %= _tables.size();

			// 头插到对应桶里
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			_n++;

			return true;
		}

		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return nullptr;
			}

			size_t hashi = key;
			hashi %= _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}

				cur = cur->_next;
			}
			return nullptr;
		}

	private:
		vector<Node*> _tables;// 指针数组
		size_t _n = 0;
	};
}

开散列增容
桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希表进行增容,那该条件怎么确认呢?

开散列最好的情况是:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。

// 负载因子==1(每个位置挂一个节点)就扩容
if (_tables.size() == _n)
{
    size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
    vector<Node*> newTable;
    newTable.resize(newSize, nullptr);
    for (size_t i = 0; i < _tables.size(); ++i)
    {
        // 新建节点,到时候还要把原节点删除,效率低
        //newHT.Insert(cur->_kv);
        //cur = cur->_next; 

        // 改进:把原节点转移到新表上去
        Node* cur = _tables[i];
        while (cur)
        {
            Node* next = cur->_next;

            size_t hashi = hf(kot(cur->_data)) % newSize;
            cur->_next = newTable[hashi];
            newTable[hashi] = cur;

            cur = next;
        }

        _tables[i] = nullptr;
    }

    newTable.swap(_tables);
}

开散列改进

目前只能存储key为整形的元素,其他类型怎么解决?

// 哈希函数采用处理余数法,被模的key必须要为整形才可以处理,此处提供将key转化为整形的方法
// 整形数据不需要转化
template<class T>
class DefaultHash
{
public:
    size_t operator()(const T& val)
    {
    	return val;
    }
};
// key为字符串类型,需要将其转化为整形
//class Str2Int
//{
//public:
//    size_t operator()(const string& s)
//    {
//        const char* str = s.c_str();
//        unsigned int seed = 131; // 31 131 1313 13131 131313
//        unsigned int hash = 0;
//        while (*str)
//        {
//        	hash = hash * seed + (*str++);
//        }
//        return (hash & 0x7FFFFFFF);
//    }
//};

// 更方便的写法:类模板的偏特化
template<>
struct DefaultHash<string>
{
    size_t operator()(const string& s)
    {
        const char* str = s.c_str();
        unsigned int seed = 131; // 31 131 1313 13131 131313
        unsigned int hash = 0;
        while (*str)
        {
        	hash = hash * seed + (*str++);
        }
        return (hash & 0x7FFFFFFF);
    }
};

5.3 开散列与闭散列比较

应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上:
由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子a <=0.7,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间

6. 模拟实现

相关的gitee链接

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

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

相关文章

配置野火霸道V2环境

配置野火霸道V2环境野火霸道开发板学习笔记信息说明下载安装Keil5配置Keil以使用DAP下载器DAP下载器的使用使用串口下载程序安装USB转串口驱动CH340检查是否安装成功配置MCUISP软件野火霸道开发板学习笔记 信息说明 日期 : 2023-01-23开发板: 野火霸道V2芯片型号: STM32F103Z…

[Paper Reading] Towards Conversational Recommendation over Multi-Type Dialogs

[Paper Reading] Towards Conversational Recommendation over Multi-Type Dialogs 文章目录[Paper Reading] Towards Conversational Recommendation over Multi-Type Dialogs论文简介快速回顾论文&#xff08;借助scispace&#xff09;梳理一下文章内容&#xff08;参考百度N…

自动化将Gitee的仓库导入Github

自动化将Gitee的仓库导入Github准备工作获取方式gitee的授权码github授权码工具源码用法下载gitee所有仓库到本地下载并更新到github&#xff08;自动创建仓库&#xff09;写在最后本方法能实现自动创建仓库 脚本及用法放在文章最后了&#xff0c;需要的自取 转跳到结尾 准备工…

高性能 Java 框架。Solon v1.12.3 发布(春节前兮的最后更)

一个更现代感的 Java "生态型"应用开发框架&#xff1a;更快、更小、更自由。不是 Spring&#xff0c;没有 Servlet&#xff0c;也无关 JavaEE&#xff1b;新兴独立的开放生态 &#xff08;已有150来个生态插件&#xff09; 。主框架仅 0.1 MB。 相对于 Spring Boot…

计算正整数的阶乘math.factorial()

【小白从小学Python、C、Java】【计算机等级考试500强双证书】【Python-数据分析】计算正整数的阶乘math.factorial()[太阳]选择题请问math.factorial(3)的输出结果是?import mathprint("【执行】math.factorial(3):",math.factorial(3))print("【执行】math.f…

带你玩转Jetson之Deepstream简明教程(二)Deepstream是什么?干什么?有什么优势?

1.Deepstream是什么&#xff1f; Deepstream是Nvidia公司推出的一套基于开源视频流框架Gstreamer的一套库。其本身由多个.lib.so和.h构成&#xff0c;其支持语言包括了Python和Cpp两种主流语言。你可以在任何Python或者Cpp编译器、开发环境中引用库的API构建属于你自己的推理流…

【c++之于c的优化】

目录&#xff1a;前言关键字一、命名空间1.什么是命名空间2.如何使用命名空间3.如何自己创建命名空间4.为什么要使用命名空间5.命名空间起别名6.匿名命名空间二、缺省参数定义缺省参数类型注意事项三、函数重载定义函数重载的三种方式操作系统的区分方式四、引用定义引用特性使…

【4-网络八股扩展】北京大学TensorFlow2.0

课程地址&#xff1a;【北京大学】Tensorflow2.0_哔哩哔哩_bilibiliPython3.7和TensorFlow2.1六讲&#xff1a;神经网络计算&#xff1a;神经网络的计算过程&#xff0c;搭建第一个神经网络模型神经网络优化&#xff1a;神经网络的优化方法&#xff0c;掌握学习率、激活函数、损…

【LeetCode每日一题】【2023/1/24】1828. 统计一个圆中点的数目

文章目录1828. 统计一个圆中点的数目方法1&#xff1a;枚举1828. 统计一个圆中点的数目 LeetCode: 1828. 统计一个圆中点的数目 中等\color{#FFB800}{中等}中等 给你一个数组 points &#xff0c;其中 points[i] [x_i, y_i] &#xff0c;表示第 i 个点在二维平面上的坐标。多…

【算法面试】队列算法笔试面试全解(金三银四面试专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;专注于研究 Java/ Liunx内核/ C及汇编/计算机底层原理/源码&#xff0c;就职于大型金融公司后端高级工程师&#xff0c;擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。 &#x1…

02_gpio子系统

总结 驱动程序还想控制gpio 可以不用读写寄存器 直觉用gpio子系统开发的接口就能用了 轻松做输入输出 获取当前值 详细介绍 用设备树里的节点 gpio1 介绍 imx6ull.dtsi gpio1 记录了控制器相关的寄存器基地址 gpio1: gpio209c000 {compatible "fsl,imx6ul-gpio"…

三、利用迁移学习进行模型微调(Datawhale组队学习)

文章目录安装配置环境准备图像分类数据集迁移学习微调训练图像分类模型导入环境图像预处理载入图像分类数据集建立类别和索引号之间映射关系定义数据加载器查看一个batch的图像和标注可视化一个batch的图像和标注模型的构建与测试可视化常见的迁移学习训练方式训练配置模型训练…

过完2022,依然记得仰望星空

&#x1f57a;作者&#xff1a;一名普普通通的双非大二学生迷茫的启明星&#x1f383;专栏&#xff1a;《数据库》《C语言从0到1专栏》《数据结构》《C语言杂谈》目录 ​编辑 一.2022之初 二.2022年中 三.2022年末 四.展望2023 一.2022之初 想起一年前这个时候&#xff0c…

07_plantform平台总线

总结 /sys/bus/plantform 平台总线其实就是继承 06_自己创建xbus总线 有了更多的玩法 和自己创建的xbus总线一样 平台总线也有dev和drv 需要这两个进行匹配之后 进行porbe调用 plantform_device 结构体中直觉继承了 struc device lantform_driver 继承了driver 详细介绍 plan…

树(基础部分)

章节目录&#xff1a;一、二叉树1.1 为什么要使用树&#xff1f;1.2 树的常用术语1.3 二叉树概念1.4 二叉树应用二、顺序存储二叉树2.1 概述2.2 基本应用三、线索化二叉树3.1 问题引出3.2 概述3.3 基本应用四、结束语一、二叉树 1.1 为什么要使用树&#xff1f; 数组存储方式&…

MP-2平面烟雾气体传感器介绍

MP-2平面烟雾气体传感器简介MP-2烟雾检测气体传感器采用多层厚膜制造工艺&#xff0c;在微型Al2O3陶瓷基片的两面分别制作加热器和金属氧化物半导体气敏层&#xff0c;封装在金属壳体内。当环境空气中有被检测气体存在时传感器电导率发生变化&#xff0c;该气体的浓度越高&…

【数据库概论】3.1 SQL简述、数据定义和索引

第三章 关系数据库标准语言SQL 目录第三章 关系数据库标准语言SQL3.1 SQL概述3.1.1 产生与发展3.1.2 SQL的特点3.1.3 SQL的基本概念3.2 数据库实例3.3 数据定义3.3.1 模式的定义和删除3.2.2基本表的定义、删除和修改1.常见数据类型2.定义基本表3.修改基本表4.删除基本表5.模式和…

英语学习打卡day3

2023.1.22 1.mariner n.水手 2.formation n.队形;组成;形成 n.形状;形式样式;表格 the formation of landscapes Keep the formation 保持队形 The chairs were arranged in the form of circle. fill in the form 填写表格 formal adj.正式的inform 通知deform 变形uniform 统…

06_平台总线匹配规则,自己搭建总线xbus

总结 bus_register() 自己创建平台总线 /sys/bux/xxx device_register() 对平台总线加入dev /sys/bus/xxx/dev driver_register() 对平台总线加入drv /sys/bus/xxx/drv 两个相匹配的时候 直接调用drv->probe 函数 进行基本的class_create() device_create()等 创建设备文件…

TryHackMe-红队-07_武器化

Weaponization 了解并探索常见的红队武器化技术。您将学习如何使用业内常见的方法来构建自定义有效负载&#xff0c;以获得初始访问权限。 什么是武器化 武器化是网络杀伤链模式的第二阶段。在此阶段&#xff0c;攻击者使用可交付的有效负载&#xff08;如word文档&#xff…