【C++】哈希(模拟实现unordered系列容器)

一、哈希表的改造

1、模板参数列表的改造

  • K:关键码类型
  • V:不同容器V的类型不同。如果是 unordered_map,V 代表一个键值对;如果是 unordered_set,V 为 K。
  • KeyOfValue:因为 V 的类型不同,通过 value 取 key 的方式就不同,通过T的类型来获取 key 值。
  • HF:哈希函数仿函数对象类型,哈希函数使用除留余数法,需要将不能取模的类型 Key 转换为可以取模的 size_t (整形数字)。
template<class K, class V, class KeyOfValue, class HF = DefHashF<T>>
class HashBucket;

2、增加迭代器操作

// 为了实现简单,在哈希桶的迭代器类中需要用到hashBucket本身,
template<class K, class V, class KeyOfValue, class HF>
class HashBucket;

// 注意:因为哈希桶在底层是单链表结构,所以哈希桶的迭代器不需要--操作
template <class K, class V, class KeyOfValue, class HF>
struct HBIterator
{
    // 类型重命名
    typedef HashBucket<K, V, KeyOfValue, HF> HashBucket; // 哈希表
    typedef HashBucketNode<V> Node;                      // 哈希表节点
    typedef HBIterator<K, V, KeyOfValue, HF> Self;       // 迭代器

    // 构造函数(迭代器由节点指针和哈希表指针来构造)
    HBIterator(Node* node = nullptr, HashBucket* pHt = nullptr);

    // 相关运算符重载
    // 1、让迭代器可以移动,前置++运算符的重载(单向迭代器,不支持--操作)
    Self& operator++() // 前置++,返回指向下一个节点的迭代器的引用
    {
        // 遍历当前桶的节点
        // 1.当前节点的下一个节点不为空
        if (_node->_next)
        {
            _node = _node->_next; // _node指向下一个节点
        }
        // 2.当前节点的下一个节点为空,说明走到当前哈希桶的桶底了
        else
        {
            // 先通过哈希函数计算出当前桶的位置(先取出key再进行取模)
            size_t index = Hash()(KeyOfT()(_node->_data)) % _ht->_tables.size();

            // 从下一个位置开始,遍历哈希表,找到下一个不为空的哈希桶
            index++;
            while (index < _ht->_tables.size())
            {
                if (_ht->_tables[index]) // 找到下一个不为空的桶了
                {
                    _node = _ht->_tables[index]; // _node指向该桶的第一个节点
                    return *this;
                }
                index++;
            }
            // 后面没有不为空的桶了
            _node = nullptr; // _node指向空
        }
        return *this; // 返回下一个节点的迭代器的引用
    }

    Self operator++(int);

    // 2、让迭代器具有类似指针的行为
    V& operator*() // *解引用操作符
    {
        return _node->_data; // 返回迭代器指向节点中数据的引用
    }

    V* operator->() // ->成员访问(指针)操作符
    {
        return &_node->_data; // 返回迭代器指向节点中数据的地址
    }

    // 让迭代器可以比较
    bool operator==(const Iter& it) const
    {
        return _node == it._node; // 比较两个迭代器中的节点指针,看是否指向同一节点
    }

    bool operator!=(const Iter& it) const
    {
        return _node != it._node; // 比较两个迭代器中的节点指针,看是否指向同一节点
    }

    Node _node;     // 当前迭代器关联的节点
    HashBucket* _pHt; // 哈希桶--主要是为了找下一个空桶时候方便
};

注意:哈希表迭代器封装的是节点指针哈希表指针,因为要在哈希表中找下一个桶,所以要用到哈希表指针。 

 【思路】前置++运算符的重载(单向迭代器,不支持 -- 操作)

  • 如果当前桶的节点没有遍历完,则让节点指针 _node 指向下一个节点。
  • 如果当前桶的节点遍历完了,则让节点指针 _node 指向下一个不为空的桶的第一个节点。


