【C++进阶】哈希(万字详解)—— 运用篇(下)

news2024/11/30 8:45:55

🎇C++学习历程:入门


  • 博客主页:一起去看日落吗
  • 持续分享博主的C++学习历程
  • 博主的能力有限,出现错误希望大家不吝赐教
  • 分享给大家一句我很喜欢的话: 也许你现在做的事情,暂时看不到成果,但不要忘记,树🌿成长之前也要扎根,也要在漫长的时光🌞中沉淀养分。静下来想一想,哪有这么多的天赋异禀,那些让你羡慕的优秀的人也都曾默默地翻山越岭🐾。

在这里插入图片描述

🍁 🍃 🍂 🌿


目录

  • 🍃 1. 模拟实现
    • 🌿 1.1 哈希表代码
    • 🌿 1.2 哈希表的改造
    • 🌿 1.3 哈希表的最终代码
    • 🌿 1.4 unordered_map 的模拟实现
  • 🍃 2. 位图
    • 🌿 2.1 库中的位图
    • 🌿 2.2 模拟实现位图
  • 🍃 3. 布隆过滤器
    • 🌿 3.1 布隆过滤器提出
    • 🌿 3.2 布隆过滤器概念
    • 🌿 3.3 布隆过滤器优点
    • 🌿 3.4 布隆过滤器缺陷
    • 🌿 3.5 布隆过滤器模拟实现
  • 🍃 4. 海量数据面试题
    • 🌿 4.1 位图应用
    • 🌿 4.2 哈希切分+布隆过滤器应用

🍃 1. 模拟实现

🌿 1.1 哈希表代码

namespace Byih
{
	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)
		{}
	};

	size_t GetNextPrime(size_t prime)
	{
		const int PRIMECOUNT = 28;
		static const size_t primeList[PRIMECOUNT] =
		{
			53ul, 97ul, 193ul, 389ul, 769ul,
			1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
			49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
			1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
			50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
			1610612741ul, 3221225473ul, 4294967291ul
		};

		size_t i = 0;
		for (; i < PRIMECOUNT; ++i)
		{
			if (primeList[i] > prime)
				return primeList[i];
		}

		return primeList[i];
	}

	template<class K, class V, class Hash = HashFunc<K>>
	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;
			}
			_n = 0;
		}

		bool Erase(const K& key)
		{
			if (_tables.size() == 0)
			{
				return false;
			}

			Hash hf;
			// 素数
			size_t index = hf(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[index];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					// 1、cur是头结点
					// 2、非头节点
					if (prev == nullptr)
					{
						_tables[index] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}

					delete cur;
					--_n;

					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}

			return false;
		}

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

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

			return nullptr;
		}

		bool Insert(const pair<K, V>& kv)
		{
			Hash hf;

			//当负载因子到1时,进行扩容
			if (_n == _tables.size())
			{
				//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				size_t newSize = GetNextPrime(_tables.size());

				//HashTable<K, V> newHT;
				vector<Node*> newtables;
				newtables.resize(newSize, nullptr);
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						size_t index = hf(cur->_kv.first) % newSize;
						cur->_next = newtables[index];
						newtables[index] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}

				newtables.swap(_tables);
			}

			size_t index = hf(kv.first) % _tables.size();
			Node* cur = _tables[index];
			while (cur)
			{
				if (cur->_kv.first == kv.first)
				{
					return false;
				}
				else
				{
					cur = cur->_next;
				}
			}

			Node* newnode = new Node(kv);
			newnode->_next = _tables[index];
			_tables[index] = newnode;

			++_n;

			return true;
		}

	private:
		vector<Node*> _tables;
		size_t _n = 0; // 存储多少有效数据
	};
}


🌿 1.2 哈希表的改造

  • 模板参数的改造

K:关键码类型
V: 不同容器V的类型不同,如果是unordered_map,V代表一个键值对,如果是unordered_set,V为 K
KeyOfT: 在哈希表中需要取到value,因为V的类型不同,通过value取key的方式就不同,详细见unordered_map/set的实现
Hash: 哈希函数仿函数对象类型,哈希函数使用除留余数法,如果是Key为string类型,需要将Key转换为整形数字才能取模

template<class K, class T, class KeyOfT, class Hash = HashFunc<T> >
class HashBucket;

  • 哈希表节点结构
