哈希(c++)

目录

一、unordered系列关联式容器

(一)unordered_set

(二)unordered_map

练习:961. 在长度2N的数组中找出重复N次的元素

二、哈希的底层结构

(一)哈希概念

(二)哈希冲突

三、哈希冲突解决(闭散列)

(一)线性探测

1. 仿函数(将key值转整型)

2. 插入

3. 查找和删除

4. 完整代码

(二)二次探测

四、哈希冲突解决(开散列)

(一)插入

(二)删除

(三)桶的数量、最大长度、平均长度

(四)完整代码


一、unordered系列关联式容器

  • 在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到$log_2N$,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同
  • 树状关联式容器(比如C++中的std::mapstd::set)通常使用比较函数(仿函数)来确定元素的顺序,这样可以进行元素的大小比较。而哈希容器(比如C++中的std::unordered_mapstd::unordered_set)需要将键转换为整数值,通常使用哈希函数(也是通过仿函数实现)来计算键的哈希值,然后将哈希值用于确定存储位置或进行快速查找 

(一)unordered_set

#include <iostream>
#include <unordered_set>
using namespace std;
int main()
{
    unordered_set<int>s;
    s.insert(1);
    s.insert(3);
    s.insert(5);
    s.insert(1);
    s.insert(2);

    unordered_set<int>::iterator it = s.begin();
    while (it != s.end())
    {
        cout << *it << " ";
        ++it;
    }
    //1  3  5  2
    return 0;
}

(二)unordered_map

  • unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
  • 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  • 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序,为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的中 
#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
    unordered_map<string,string>dict;
    dict["sort"] = "排序";
    dict["string"] = "字符串";


    unordered_map<string, string>::iterator it = dict.begin();
    while (it != dict.end())
    {
        cout << it->first << " "<<it->second<<endl;
        ++it;
    }
    //sort 排序
    //string 字符串
    return 0;
}

练习:961. 在长度2N的数组中找出重复N次的元素

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

class Solution {
public:
    int repeatedNTimes(vector<int>& nums) {
        int n = nums.size();
        unordered_map<int, int> m;
        for(int i=0;i<nums.size();i++)
        {
            m[nums[i]]++;
        }

        unordered_map<int, int>::iterator it = m.begin();
        while(it!=m.end())
        {
            if(it->second == n/2)
            {
                return it->first;
            }
            ++it;
        }
        return 0;
    }
};

二、哈希的底层结构

(一)哈希概念

理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。


当向该结构中:

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

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称
为哈希表(Hash Table)(或者称散列表)

(二)哈希冲突

  • 不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突哈希碰撞
  • 把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”

三、哈希冲突解决(闭散列)

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

如何寻找下一个空位置?

(一)线性探测

hashi+i(i>=0) ,i可以是0,1,2,3

  • 从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止
hashi++;
hashi = hashi % _tables.size();
  • 如果发生越界,继续从0开始探测

1. 仿函数(将key值转整型)

模板特化中使用了一个简单的哈希算法,遍历字符串中的每个字符,将哈希值乘以一个常数(31),然后加上当前字符的 ASCII 值。这样的操作可以在一定程度上减少哈希冲突。这个特化的哈希函数适用于处理字符串类型的关键字 

//伪函数,关键字转换成无符号整型
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

template<>//模板特化
struct HashFunc<string>//特化的哈希函数模板,专门用于处理 string 类型的关键字
{
	size_t hash = 0;
	size_t operator()(const string& key)
	{
		for (auto e : key)
		{
			hash *= 31;
			hash += e;
		}
		return hash;
	}
};

2. 插入