3、定义哈希表的结构

一个数组,数组中存储着各个链表头结点的地址。

注意这里有一个私有成员访问的问题,我们在哈希表迭代器中封装了一个哈希表指针,当一个桶遍历完成时,用来在哈希表中找下一个桶,但哈希表 _tables 是私有成员,无法访问,所以需要将迭代器类模板声明为友元。

K:键值 key 的类型。
T:数据的类型,如果是 unordered_set,则为 key,如果是 unordered_map,则为 pair<const key, V>。
Hash:仿函数类,将不能取模的类型转换成可以取模的 size_t 类型。
KeyOfT:仿函数类,通过 T 的类型来获取 key 值。

// 定义哈希表结构
template<class K, class T, class Hash, class KeyOfT>
class HashTable
{
    typedef HashNode<T> Node; // 哈希表节点

    // 声明迭代器类模板为友元,因为迭代器中的哈希表指针要访问哈希表的私有成员_tables
    // 写法一:
    template<class K, class T, class Hash, class KeyOfT>
    friend struct HTIterator;
    // 写法二:
    friend struct HTIterator<K, T, Hash, KeyOfT>;

public:
    // 迭代器
    typedef HTIterator<K, T, Hash, KeyOfT> iterator; // iterators内嵌类型
    iterator begin(); // 返回指向第一个节点的迭代器
    iterator end();   // 返回指向nullptr的迭代器
    
    // 构造、拷贝构造、赋值重载、析构函数
    HashTable() = default; // 默认生成,因为实现了拷贝构造函数,就不会再生成构造函数了
    HashTable(const HashTable& ht);
    HashTable& operator=(HashTable ht);
    ~HashTable();
    
    Node* Find(const K& key);                   // 查找节点
    pair<iterator, bool> Insert(const T& data); // 插入节点
    bool Erase(const K& key);                   // 删除节点
    
private:
    vector<Node*> _tables;  // 哈希表(存储着各个链表头结点的地址)
    size_t _n = 0;          // 哈希表中有效节点的个数(缺省为0)
};

① begin() 和 end() 的实现
// 返回指向第一个节点的迭代器
iterator begin()
{
    // 遍历哈希表,找到第一个不为空的哈希桶
    for (size_t i = 0; i < _tables.size(); i++)
    {
        if (_tables[i])
        {
            // 注意:迭代器由节点指针和哈希表指针构造,而成员函数的this指针就是指向哈希表的
            return iterator(_tables[i], this); // 返回迭代器
        }
    }
    return end();
}

// 返回指向nullptr的迭代器
iterator end()
{
    // 注意:迭代器由节点指针和哈希表指针构造,而成员函数的this指针就是指向哈希表的
    return iterator(nullptr, this);
}

注意:this 指针指向当前调用 begin() 成员函数的哈希表对象的。


② 默认成员函数的实现
a. 构造函数的实现
HashTable() = default; // 默认生成,因为实现了拷贝构造函数,就不会再生成构造函数了

b. 拷贝构造函数的实现(深拷贝)

必须是深拷贝,浅拷贝出来会导致两个哈希表中存放的是同一批哈希桶的地址

  • 将新哈希表大小调整为 ht._tables.size()
  • 遍历 ht._tables 的所有哈希桶,将桶里的节点 一一 拷贝到新哈希表对应位置上。
  • 更改新哈希表中的有效节点个数为 ht._n
// 拷贝构造
HashTable(const HashTable& ht)
{
    // 深拷贝,用已存在的对象ht去拷贝构造一个新对象
    // 将新哈希表大小调整为ht._tables.size()
    _tables.resize(ht._tables.size());

    // 遍历ht._tables的所有哈希桶,将桶里的节点一一拷贝到新哈希表对应位置上
    for (size_t i = 0; i < ht._tables.size(); i++)
    {
        Node* cur = ht._tables[i]; // 记录当前桶的位置
        while (cur) // 当前桶不为空,开始拷贝桶中的节点
        {
            Node* copy = new Node(cur->_data); // 申请一个新节点

            copy->_next = _tables[i]; // 将新节点插入到新哈希表中
            _tables[i] = copy;

            cur = cur->_next; // 继续遍历下一个节点
        }
    }
    // 更改新哈希表中的有效节点个数
    _n = ht._n;
}