template<class T>
struct HashNode
{
    T _data;
    HashNode<T>* _next;

    HashNode(const T& data)
        :_data(data)
            , _next(nullptr)
        {}
};

如果是unordered_map,v代表一个键值对,如果是unordered_set,v为 K


  • operator++模拟实现

当下一个节点不为空时,++后的节点就在当前桶,返回即可,当下一个节点为空时,我们需要找下一个桶,首先通过当前节点计算找到当前节点所在桶的位置index,计算出后,++index即找到了下一个桶,当下一个桶存在时,如果下一个桶里面有数据(即不为空),则将第一个数据给当前节点,就实现了++,否则继续找下一个桶,当循环出来时,有可能是找到++后的节点了,也有可能说明走完了后面没有桶了,所以循环出来需要判断是不是没有桶了,没有桶则返回nullptr

Self operator++()
{
    if (_node->_next) // 在当前桶迭代
    {
        _node = _node->_next;
    }
    else // 找下一个桶
    {
        KeyOfT kot;
        const K& key = kot(_node->_data);
        Hash hf;
        size_t index = hf(key) % _ht->_tables.size();
        ++index;
        _node = nullptr;
        while (index < _ht->_tables.size())
        {
            if (_ht->_tables[index])
            {
                _node = _ht->_tables[index];
                break;
            }
            else
            {
                ++index;
            }
        }

        // 后面没有桶了
        if (index == _ht->_tables.size())
        {
            _node = nullptr;
        }
    }

    return *this;
}


  • operator*的模拟实现
T& operator*()
{
    return _node->_data;
}

  • operator->的模拟实现
T* operator->()
{
    return &_node->_data;
}

  • operator==和operator!=的模拟实现
bool operator!=(const Self& s) const
{
    return _node != s._node;
}

bool operator==(const Self& s) const
{
    return _node == s._node;
}

🌿 1.3 哈希表的最终代码

namespace Byih
{
	template<class T>
	struct HashNode
	{
		T _data;
		HashNode<T>* _next;

		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{}
	};

	size_t GetNextPrime(size_t prime)
	{
		const int PRIMECOUNT = 28;
		static const size_t primeList[PRIMECOUNT] =
		{
			53ul, 97ul, 193ul, 389ul, 769ul,
			1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
			49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
			1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
			50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
			1610612741ul, 3221225473ul, 4294967291ul
		};

		size_t i = 0;
		for (; i < PRIMECOUNT; ++i)
		{
			if (primeList[i] > prime)
				return primeList[i];
		}

		return primeList[i];
	}

	// 前置声明
	template<class K, class T, class Hash, class KeyOfT>
	class HashTable;

	template<class K, class T, class Hash, class KeyOfT>
	struct HTIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, Hash, KeyOfT> HT;
		typedef HTIterator<K, T, Hash, KeyOfT> Self;

		Node* _node;
		HT* _ht;

		HTIterator(Node* node, HT* ht)
			:_node(node)
			, _ht(ht)
		{}

		bool operator!=(const Self& s) const
		{
			return _node != s._node;
		}

		T& operator*()
		{
			return _node->_data;
		}


		T* operator->()
		{
			return &_node->_data;
		}