bool Insert(const pair<K, V>& kv)
{
	//负载因为0.7就扩容
	if (_n * 10 / _tables.size() == 7)
	{
		size_t newsize = _tables.size() * 2;
		HashTable<K, V> newHash;
		newHash._tables.resize(newsize);

		//遍历旧表依次将数据插入
		for (int i = 0; i < _tables.size(); i++)
		{
			if (_tables[i]._s == EXIST)
			{
				newHash.Insert(_tables[i]._kv);
			}
		}
		_tables.swap(newHash._tables);//交换旧哈希表和新哈希表

	}

	Hash hf;
	// 线性探测
	size_t hashi = hf(kv.first) % _tables.size();
	while (_tables[hashi]._s == EXIST)
	{
		hashi++;
		hashi = hashi % _tables.size();
	}
	_tables[hashi]._kv = kv;
	_tables[hashi]._s = EXIST;
	++_n;
	return true;
}

3. 查找和删除

HashData<K, V>* Find(const K& key)
{
	Hash hf;
	size_t hashi = hf(key) % _tables.size();
	while (_tables[hashi]._s != EMPTY)//容易出错
	{
		if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key)
		{
			return &_tables[hashi];
		}
		hashi++;
		hashi = hashi % _tables.size();//越界的时候,继续从头开始探测(查找)
	}
	return nullptr;
}

bool Erase(const K& key)//删除
{
	HashData<K, V>* ret = Find(key);
	if (ret != nullptr)
	{
		ret->_s = DELETE;//修改状态为删除
		--_n;//数据个数减少
		return true;
	}
	else
	{
		return false;
	}
}

4. 完整代码

  •  HashTable.h
#pragma once
#include<vector>
#include<string>
namespace Imitate_HashTbale
{
	enum Status//哈希表每个空间状态标记
	{
		EMPTY,//空
		EXIST,//存在数据
		DELETE//已被删除
	};

	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		Status _s;//标记哈希表每个位置的状态
	};

	//伪函数,关键字转换成无符号整型
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

	template<>//模板特化
	struct HashFunc<string>//特化的哈希函数模板,专门用于处理 string 类型的关键字
	{
		size_t hash = 0;
		size_t operator()(const string& key)
		{
			for (auto e : key)
			{
				hash *= 31;
				hash += e;
			}
			return hash;
		}
	};


	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		HashTable()//构造函数
		{
			_tables.resize(10);
		}
		bool Insert(const pair<K, V>& kv)
		{
			//负载因为0.7就扩容
			if (_n * 10 / _tables.size() == 7)
			{
				size_t newsize = _tables.size() * 2;
				HashTable<K, V> newHash;
				newHash._tables.resize(newsize);

				//遍历旧表依次将数据插入
				for (int i = 0; i < _tables.size(); i++)
				{
					if (_tables[i]._s == EXIST)
					{
						newHash.Insert(_tables[i]._kv);
					}
				}
				_tables.swap(newHash._tables);//交换旧哈希表和新哈希表

			}

			Hash hf;
			// 线性探测
			size_t hashi = hf(kv.first) % _tables.size();
			while (_tables[hashi]._s == EXIST)
			{
				hashi++;
				hashi = hashi % _tables.size();
			}
			_tables[hashi]._kv = kv;
			_tables[hashi]._s = EXIST;
			++_n;
			return true;
		}
		HashData<K, V>* Find(const K& key)
		{
			Hash hf;
			size_t hashi = hf(key) % _tables.size();
			while (_tables[hashi]._s != EMPTY)//容易出错
			{
				if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key)
				{
					return &_tables[hashi];
				}
				hashi++;
				hashi = hashi % _tables.size();//越界的时候,继续从头开始探测(查找)
			}
			return nullptr;
		}

		bool Erase(const K& key)//删除
		{
			HashData<K, V>* ret = Find(key);
			if (ret != nullptr)
			{
				ret->_s = DELETE;//修改状态为删除
				--_n;//数据个数减少
				return true;
			}
			else
			{
				return false;
			}
		}
		void print()
		{
			for (int i = 0; i < _tables.size(); i++)
			{
				if (_tables[i]._s == EXIST)
				{
					cout << "[" << i << "]" << "->" << _tables[i]._kv.first << ":" << _tables[i]._kv.second << endl;
				}
			}
			cout << endl;
		}
	private:
		vector<HashData<K, V>> _tables;
		size_t _n;//存放关键字的个数
	};


	void test1()
	{
		HashTable<int, int> HT;
		int data[] = { 1,3,6,2,7,9 ,5,67,8,19 };
		for (auto ch : data)
		{
			HT.Insert(make_pair(ch, ch));
		}
		HT.print();


		if (HT.Find(99))
		{
			cout << "99存在" << endl;
		}
		else
		{
			cout << "99不存在" << endl;
		}


		if (HT.Erase(8))
		{
			cout << "成功删除8" << endl;
		}
		else
		{
			cout << "删除失败" << endl;
		}
	}

	void test2()
	{
		string arr[] = { "香蕉", "甜瓜","苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
		HashTable<string, int> ht;

		for (auto& e : arr)
		{
			HashData<string, int>* ret = ht.Find(e);
			if (ret != nullptr)//已经存在了
			{
				ret->_kv.second++;
			}
			else
			{
				ht.Insert(make_pair(e, 1));
			}
		}
		ht.print();
	}
}
  • test.c 