c. 赋值运算符重载函数的实现(现代写法)

通过参数间接调用拷贝构造函数,然后将拷贝构造出来的哈希表和当前哈希表的两个成员变量分别进行交换即可,这样当前哈希表就拿到了自己想要的内容,当函数调用结束后,拷贝构造出来的哈希表 ht 出了作用域会被自动析构。

// 赋值运算符重载
HashTable& operator=(HashTable ht)
{
    // 传参时,调用拷贝构造函数,拷贝构造了一个哈希表ht
    // 将拷贝构造出来的哈希表ht和当前哈希表的两个成员变量分别进行交换
    _tables.swap(ht._tables);
    _n = ht._n;

    return *this; // 返回当前哈希表
}

d. 析构函数的实现 
// 析构函数
~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; // 当前桶释放完毕,置空
    }

    _n = 0; // 有效节点个数置0
}

4、实现哈希表的相关接口

① 查找节点

查找节点接口中,需要获取不同类型元素关键码 key,因为元素间的比较是通过 key 值来比较的,以及通过元素关键码 key 取模计算出哈希位置。

所以需要借助两个仿函数类:

  • 仿函数类 KeyOfT,获取不同类型的 data 中的关键码 key 值(如果 data 是 key 就取 key,如果是 pair 就取 first)
  • 仿函数类 Hash,有些元素关键码 key 的类型不能直接取模,需要将其转换成可以取模的 size_t 类型。
Node* Find(const K& key)
{
    // 1、先检查哈希表是否为空
    if (_tables.size() == 0)
    {
        return nullptr;
    }

    // 2、再通过哈希函数计算出该元素映射的哈希桶的位置
    size_t index = Hash()(key) % _tables.size();

    // 3、遍历该哈希桶,查找节点
    Node* cur = _tables[index]; // cur指向该哈希桶
    while (cur)                 // 遍历该哈希桶的所有节点
    {
        if (key == KeyOfT()(cur->_data)) // 取key出来比较
        {
            return cur; // 找到了,返回节点地址
        }

        // 继续往后遍历
        cur = cur->_next;
    }

    // 4、没找到,返回空
    return nullptr;
}

② 插入节点

插入节点接口中,需要获取不同类型元素关键码 key,因为元素间的比较是通过 key 值来比较的,以及通过元素关键码 key 取模计算出哈希位置。

所以需要借助两个仿函数类:

  • 仿函数类 KeyOfT,获取不同类型的 data 中的关键码 key 值(如果 data 是 key 就取 key,如果是 pair 就取 first)
  • 仿函数类 Hash,有些元素关键码 key 的类型不能直接取模,需要将其转换成可以取模的 size_t 类型。

函数的返回值为 pair<iterator, bool> 类型对象:

  • 目的是为了在 unordered_set 和 unordered_map 中模拟实现 operator[] 运算符重载函数。

插入元素接口功能:插入元素前,会通过该元素的关键码 key 检查该元素是否已在哈希表中(不允许冗余):

  • 如果在,返回:pair<指向该元素的迭代器, false>。
  • 如果不在,先插入节点,再返回:pair<指向该元素的迭代器, true>。

T:数据的类型,如果是 unordered_set,则为 key,如果是 unordered_map,则为 pair<const key, V> 。