		Self operator++()
		{
			if (_node->_next) // 在当前桶迭代
			{
				_node = _node->_next;
			}
			else // 找下一个桶
			{
				KeyOfT kot;
				const K& key = kot(_node->_data);
				Hash hf;
				size_t index = hf(key) % _ht->_tables.size();
				++index;
				_node = nullptr;
				while (index < _ht->_tables.size())
				{
					if (_ht->_tables[index])
					{
						_node = _ht->_tables[index];
						break;
					}
					else
					{
						++index;
					}
				}

				// 后面没有桶了
				if (index == _ht->_tables.size())
				{
					_node = nullptr;
				}
			}

			return *this;
		}
	};

	template<class K, class T,  class KeyOfT, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<T> Node;

		//template<class K, class T, class Hash, class KeyOfT>
		friend struct HTIterator<K, T, Hash, KeyOfT>;
	public:
		typedef HTIterator<K, T, Hash, KeyOfT> iterator;

		iterator begin()
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					return iterator(_tables[i], this);
				}
			}

			return end();
		}

		iterator end()
		{
			return iterator(nullptr, this);
		}

		// 拷贝 和 赋值 需要自己实现桶的拷贝

		~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;
			}

			_n;
		}

		bool Erase(const K& key)
		{
			if (_tables.size() == 0)
			{
				return false;
			}

			Hash hf;
			KeyOfT kot;

			// 素数
			size_t index = hf(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[index];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					// 1、cur是头结点
					// 2、非头节点
					if (prev == nullptr)
					{
						_tables[index] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}

					delete cur;
					--_n;

					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}

			return false;
		}

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

			Hash hf;
			KeyOfT kot;
			size_t index = hf(key) % _tables.size();
			Node* cur = _tables[index];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return cur;
				}
				else
				{
					cur = cur->_next;
				}
			}

			return nullptr;
		}

		bool Insert(const T& data)
		{
			Hash hf;
			KeyOfT kot;

			//当负载因子到1时,进行扩容
			if (_n == _tables.size())
			{
				//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				size_t newSize = GetNextPrime(_tables.size());

				//HashTable<K, V> newHT;
				vector<Node*> newtables;
				newtables.resize(newSize, nullptr);
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						const K& key = kot(cur->_data);
						size_t index = hf(key) % newSize;

						cur->_next = newtables[index];
						newtables[index] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}

				newtables.swap(_tables);
			}

			const K& key = kot(data);
			size_t index = hf(key) % _tables.size();
			Node* cur = _tables[index];
			while (cur)
			{
				if (kot(cur->_data) == kot(data))
				{
					return false;
				}
				else
				{
					cur = cur->_next;
				}
			}

			Node* newnode = new Node(data);
			newnode->_next = _tables[index];
			_tables[index] = newnode;

			++_n;

			return true;
		}

	private:
		vector<Node*> _tables;
		size_t _n = 0; // 存储多少有效数据
	};
}


🌿 1.4 unordered_map 的模拟实现

#pragma once
#include "HashTable.h"
namespace byh
{
	template<class K, class V>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K, V>& kv) const
			{
				return kv.first;
			}
		};
	public:
         iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}
        //插入
		pair<iterator,bool> insert(const pair<const K, V>& kv)
		{
			return _ht.Insert(kv);
		}
        //[]运算符重载
         V& operator[](const K& key)
         {
             pair<iterator,bool> ret = insert(make_pair(key,V()));
             iterator it = ret.first;
             return it->second;
         }
        //删除
        bool erase(const K& key)
        {
            return _ht.Erase(key);
        }
        //查找
        iterator find(const K& key)
        {
            return _ht.Find(key);
        }
	private:
		Byih::HashTable<K, pair<const K, V>, MapKeyOfT> _ht;
	};
}

实现unordered_map只需要调用底层哈希表对应的接口即可,实现unordered_set不一样的是它不需要实现[]运算符重载


🍃 2. 位图

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。适用于海量数据的状态:比如:40亿数据,需要16G内存;若用位图存放这些数据在不在的状态,只需要16/32G,约500M。

数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。比如:

在这里插入图片描述

主要应用:

  1. 快速查找某个数据是否在一个集合中
  2. 排序 + 去重
  3. 求两个集合的交集、并集等
  4. 操作系统次磁盘中的标记等
  • 优点:节省空间,速度快
  • 缺点:只能处理整形数据

🌿 2.1 库中的位图

主要接口:set(将某位设为1) reset(将某位设为0) test(判断某一位是否为1)

	bitset<100> bs;
	//将某位设置为1
	bs.set(11);bs.set(5);bs.set(78);bs.set(23);bs.set(12);
	//将某位设置为0
	bs.reset(11);
 
	//判断某位是否为1
	for (size_t i = 0; i < 100; i++)
	{
		//cout << i << ":" << bs.test(i) << " ";
		//if (i != 0 && i % 10 == 0)
		//	cout << endl;
		if (bs.test(i) == 1)
			cout << i << " ";
	}
	cout << endl;