#include<iostream>
using namespace std;
#include"HashTable.h"
int main()
{
	Imitate_HashTbale::test2();
	return 0;
}

(二)二次探测

hashi+i^2(i>=0)

  • 线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题 

四、哈希冲突解决(开散列)

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

(一)插入

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

	Hash hf;

	// 负载因子最大到1
	if (_n == _tables.size())
	{
		vector<Node*> newTables;
		newTables.resize(_tables.size() * 2, nullptr);

		//遍历旧表
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur)//循环将某一个桶中的所有结点进行头插
			{
				Node* next = cur->_next;
				size_t hashi = hf(cur->_kv.first) % newTables.size();
				//采用头插
				cur->_next = newTables[hashi];
				newTables[hashi] = cur;
				cur = next;
			}
			_tables[i] = nullptr;//旧表置空
		}

		_tables.swap(newTables);
	}

	//插入结点
	size_t hashi = hf(kv.first) % _tables.size();
	Node* cur = new Node(kv);
	//采用头插
	cur->_next = _tables[hashi];
	_tables[hashi] = cur;
	++_n;
	return true;
}

(二)删除

bool Erase(const K& key)
{
	Hash hf;
	size_t hashi = hf(key) % _tables.size();
	Node* cur = _tables[hashi];
	Node* prev = nullptr;
	while (cur)
	{
		if (cur->_kv.first == key)
		{
			if (cur == _tables[hashi])//删除头结点
			{
				_tables[hashi] = cur->_next;
			}
			else
			{
				prev->_next = cur->_next;
			}
			//释放结点
			delete cur;
			cur = nullptr;
			--_n;
			return true;
		}
		else
		{
			prev = cur;
			cur = cur->_next;
		}
	}
	return false;
}

(三)桶的数量、最大长度、平均长度

void Some()
{
	size_t bucketSize = 0;//使用桶的个数
	size_t maxBucketLen = 0;//桶的最大长度
	double averageBucketLen = 0;//桶的平均长度
	size_t sum = 0;
	for (size_t i = 0; i < _tables.size(); i++)
	{
		Node* cur = _tables[i];
		if (cur != nullptr)
		{
			++bucketSize;
		}

		size_t bucketLen = 0;//记录一个桶的长度
		while (cur)
		{
			++bucketLen;
			cur = cur->_next;
		}
		sum += bucketLen;

		//记录最大的桶的长度
		if (bucketLen > maxBucketLen)
		{
			maxBucketLen = bucketLen;
		}
	}
	averageBucketLen = double(sum) / bucketSize;//桶的平均长度
	printf("all bucketSize:%d\n", _tables.size());
	printf("bucketSize:%d\n", bucketSize);
	printf("maxBucketLen:%d\n", maxBucketLen);
	printf("averageBucketLen:%lf\n\n", averageBucketLen);
}