// 插入节点
pair<iterator, bool> Insert(const T& data)
{
    // 1、先检查哈希表是否需要扩容:表为空或者负载因子超过1
    if (_n == _tables.size())
    {
        // 计算新容量(按2倍扩)
        size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;

        // 开始扩容
        // 创建一个新表(局部变量)
        vector<Node*> newTables;
        newTables.resize(newSize);

        // 遍历完旧表中的所有节点,重新计算它在新表中的位置,转移到新表中
		// 这里是把旧表的节点转移到新表中,而不是构造新的节点插入到新表中
        for (size_t i = 0; i < _tables.size(); i++)
        {
            Node* cur = _tables[i]; // cur当前指向的哈希桶

            // 哈希桶不为空,开始转移哈希桶中的节点
            while (cur != nullptr)
            {
                // 保存cur指向节点的下一个节点
                Node* next = cur->_next;

                // 重新计算cur指向的旧表节点,映射到新表中的位置
                size_t index = Hash()(KeyOfT()(cur->_data)) % newSize; // 取key出来取模

                // 把cur指向的旧表节点,转移到新表中
                cur->_next = newTables[index];
                newTables[index] = cur;

                // 继续转移下一个旧表节点
                cur = next;
            }
            // 节点转移完毕,把当前哈希桶置空
            _tables[i] = nullptr;
        }
        // 旧表所有节点全部转移到新表中了,交换新表与旧表
        _tables.swap(newTables);
    }

    // 2、再通过哈希函数计算出待插入元素映射的哈希桶的位置
    size_t index = Hash()(KeyOfT()(data)) % _tables.size(); // 取key出来取模

    // 3、插入节点到该位置的哈希桶中
    // 先检查哈希桶中是否存在重复节点(因为不允许数据冗余)
    Node* cur = _tables[index]; // cur指向哈希桶的第一个节点
    while (cur)                 
    {
        // 存在重复节点,插入失败
        if (KeyOfT()(data) == KeyOfT()(cur->_data)) // 取key出来比较
        {
            // 构造pair<cur指向节点的迭代器, false>,进行返回
            return make_pair(iterator(cur, this), false);
        }
        cur = cur->_next;
    }

    // 开始头插
    Node* newNode = new Node(data);  // 申请新节点
    newNode->_next = _tables[index]; // 头插
    _tables[index] = newNode;
    _n++;                            // 有效节点个数+1

    // 插入成功
    // 构造pair<cur指向节点的迭代器, false>,进行返回
    return make_pair(iterator(newNode, this), true);
}

③ 删除节点

删除节点接口中,需要获取不同类型元素关键码 key,因为元素间的比较是通过 key 值来比较的,以及通过元素关键码 key 取模计算出哈希位置。

所以需要借助两个仿函数类:

  • 仿函数类 KeyOfT,获取不同类型的 data 中的关键码 key 值(如果 data 是 key 就取 key,如果是 pair 就取 first)。
  • 仿函数类 Hash,有些元素关键码 key 的类型不能直接取模,需要将其转换成可以取模的 size_t 类型。
bool Erase(const K& key)
{
    // 1、先判断哈希表是否为空
    if (_tables.size() == 0)
    {
        return false; // 表为空,删除失败
    }

    // 2、通过哈希函数计算出待删除节点所映射哈希桶的位置
    size_t index = Hash()(key) % _tables.size();

    // 3、遍历该哈希桶,查找待删除节点,以及它的前驱节点
    Node* cur = _tables[index];
    Node* prev = nullptr;
    while (cur)
    {
        // 找到该节点了
        if (key == KeyOfT()(cur->_data)) // 取key出来比较
        {
            if (cur == _tables[index]) // cur是头节点,进行头删
            {
                _tables[index] = cur->_next;
            }
            else // cur不是头节点
            {
                prev->_next = cur->_next;
            }

            delete cur;    // 删除节点
            cur = nullptr;
            _n--;          // 有效节点个数-1

            return true;   // 删除成功,返回true
        }
        // 继续往后遍历
        prev = cur;
        cur = cur->_next;
    }

    // 没有找到该节点,返回false
    return false;
}

5、针对除留余数法的取模问题