🌿 2.2 模拟实现位图

	template<size_t N>
	class byh
	{
	public:
		BitSet()
		{
			_bits.resize(N / 32 + 1, 0);//默认构造,会对位图进行初始化
		}
 
		// 把x映射的位标记成1
		void Set(size_t x)
		{
			assert(x < N);
 
			// 算出x映射的位在第i个整数
			// 算出x映射的位在这个整数的第j个位
			size_t i = x / 32;
			size_t j = x % 32;
 
			// _bits[i] 的第j位标记成1,并且不影响他的其他位
			_bits[i] |= (1 << j); //或等于
			//(1 << j)
			//00000001000000000
		}
 
		void Reset(size_t x)
		{
			assert(x < N);
 
			size_t i = x / 32;
			size_t j = x % 32;
 
			// _bits[i] 的第j位标记成0,并且不影响他的其他位
			_bits[i] &= (~(1 << j)); //与等于
			//对 1 << j 取反就行
			//~(1 << j)
			//1111111101111111111	
		}
 
 
		bool Test(size_t x)
		{
			assert(x < N);
 
			size_t i = x / 32;
			size_t j = x % 32;
 
			// 如果第j位是1,结果是非0,非0就是真
			// 如果第j为是0,结果是0,0就是假
			return _bits[i] & (1 << j);//直接把这一位取出来是1还是0
			//return (_bits[i] >> j) & 1;//这样写也可以
		}
	private:
		vector<int> _bits;
	};


🍃 3. 布隆过滤器

🌿 3.1 布隆过滤器提出

我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。 如何快速查找呢?

  1. 用哈希表存储用户记录,缺点:浪费空间
  2. 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了。
  3. 将哈希与位图结合,即布隆过滤器

🌿 3.2 布隆过滤器概念

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。

在这里插入图片描述
原理讲解


🌿 3.3 布隆过滤器优点

  1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
  2. 哈希函数相互之间没有关系,方便硬件并行运算
  3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
  4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
  5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
  6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算

🌿 3.4 布隆过滤器缺陷

  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
  2. 不能获取元素本身
  3. 一般情况下不能从布隆过滤器中删除元素
  4. 如果采用计数方式删除,可能会存在计数回绕问题

🌿 3.5 布隆过滤器模拟实现

//布隆过滤器实际上是对位图的改进,所以实现上也是对位图的封装,一般只提供set和test接口,不能实现reset(删除)
template<size_t N, class K = std::string,class Hash1 = HashBKDR,class Hash2 = HashAP,class Hash3 = HashDJB>
//后面几个是字符串哈希函数的仿函数
class BloomFilter
{
public:
	void Set(const K& key)
	{
		//Hash1 hf1;
		//size_t i1 = hf1(key);//以下写法也可以
		size_t i1 = Hash1()(key) % N;//Hash1()是仿函数的匿名对象
		size_t i2 = Hash2()(key) % N;
		size_t i3 = Hash3()(key) % N;
 
		cout << i1 << " " << i2 << " " << i3 << endl;
 
		_bitset.Set(i1);
		_bitset.Set(i2);
		_bitset.Set(i3);
	}
 
	bool Test(const K& key)//判断是否存在
	{
		size_t i1 = Hash1()(key) % N;
		if (_bitset.Test(i1) == false)
		{
			return false;
		}
 
		size_t i2 = Hash2()(key) % N;
		if (_bitset.Test(i2) == false)
		{
			return false;
		}
 
		size_t i3 = Hash3()(key) % N;
		if (_bitset.Test(i3) == false)
		{
			return false;
		}
 
		// 这里3个位都在,有可能是其他key占了,在是不准确的,存在误判
		// 不在是准确的
		return true;
	}
 
private:
	byh::BitSet<N> _bitset; // 对位图的封装
	//byh::vector<char> _bitset;
};


🍃 4. 海量数据面试题

🌿 4.1 位图应用

  • 题目一

给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?

思路:100亿的整数范围还是42亿,因此用一个位图来存储只需要512M
将一个文件映射到位图中,再依次读取另一个文件的数据,看在不在位图中,在就是交集;或者构建两个位图,求他们的交集;

  • 题目二

给定100亿个整数,设计算法找到只出现一次的整数?

思路:用位图的思想,一个bit位能表示两种状态,这里至少是3种状态,因此需要两个bit位00表示没出现;01表示只出现一次;10表示出现过2次及以上;将所有数插入位图中,然后遍历位图,找出标志位01的位即为所求


🌿 4.2 哈希切分+布隆过滤器应用

哈希切分的原理:就是将一个大文件,利用哈希的原理(i = Hash()(ip) % 100, i表示小文件的编号),将其分为若干个小文件。

哈希切割的特点:相同的ip一定进入了同一个小文件当中。

  • 题目三