(四)完整代码

#pragma once
#include<vector>
#include<string>
#include<cstdbool>
//namespace Imitate_hash_bucket
//{
	template<class K, class V>
	struct HashNode//结点定义
	{
		HashNode* _next;
		pair<K, V>_kv;

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


	//伪函数,关键字转换成无符号整型
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

	template<>//模板特化
	struct HashFunc<string>//特化的哈希函数模板,专门用于处理 string 类型的关键字
	{
		size_t hash = 0;
		size_t operator()(const string& key)
		{
			for (auto e : key)
			{
				hash *= 31;
				hash += e;
			}
			return hash;
		}
	};


	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		HashTable()
		{
			_tables.resize(10);
		}
		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;//cur的后驱结点
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}

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

			Hash hf;

			// 负载因子最大到1
			if (_n == _tables.size())
			{
				vector<Node*> newTables;
				newTables.resize(_tables.size() * 2, nullptr);

				//遍历旧表
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)//循环将某一个桶中的所有结点进行头插
					{
						Node* next = cur->_next;
						size_t hashi = hf(cur->_kv.first) % newTables.size();
						//采用头插
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;
						cur = next;
					}
					_tables[i] = nullptr;//旧表置空
				}

				_tables.swap(newTables);
			}

			//插入结点
			size_t hashi = hf(kv.first) % _tables.size();
			Node* cur = new Node(kv);
			//采用头插
			cur->_next = _tables[hashi];
			_tables[hashi] = cur;
			++_n;
			return true;
		}

		Node* Find(const K& key)
		{
			Hash hf;
			size_t hashi = hf(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				else
				{
					cur = cur->_next;
				}
			}
			return nullptr;
		}

		bool Erase(const K& key)
		{
			Hash hf;
			size_t hashi = hf(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (cur == _tables[hashi])//删除头结点
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					//释放结点
					delete cur;
					cur = nullptr;
					--_n;
					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}

		void Some()
		{
			size_t bucketSize = 0;//使用桶的个数
			size_t maxBucketLen = 0;//桶的最大长度
			double averageBucketLen = 0;//桶的平均长度
			size_t sum = 0;
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur != nullptr)
				{
					++bucketSize;
				}

				size_t bucketLen = 0;//记录一个桶的长度
				while (cur)
				{
					++bucketLen;
					cur = cur->_next;
				}
				sum += bucketLen;

				//记录最大的桶的长度
				if (bucketLen > maxBucketLen)
				{
					maxBucketLen = bucketLen;
				}
			}
			averageBucketLen = double(sum) / bucketSize;//桶的平均长度
			printf("all bucketSize:%d\n", _tables.size());
			printf("bucketSize:%d\n", bucketSize);
			printf("maxBucketLen:%d\n", maxBucketLen);
			printf("averageBucketLen:%lf\n\n", averageBucketLen);
		}

	private:
		vector<Node*>_tables;
		size_t _n;
	};

	void test1()
	{
		HashTable<int, int>ht;
		int data[] = { 1,4,2,7,8,12,6 };
		for (auto e : data)
		{
			ht.Insert(make_pair(e, e));
		}



		if (ht.Find(12))
		{
			cout << "12找到了" << endl;
		}
		else
		{
			cout << "未找到" << endl;
		}

		//ht.Erase(4);
		//ht.Erase(2);
	}
	void test2()
	{
		const int N = 100;
		vector<int>v;
		HashTable<int, int> ht;
		srand(time(0));
		for (size_t i = 0; i < N; i++)
		{
			v.push_back(rand() + i); // 重复值相对少
		}

		for (auto e : v)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Some();
	}

	void test3()
	{
		string arr[] = { "香蕉", "甜瓜","苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
		HashTable<string, int> ht;
		for (auto& e : arr)
		{
			HashNode<string, int>* ret = ht.Find(e);
			if (ret)
			{
				ret->_kv.second++;
			}
			else
			{
				ht.Insert(make_pair(e, 1));
			}
		}
	}

//}
#include<iostream>
using namespace std;
#include"HashTable.h"
int main()
{
	//Imitate_hash_bucket::test3();
	test3();
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue实现聊天栏定位到最底部(超简单、可直接复制使用)

原理 通过watch监听聊天内容的加载&#xff0c;一旦加载完成或者数据更新触发vue的数据监听时&#xff0c;就重新修改【滚动滑钮到滚动条顶部的距离滚动条的高度】&#xff0c;从而实现定位到底部的效果。 实现 1、布局 新建一个div&#xff08;聊天框&#xff0c;如下&…

【数据结构算法(二)】链表总结

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 普通单向链表 双向链表 带哨兵的链表 环形链表 ⭐双向带头带环链表的实现⭐ ⭐链表基础OJ⭐ 普通单向链表 结点结构&#xff1a;只有val 和 next指针 初始时&#xff1a;head null; 双向链表 指针&…

dedecms标签

【Arclist 标记】这个标记是DedeCms最常用的一个标记&#xff0c;也叫自由列表标记&#xff0c;其中 hotart、coolart、likeart、artlist、imglist、imginfolist、specart、autolist 这些标记都是由这个标记所定义的不同属性延伸出来的别名标记。功能说明&#xff1a;获取指定的…

suricata识别菜刀流量

一、捕获菜刀流量 payload特征&#xff1a; PHP: <?php eval($_POST[caidao]);?> ​ ASP: <%eval request(“caidao”)%> ​ ASP.NET: <% Page Language“Jscript”%><%eval(Request.Item[“caidao”],“unsafe”);%>数据包流量特征&#xff1a; …

【C++上层应用】3. 动态内存

文章目录 【 1. new和delete运算符 】1.1 new 分配内存1.2 delete 释放内存1.3 实例 【 2. 数组的动态内存分配 】2.1 一维数组2.2 二维数组2.3 三维数组 【 3. 对象的动态内存分配 】 C 程序中的内存分为两个部分&#xff1a; 栈&#xff1a;在 函数内部 声明的所有变量都将占…

LeetCode算法心得——使用最小花费爬楼梯(记忆化搜索+dp)

大家好&#xff0c;我是晴天学长&#xff0c;很重要的思想动规思想&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。&#x1f4aa;&#x1f4aa;&#x1f4aa; 1&#xff09;使用最小花费爬楼梯 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从…

WinApp自动化测试之工具的选择

WinApp&#xff08;Windows APP&#xff09;是运行在Windows操作系统上的应用程序&#xff0c;通常会提供一个可视的界面&#xff0c;用于和用户交互。 例如运行在Windows系统上的Microsoft Office、PyCharm、Visual Studio Code、Chrome&#xff0c;都属于WinApp。常见的WinA…

【Linux】xfs文件系统的xfs_info命令

xfs_info命令 ① 查看命令工具自身的版本号 xfs_info -V ② 查看指定XFS设备的详细信息 xfs_info <device_name> 其他的一些命令可以使用man xfs_info去查阅man手册&#xff1a;

vue3中v-for报错 ‘item‘ is of type ‘unknown‘

报错 在写vue3ts的项目&#xff0c;得到一个数组&#xff0c;需要循环展示&#xff0c;使用v-for循环&#xff0c;写完之后发现有个报错&#xff0c;如下&#xff1a; 解决 props的时候使用PropType将数据类型完整标注即可 以为没有显示的表示出list中item的类型&#xff…

面对网络渠道低价 品牌如何应对

品牌在发展过程中&#xff0c;会不断拓展自己的销售渠道&#xff0c;网站渠道是顺应消费者习惯的一种销售战场&#xff0c;没有品牌会轻易丢弃这个渠道&#xff0c;但是网络渠道的低价又是很常见的&#xff0c;所以只有及时的治理渠道低价&#xff0c;对应的渠道才会发展越来越…

python数据结构与算法-10_递归

递归 Recursion is a process for solving problems by subdividing a larger problem into smaller cases of the problem itself and then solving the smaller, more trivial parts. 递归是计算机科学里出现非常多的一个概念&#xff0c;有时候用递归解决问题看起来非常简单…

呼叫中心自建好还是云外呼好用?

传统的呼叫中心在科技的发展下已经被不适用了&#xff0c;都开始使用起智能化的呼叫中心&#xff0c;一个是自建式呼叫中心&#xff0c;一个是云外呼系统。那自建式呼叫中心与云外呼系统的区别有哪些呢&#xff1f; 1、企业自建呼叫中心 劣势 系统维护更新难&#xff1a;自建…

Proxifier联动BurpSuite抓取小程序

直接上软件包 Proxifier安装包https://pan.quark.cn/s/7fb9ad6deb7cProxifier配置文件https://pan.quark.cn/s/049c5f21c97e 无话可说直接操作 1、安装Proxifier步骤可以省略..... 2、将下面文件导入到Proxifier中 3、左上角文件-导入配置文件&#xff08;因为我已经导入过…

快来瞧瞧这样制作出来的电子画册,还便于分享宣传呢!

说起电子画册制作&#xff0c;很多人都不知道从何入手。与传统纸质画册相比&#xff0c;电子画册最大的优点是便于传阅&#xff0c;通过微信、QQ等社交平台都能进行转发和分享。而且内容的排版基本上和纸质画册一致&#xff0c;不同的是&#xff0c;无论图片还是文字都可以赋予…

网络渗透测试(wireshark 抓取QQ图片)

1.打开wireshark 这里我用的wifi连接 所以点开wifi就好 打开wifi之后就开始在本机上进行抓包了 我们先给我们的QQ发送一张图片&#xff0c;用自己的手机发送给电脑 然后点击左上角的正方形&#xff0c;停止捕获抓包 QQ的关键词是oicq&#xff0c;所以我们直接找 打开oicq …

2023年国自然植物科学相关面上项目信息公布(小麦、大麦、棉花、大豆、玉米)

2024年申报国自然项目基金撰写及技巧http://mp.weixin.qq.com/s?__bizMzA4NTAwMTY1NA&mid2247575761&idx1&sn32dbacd3393f3b76a1e0668e4b8b3c89&chksm9fdd7c08a8aaf51ec31d4790067bb57751a09947eeb7e728b8c008d26b89adba37e0cab32a62&scene21#wechat_redi…

【23真题】难!下沙“小清华”难度爆增!

今天分享的是23年“下沙小清华”杭州电子科技大学843的信号与系统试题及解析。 本套试卷难度分析&#xff1a;22年杭电843考研真题&#xff0c;我也发布过&#xff0c;若有需要&#xff0c;戳这里自取&#xff01;平均分为112分&#xff0c;最高分为145分&#xff01;该院校23…

requests 库中响应最大文件大小和最大连接超时时间的设定

最近&#xff0c;requests-toolbelt库的开发者jvanasco提出了一项特性请求&#xff0c;即在发送请求时设置响应的最大文件大小和最大连接超时时间。 对于最大连接超时时间的问题&#xff0c;我们可以借鉴requests-toolbelt库的开发者kevinburke的建议&#xff0c;将请求放入线程…

基于变色龙算法优化概率神经网络PNN的分类预测 - 附代码

基于变色龙算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于变色龙算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于变色龙优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…

我的创作纪念日2048天

机缘 在这特殊的日子里&#xff0c;我要庆祝我的 CSDN 创作纪念日——已经坚持了整整2048天&#xff01; 在这2048天里&#xff0c;我经历了很多成长和收获。作为一名技术写手&#xff0c;我投入了大量的时间和精力来分享我的知识和经验。我曾经写过关于数据库、数据同步、数…