实现哈希表,计算元素关键码对应哈希位置的哈希函数采用的是除留余数法,需要传仿函数将不能取模的类型转换成可以取模的 size_t 类型。

这里给出针对普通类型取模和 string 类型取模的仿函数,放到全局域中,方便模拟实现 unordered_set 和 unordered_map 时使用。

// 仿函数(解决哈希函数采用除留余数法时,将不能取模的类型转换成可以取模的size_t类型)
// 默认仿函数类
template<class K>
struct HashFunc
{
    // 针对size_t类型和能够隐式类型转换成size_t的类型
    size_t operator()(const K& key)
    {
        return key;
    }
};

// 特化
template<>
struct HashFunc<string>
{
    // 把string类型转换成可以取模的size_t类型
    size_t operator()(const string& key)
    {
        size_t hash_key = 0;
        for (size_t i = 0; i < key.size(); i++)
        {
            hash_key *= 131;
            hash_key += key[i];
        }
        return hash_key;
    }
};

二、unordered_set 的模拟实现

unordered_set 在实现时,只需将 hashbucket 中的接口重新封装即可。

定义 unordered_set 的结构:

  • K:键值 key 的类型。
  • Hash:仿函数类,将不能取模的类型转换成可以取模的 size_t 类型。
namespace winter
{
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
		// 仿函数类,获取key对象中的key
		struct KeyOfSet
		{
			const K& operator()(const K& key) const
			{
				return key;
			}
		};
        
	public:
		// 这里是要取HashTable<...>类模板里面定义的内嵌类型iterator,要注意:
		// 编译到这里的时候,类模板HashTable<K, K, Hash, KeyOfSet>可能还没有实例化成具体的类
		// 那么编译器就不认识这个类模板,更别说去它里面找iterator了
		// 所以要加typename,告诉编译器这是个类型,等它实例化了再去它里面找iterator
		typedef typename hash_bucket::HashTable<K, K, Hash, KeyOfSet>::iterator iterator;
		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}

		// 插入元素
		pair<iterator, bool> insert(const K& key)
		{
			return _ht.Insert(key);
		}
	private:
        // 封装一张哈希表,因为要实现unordered_set,所以要传
        // 键值K、键值K、针对除留余数法取模问题的Hash仿函数,提取Key值的KeyOfSet仿函数
		hash_bucket::HashTable<K, K, Hash, KeyOfSet> _ht;
	};
}

三、unordered_map 的模拟实现

unordered_map 在实现时,只需将 hashbucket 中的接口重新封装即可。

定义 unordered_map 的结构:

  • K:键值 key 的类型。
  • V:unordered_map 存储的数据 d 类型,pair<const key, V>。
  • Hash:仿函数类,将不能取模的类型转换成可以取模的 size_t 类型。

unordered_map 中存储的是 pair<K, V> 的键值对,K 为 key 的类型,V 为 value 的类型,HF 为哈希函数类型。

template<class K, class V, class HF = DefHashF<K>>
class unordered_map
{
    typedef pair<K, V> ValueType;
    typedef HashBucket<K, ValueType, KeyOfValue, HF> HT;
    // 通过key获取value的操作
    struct KeyOfValue
    {
        const K& operator()(const ValueType& data)
        {
            return data.first;
        }
    };
public:
    typename typedef HT::Iterator iterator;
public:
    unordered_map(): _ht()
    {}

    iterator begin(){ return _ht.Begin();}
    iterator end(){ return _ht.End();}

    // capacity
    size_t size()const
    {
        return _ht.Size();
    }
    bool empty()const
    {
        return _ht.Empty();
    }

    // Acess
    V& operator[](const K& key)
    {
        return (*(_ht.InsertUnique(ValueType(key, V())).first)).second;
    }
    const V& operator[](const K& key)const;

    // lookup
    iterator find(const K& key)
    {
        return _ht.Find(key);
    }
    size_t count(const K& key)
    {
        return _ht.Count(key);
    }