给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法

  1. 近似算法:利用布隆过滤器100亿个query(ip),可以看做string,假设为100G,那么两个文件一共是200G。将A文件依次映射到一个布隆过滤器中,再依次读取B文件中的数据,与布隆过滤器里的内容比较,在就是交集,但是会有一定的误判率。
  2. 精确算法:利用哈希切分 + 布隆过滤器
    可以将AB文件都切割成200个小文件(哈希切分并不是均匀的,依次要保证小文件小于内存大小),按照同样的映射函数 i = Hash()(ip) % 200 这样AB中相同的ip,都进了各自对应的编号i的小文件,因此只需要依次比较Ai和Bi中的交集即可

在这里插入图片描述

  • 题目四

给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?

与上题条件相同,如何找到top K的IP?如何直接用Linux系统命令实现?

思路:哈希切分,切分成100个小文件(相同的ip一定进入了同一个小文件)然后只需统计各个小文件各个ip的频次(比如用一个map<string, int>统计),找出每个小文件频次最高的ip地址进行比较即可;要求 top K的ip,可以建一个K个元素小堆,后面的元素依次与堆顶元素比较,比它大就替换进堆,最终这个小堆就是top K ;

在这里插入图片描述


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

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

相关文章

[附源码]Python计算机毕业设计电子工厂进销存管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

为什么要把测试环境的告警当成生产环境的告警处理?是一个哲学问题,还是一个技术问题?...

开发不愿意了一个后端服务通常有三个环境&#xff1a;测试环境&#xff0c;预发布环境&#xff0c;生产环境。运维在给测试环境增加告警规则和告警路由时&#xff0c;开发人员反对。这很容易理解&#xff0c;如果真把告警规则配置到测试环境&#xff0c;他们可能无时不刻地收到…

Web GIS开发教程

Web GIS开发教程 非程序员的基本 Web GIS 开发 课程英文名&#xff1a;Web GIS development course 此视频教程共4.0小时&#xff0c;中英双语字幕&#xff0c;画质清晰无水印&#xff0c;源码附件全 下载地址 课程编号&#xff1a;355 百度网盘地址&#xff1a;https://p…

杭州联合银行 x 袋鼠云:打造智能标签体系,助力银行大零售业务转型

“智能标签平台上线后&#xff0c;支行及业务部门已创建多个客群用于营销&#xff0c;为我行客户精细化管理打下了良好基础。” 2021 年&#xff0c;联合银行就已搭建了大数据基础平台&#xff0c;围绕平台搭建了数据研发平台、大数据调度平台及大数据服务平台&#xff0c;提高…

(附源码)Python飞机票销售系统 毕业设计 141432

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…

Vue组件的嵌套关系,父组件传递子组件 ,事件总线,Provide,inject,作用域插槽,具名插槽非props的attribute ,子组件传递父组件

组件化 – 组件间通信 认识组件的嵌套 ◼ 前面我们是将所有的逻辑放到一个App.vue中:  在之前的案例中,我们只是创建了一个组件App;  如果我们一个应用程序将所有的逻辑都放在一个组件中,那么这个组件就会变成非常的臃 肿和难以维护;  所以组件化的核心思想应该是对…

【YOLOv5】记录YOLOv5的学习过程

以下记录的是Ubuntu20.04版本&#xff0c;其他Ubuntu版本也相差不大~ 一、安装pytorch GPU版本、显卡驱动、CUDA、cuDNN 下载pytorch GPU版本&#xff1a; 最新版本链接&#xff1a;Start Locally | PyTorch 历史版本链接&#xff1a;Previous PyTorch Versions | PyTorch…

MySQL——内置函数

文章目录内置函数日期函数字符串函数数学函数其他函数内置函数 日期函数 基本使用&#xff1a; 可以进行运算&#xff1a; 在日期基础上加时间 在日期基础上减时间 计算两个日期相差的天数 案例1&#xff1a; 建一张表&#xff0c;记录生日 案例2&#xff1a; 创建一…

设计有趣的轻巧真无线,体积小续航长,南卡小音舱上手

大家平时都会听听音乐、玩玩游戏&#xff0c;这时候就需要用到蓝牙耳机&#xff0c;特别是在户外接打电话时&#xff0c;戴上一副耳机都会方便很多。最近发现了一款南卡小音舱Lite2&#xff0c;这些天用过之后感觉它质量不错&#xff0c;做得十分小巧&#xff0c;日常携带特别方…