    // modify
    pair<iterator, bool> insert(const ValueType& valye)
    {
        return _ht.Insert(valye);
    }
    iterator erase(iterator position)
    {
        return _ht.Erase(position);
    }

    // bucket
    size_t bucket_count()
    {
        return _ht.BucketCount();
    }
    size_t bucket_size(const K& key)
    {
        return _ht.BucketSize(key);
    }
private:
    HT _ht;
};

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

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

相关文章

京东API商品详情接口丨关键词搜索接口丨优惠券接口丨京东店铺所有商品接口

京东API商品详情接口&#xff0c;关键词搜索接口&#xff0c;优惠券接口&#xff0c;京东店铺所有商品接口如下&#xff1a; item_get-获得JD商品详情 公共参数 请求地址: https://o0b.cn/anzexi 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&…

相关关系与因果关系

本文来自&#xff1a;https://towardsdatascience.com/a-step-by-step-guide-in-detecting-causal-relationships-using-bayesian-structure-learning-in-python-c20c6b31cee5 作者&#xff1a;Erdogan Taskesen 在机器学任务中&#xff0c;确定变量间的因果关系&#xff08;c…

BUUCTF 荷兰宽带数据泄露 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 下载附件&#xff0c;解压得到一个.bin文件。 密文&#xff1a; 解题思路&#xff1a; 1、刚开始没什么思路&#xff0c;看了别人的题解&#xff0c;了解到一个新工具RouterPassView。大多数现代路由器都可以让您备…

遍历一个对象,并得出所对应的

var dates {//定义的对象year:now.getFullYear(),month:now.getMonth()1,date:now.getDate(),hour:now.getHours(),minute:now.getMinutes(),second:now.getSeconds() }//开始遍历循环 var val; for (val in dates){console.log(对象名称&#xff1a;val-对象的值&#xff1a;…

PDF文件标题修改方法

目录 一、PDF文件的标题和名称 二、标题修改方法 1.浏览器打开PDF Editor Free网站 2.点击Free Oline 3.选择第三个从本地上传PDF附件 4.将附件上传&#xff0c;两种方法都可以​编辑 5.等待加载&#xff0c;附件大的情况下会有些慢&#xff0c;耐心等待即可 6. 导入文…

MATLAB中uiwait函数用法

目录 语法 说明 示例 等待对警报对话框的响应 等待对模态消息对话框的响应 等待按钮按下 等待超时 uiwait函数功能是阻止程序执行并等待恢复。 语法 uiwait uiwait(f) uiwait(f,timeout) 说明 uiwait 阻止程序执行&#xff0c;直至调用了 uiresume 函数或删除了当前…

Linux_包管理_apt相关命令的使用

以思维导图的形式整理了下apt相关的命令&#xff0c;便于查阅&#xff0c;主要分为软件源、安装卸载升级、查看&#xff1b; 1、软件源 2、安装、卸载、升级 3、查看 参考链接&#xff1a; Using apt Commands in Linux [Ultimate Guide] 6. apt更新软件源 — 快速使用手册—…

2023年10月国产数据库大事记-墨天轮

本文为墨天轮社区整理的2023年10月国产数据库大事件和重要产品发布消息。 目录 10月国产数据库大事记 TOP1010月国产数据库大事记&#xff08;时间线&#xff09;产品/版本发布兼容认证代表厂商大事记厂商活动排行榜新增数据库相关资料 10月国产数据库大事记 TOP10 10月国产…

Masked Relation Learning for DeepFake Detection

一、研究背景 1.现有deepfake检测方法大多关注于局部伪影或面部不协调&#xff0c;较少挖掘局部区域间的关系。 2.现有关系挖掘类的工作往往忽略了关系信息的传播。 3.遮挡建模在减轻信息冗余的同时促进高级语义信息&#xff08;诱导性偏差较小&#xff09;的挖掘&#xff0c;有…

小型洗衣机哪个牌子好用又耐用?性价比高的内衣洗衣机测评

大多数的用户对自己的内衣、内裤的卫生非常注重&#xff0c;而往往许多的用户都会选择自己手工清洗&#xff0c;但是单靠手工洗只是表面的污渍&#xff0c;并不能完全去除贴身衣物上的各种细菌。现在通过内衣裤感染到细菌真的是越来越多&#xff0c;所以我们对内衣裤的清洗频次…

LeetCode——OJ题之二叉树【上】

✏️✏️✏️今天给大家分享几道二叉树OJ题&#xff01; &#x1f61b;&#x1f61b;&#x1f61b;希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xff0c;大家一起学习交流&#xff01; 动动你们发财的小手&#xff0c;点点关注点点赞&#xff…

企业设备巡检的痛点和解决方案

在设备巡检过程中&#xff0c;企业常面临多种痛点。首先&#xff0c;信息管理不足是一个关键问题&#xff0c;企业往往缺乏全面、准确的设备信息记录&#xff0c;这导致巡检工作缺乏针对性和效率。其次&#xff0c;巡检流程的非标准化使得巡检结果出现不一致&#xff0c;重要的…

ssm+vue的物流配送管理系统(有报告)。Javaee项目,ssm vue前后端分离项目

演示视频&#xff1a; ssmvue的物流配送管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 项目介…

机器学习-搜索技术:从技术发展到应用实战的全面指南

在本文中&#xff0c;我们全面探讨了人工智能中搜索技术的发展&#xff0c;从基础算法如DFS和BFS&#xff0c;到高级搜索技术如CSP和优化问题的解决方案&#xff0c;进而探索了机器学习与搜索的融合&#xff0c;最后展望了未来的趋势和挑战&#xff0c;提供了对AI搜索技术深刻的…

机器学习基础之《回归与聚类算法(7)—无监督学习K-means算法》

一、什么是无监督学习 1、没有目标值—无监督学习 一家广告平台需要根据相似的人口学特征和购买习惯将美国人口分成不同的小组&#xff0c;以便不同的用户采取不同的营销策略。 Airbnb需要将自己的房屋清单分组成不同的社区&#xff0c;以便用户能更轻松地查阅这些清单&#x…

chatGPT真的会改变我们的生活吗?

先不说生活影响有多大&#xff0c;工作职场影响很大&#xff0c;现在在职场&#xff0c;随处可见Chat GPT的身影 OpenAI 开发的 ChatGPT 和类似的人工智能工具在短时间内不会取代我们的工作。但是&#xff0c;在科技、媒体等许多行业中&#xff0c;它们可以帮助员工更好、更快地…

正版软件|Soundop 专业音频编辑器,实现无缝的音频制作工作流程

关于Soundop Soundop 音频编辑器 直观而专业的音频编辑软件&#xff0c;用于录制、编辑、混合和掌握音频内容。 Soundop 是一款适用于 Windows 的专业音频编辑器&#xff0c;可在具有高级功能的直观灵活的工作区中录制、编辑和掌握音频并混音轨道。音频文件编辑器支持波形和频谱…

企业云盘:作用和特点全解析

一、什么是企业云盘&#xff1f; 企业云盘是基于云计算理念推出的企业数据网络存储和管理解决方案&#xff0c;利用互联网后台数据中心的海量计算和存储能力为企业提供数据汇总分发、存储备份和管理等服务。 简单来讲&#xff0c;企业云盘其实就是企业网盘&#xff0c;是一种为…

一则DNS被重定向导致无法获取MySQL连接处理

同事反馈xwik应用端报java exception 获取MySQL连接超时无法连接到数据库实例 经过告警日志发现访问进来的IP地址数据库端无法被解析&#xff0c;这里可以知道问题出现在Dns配置上了 通过以上报错检查/etc/resolve.conf 发现namesever 被重定向设置成了114.114.114.114 域名 …
最新文章