Postman带sessionId的post请求访问失败

Postman带sessionId的post请求访问失败1、Python 调用过程2、Postman 错误示例3、Postman 正确示例4、总结使用 Python 访问一个数据接口&#xff0c;调用是正常的&#xff0c;但是使用 Postman 进行访问时出错了&#xff0c;搞了两天&#xff0c;后面发现很简单&#xff0c;故…

如何理解FFT中时间窗与RBW的关系

作为一种常用的频谱分析工具&#xff0c;快速傅里叶变换(FFT) 实现了时域到频域的转换&#xff0c;是数字信号分析中最常用的基本功能之一。FFT 频谱分析是否与传统的扫频式频谱仪类似&#xff0c;也具有分辨率带宽(RBW) 的概念&#xff1f;如果具有RBW &#xff0c;那么FFT 的…

前端食堂技术周刊第 63 期:Vite 4.0、State of CSS 2022、Rome v11、Web 性能日历、VueConf 2022 PPT

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;霜糖山楂 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 本期摘要 Vite 4.0State of CSS 2022 调查结果Rome v11HTMHell Advent Calendar 20…

虚幻引擎VR游戏开发基础教程

虚幻引擎VR游戏开发基础教程 了解如何使用 Oculus Quest 2 的蓝图在虚幻引擎 4 中从头开始构建基本的 VR 机制 课程英文名&#xff1a;Unreal Engine VR Development Fundamentals 此视频教程共4.0小时&#xff0c;中英双语字幕&#xff0c;画质清晰无水印&#xff0c;源码附…

推荐一些Python练手项目,了解完毕后才吃惊

前言 入门篇&#xff1a; 0.Python初学者一般都是那些根本没有编程基础的学生。做这个项目&#xff0c;你应该首先开始基本语法。教程中的几个实验可以让完全零基础的学生在一个下午学习Linux、python基础知识和GitHub命令。 1.Python-Python 图片转字符画50 行 Python 代码…

web前端期末大作业网页设计与制作 ——汉口我的家乡旅游景点 5页HTML+CSS+JavaScript

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…

JDBC 入门

目录1 JDBC 快速入门1.1 JDBC 的概念1.2 JDBC 快速入门2 JDBC 功能类详解2.1 DriverManager2.2 Connection2.3 Statement2.4 ResultSet3 JDBC 工具类4 SQL 注入攻击5 JDBC 事务5.1 JDBC 管理事务6 连接池6.1 数据库连接池的概念6.2 自定义数据库连接池6.2.1 DataSource6.2.2 归…

嵌入式:ARM存储器组织、协处理器及片上总线

文章目录ARM存储器组织ARM存储数据类型和存储格式ARM的存储器层次简介存储器管理单元MMUARM协处理器ARM片上总线AMBAARM存储器组织 ARM存储数据类型和存储格式 ARM处理器支持以下6种数据类型 8位有符号和无符号字节。16位有符号和无符号半字&#xff0c;它们以两字节的边界定…

字符串匹配问题(KMP)

文章目录题目KMP 算法1&#xff09;例子演示2&#xff09;KMP算法思路3&#xff09;疑惑模型验证4&#xff09;求 next 数组5&#xff09;代码演示6&#xff09;复杂度分析题目 有字符串 str1 和 str2 &#xff0c;str1 中是否包含 str2&#xff0c;如果没有包含返回 -1&#…

电商行业用天翎低代码平台做客服管理系统

编者按&#xff1a;在市场竞争越来越激烈的今天&#xff0c;客服作为电商行业的重要组成部分&#xff0c;如何科学管理成为企业管理层不可避免的难题&#xff0c;做好客服管理对企业具有重要意义。本文通过唯品会金牌客服管理系统案例介绍了低代码平台在定制化和快速落地的特点…

python tkinter 登录 计算器

使用tkinter开发图形化小项目&#xff1a; 功能&#xff1a; 登录 &#xff1a;登录成功 跳转到 计算器 页面&#xff0c;否则登录失败计算器 &#xff1a;登录成功后&#xff0c;窗口标题栏显示当前登录的用户 技术&#xff1a; 面向对象标准模块SQLite数据库登录成功后页…