C++【初识哈希】

news2024/11/15 21:54:50

✨个人主页: 北 海
🎉所属专栏: C++修行之路
🎃操作环境: Visual Studio 2019 版本 16.11.17

成就一亿技术人


文章目录

  • 🌇前言
  • 🏙️正文
    • 1、哈希思想
    • 2、哈希函数
      • 2.1、哈希函数的设计原则
      • 2.2、常见的哈希函数
    • 3、哈希冲突
      • 3.1、冲突的原因
      • 3.2、解决方法
    • 4、unordered_set / unordered_map
      • 4.1、使用
      • 4.2、与 set/map 的区别
      • 4.3、性能对比
  • 🌆总结


🌇前言

哈希(Hash)是一个广泛的概念,其中包括哈希表、哈希冲突、哈希函数等,核心为 元素(键值)存储位置(哈希值) 之间的映射关系,哈希值 可以通过各种哈希函数进行计算,需要尽量确保 “唯一性”,避免冲突,除此之外,哈希函数还可用于 区块链 中,计算 区块头(Head)中的信息,本文将带你认识哈希,学习其中的各种知识

图示


🏙️正文

1、哈希思想

哈希(Hash) 是一种 映射 思想,规定存在一个唯一的 哈希值键值 之前建立 映射 关系,由这种思想而构成的数据结构称为 哈希表(散列表)

图示

图示

哈希表中的数据查找时间可以做到 O(1)

这是非常高效的,比 AVL树 还要快

哈希表插入数据查找数据 的步骤如下:

  • 插入数据:根据当前待插入的元素的键值,计算出哈希值,并存入相应的位置中
  • 查找数据:根据待查找元素的键值,计算出哈希值,判断对应的位置中存储的值是否与 键值 相等

比如在 数组 中利用哈希思想,构建哈希表,存储数据:549273855

假设此时 数组 的大小 capacity8哈希函数 计算哈希值:HashI = key % capacity

数据存储如下:

图示

显然,这个哈希表并没有把所有位置都填满,数据分布无序且分散

因此,哈希表 又称为 散列表


2、哈希函数

元素对应的存储位置(哈希值)需要通过 哈希函数 进行计算,哈希函数 并非固定不变,可以根据需求自行设计

2.1、哈希函数的设计原则

在进行 映射 时,要尽量确保 唯一性,尽量让每一个元素都有自己的 映射 位置,这样在查找时,才能快速定位 元素

哈希函数 的设计原则如下:

  1. 哈希函数的定义域必须包括需要存储的全部键值,且如果哈希表允许有 m 个地址,其值域为 [0, m-1]
  2. 哈希函数计算出来的哈希值能均匀分布在整个哈希表中
  3. 哈希函数应该尽可能简单、实用

哈希函数 的设计没必要动用太多数学高阶知识,要确保 实用性

2.2、常见的哈希函数

哈希函数 的发展已经有很多年历史了,在前辈的实践之下,留下了这些常见的 哈希函数

1、直接定址法(常用)

函数原型:HashI = A * key + B

  • 优点:简单、均匀
  • 缺点:需要提前知道键值的分布情况
  • 适用场景:范围比较集中,每个数据分配一个唯一位置

2、除留余数法(常用)

假设 哈希表 的大小为 m

函数原型:HashI = key % p (p < m)

  • 优点:简单易用,性能均衡
  • 缺点:容易出现哈希冲突,需要借助特定方法解决
  • 适用场景:范围不集中,分布分散的数据

3、平方取中法(了解)

函数原型:HashI = mid(key * key)

  • 适用场景:不知道键值的分布,而位数又不是很大的情况

假设键值为 1234,对其进行平方后得到 1522756,取其中间的三位数 227 作为 哈希值

4、折叠法(了解)

折叠法是将键值从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按 哈希表 表长,取后几位作为散列地址

  • 适用场景:事先不需要知道键值的分布,且键值位数比较多

假设键值为 85673113,分为三部分 85673113,求和:1600,根据表长(假设为 100),哈希值 就是 600

5、随机数法(了解)

选择一个随机函数,取键值的随机函数值为它的 哈希值

函数原型:HashI = rand(key)
其中 rand 为随机数函数

  • 适用场景:键值长度不等时

哈希函数 还有很多很多种,最终目的都是为了计算出重复度低的 哈希值

最常用的是 直接定址法除留余数法


3、哈希冲突

哈希冲突(哈希碰撞) 是面试中的常客,可以通过一个 哈希冲突 间接引出 哈希 中的其他知识点

3.1、冲突的原因

哈希值键值 通过 哈希函数 计算得出的 位置标识符,难以避免重复问题

比如在上面的 哈希表 中,存入元素 20哈希值 HashI = 20 % 8 = 4,此时 4 位置中并没有元素,可以直接存入

图示

但是如果继续插入元素 36哈希值 HashI = 36 % 8 = 4,此时 4 位置处已经有元素了,无法继续存入,此时就发生了 哈希冲突

图示

不同的 哈希函数 引发 哈希冲突 的概率不同,但最终都会面临 哈希冲突 这个问题,因此需要解决一些方法,解决哈希冲突

3.2、解决方法

主要的解决方法有两种:闭散列开散列

闭散列(开放定址法)

规定:当哈希表中存储的数据量 与 哈希表的容量 比值(负载因子)过大时,扩大哈希表的容量,并重新进行映射

因为有 负载因子 的存在,所以 哈希表是一定有剩余空间的

当发生 哈希冲突 时,从冲突位置向后探测,直到找到可用位置

图示

像这种线性探测(暴力探测)可以解决 哈希冲突 问题,但会带来新的问题:踩踏

踩踏:元素的存储位置被别人占用了,于是也只能被迫线性探测,引发连锁反应,插入、查找都会越来越慢

哈希冲突 越多,效率 越低

优化方案:二次探测,每次向后探测 i ^ 2 步,尽量减少踩踏

尽管如此,闭散列 的实际效果 不尽人意,因为其本质上就是一个 零和游戏,实际中还是 开散列 用的更多一些

开散列(链地址法、开链法、哈希桶)

所谓 开散列 就在原 存储位置 处带上一个 单链表,如果发生 哈希冲突,就将 冲突的值依次挂载即可

因此也叫做 链地址法、开链法、哈希桶

开散列 中不需要 负载因子,如果每个位置都被存满了,直接扩容就好了,当然扩容后也需要重新建立映射关系

图示

开散列 中进行查找时,需要先根据 哈希值 找到对应位置,并在 单链表 中进行遍历

一般情况下,单链表的长度不会太长的,因为扩容后,整体长度会降低

如果 单链表 真的过长了(几十个节点),我们还可以将其转为 红黑树,此时效率依旧非常高

图示
图示
图片出自:2021dragon

值得一提的是 哈希表(开散列法)最快时间复杂度为 O(N),平均是 O(1)

哈希表(开散列法)快排 一样很特殊,时间复杂度不看最坏的,看 平均时间复杂度,因为 最快的情况几乎不可能出现

以上就是解决 哈希冲突 的两种方法,后面在模拟实现 哈希表 时会详细讲解


4、unordered_set / unordered_map

哈希表 最xx的地方在于 查找速度非常快

快过红黑树!

因此在 C++11 标准中,利用 哈希表 作为底层结构,重写了 set / map,就是 unordered_set / unordered_map

图示
图片出自:C++新特性之三:标准库中的新增容器

4.1、使用

哈希表 版的 unordered_set / unordered_map红黑树 版的 set / map 在功能上 没有差别

可以直接无缝衔接

关于 setmap 的使用 详见:C++【set 和 map 学习及使用】

unordered_set 的使用

#include <iostream>
#include <vector>
#include <unordered_set>
using namespace std;

int main()
{
	vector<int> arr = { 7,3,6,9,3,1,6,2 };
	unordered_set<int> s1(arr.begin(), arr.end());

	//迭代器遍历
	cout << "迭代器遍历结果: ";
	unordered_set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//判空、求大小
	cout << "===================" << endl;
	cout << "empty(): " << s1.empty() << endl;
	cout << "size(): " << s1.size() << endl;
	cout << "max_size(): " << s1.max_size() << endl;

	//插入元素
	cout << "===================" << endl;
	cout << "insert(5): ";
	s1.insert(5);
	for (auto e : s1) cout << e << " ";
	cout << endl;

	//删除元素
	cout << "===================" << endl;
	cout << "erase(6): ";
	s1.erase(6);
	for (auto e : s1) cout << e << " ";
	cout << endl;

	//交换、查找、清理
	cout << "===================" << endl;
	unordered_set<int> s2(arr.begin() + 5, arr.end());
	s1.swap(s2);
	cout << "s1: ";
	for (auto e : s1) cout << e << " ";
	cout << endl;

	cout << "s2: ";
	for (auto e : s2) cout << e << " ";
	cout << endl;

	cout << "s1.find(9): ";
	cout << (s1.find(9) != s1.end()) << endl;

	cout << "s2.clear(): " << endl;
	s2.clear();

	cout << "s1: ";
	for (auto e : s1) cout << e << " ";
	cout << endl;

	cout << "s2: ";
	for (auto e : s2) cout << e << " ";
	cout << endl;

	return 0;
}

图示

unordered_map 的使用

#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
using namespace std;

int main()
{
	vector<pair<string, int>> arr{ make_pair("z", 122), make_pair("a", 97),  make_pair("K", 75), make_pair("h", 104), make_pair("B", 66) };

	unordered_map<string, int> m1(arr.begin(), arr.end());

	//迭代器遍历
	cout << "迭代器遍历结果: ";
	unordered_map<string, int>::iterator it = m1.begin();
	while (it != m1.end())
	{
		cout << "<" << it->first << ":" << it->second << "> ";
		++it;
	}
	cout << endl;

	//判空、求大小、解引用
	cout << "===================" << endl;
	cout << "empty(): " << m1.empty() << endl;
	cout << "size(): " << m1.size() << endl;
	cout << "max_size(): " << m1.max_size() << endl;
	cout << "m1[""a""]: " << m1["a"] << endl;

	//插入元素
	cout << "===================" << endl;
	cout << "insert(""a"", 5): ";
	m1.insert(make_pair("a", 5));
	for (auto e : m1) cout << "<" << e.first << ":" << e.second << "> ";
	cout << endl;

	//删除元素
	cout << "===================" << endl;
	cout << "erase(""a""): ";
	m1.erase("a");
	for (auto e : m1) cout << "<" << e.first << ":" << e.second << "> ";
	cout << endl;

	//交换、查找、清理
	cout << "===================" << endl;
	unordered_map<string, int> m2(arr.begin() + 2, arr.end());
	m1.swap(m2);
	cout << "m1.swap(m2)" << endl;
	cout << "m1: ";
	for (auto e : m1) cout << "<" << e.first << ":" << e.second << "> ";
	cout << endl;

	cout << "m2: ";
	for (auto e : m2) cout << "<" << e.first << ":" << e.second << "> ";
	cout << endl;

	cout << "m1.find(""B""): ";
	cout << (m1.find("B") != m1.end()) << endl;

	cout << "m2.clear()" << endl;
	m2.clear();

	cout << "m1: ";
	for (auto e : m1) cout << "<" << e.first << ":" << e.second << "> ";
	cout << endl;

	cout << "m2: " << endl;
	for (auto e : m2) cout << "<" << e.first << ":" << e.second << "> ";
	cout << endl;

	return 0;
}

图示

照搬 set / map 使用的代码,可以无缝衔接,所以说正常使用就好了,无非就是名字长一些

C++ 为了确保向前兼容性,无法修改原来的名字,因此只能加上 unordered 以示区分
相比之下,Java 中两个不同版本的 set / map 就非常容易区分

  • 红黑树版:TreeSet / TreeMap
  • 哈希表版:HashSet / HashMap

注意: unordered_set / unordered_map 默认不允许出现键值冗余,如果相要存储重复的数据,可以使用 unordered_multiset / unordered_multimap

4.2、与 set/map 的区别

哈希表 版 与 红黑树 版的主要区别有两个

  • 迭代器:哈希表版 是单向迭代器,红黑树版 是双向迭代器
  • 遍历结果:哈希表版 无序,红黑树 有序

因为 unordered_set单向迭代器,自然无法适配 反向迭代器

结果

两种不同底层数据结构的遍历结果:

#include <iostream>
#include <vector>
#include <set>
#include <unordered_set>

using namespace std;

int main()
{
	vector<int> v = { 2,3,45,2,345,231,21,543,121,34 };
	set<int> TreeSet(v.begin(), v.end());
	unordered_set<int> HashSet(v.begin(), v.end());

	cout << "TreeSet: ";
	for (auto e : TreeSet)
		cout << e << " ";
	cout << endl << endl;

	cout << "HashSet: ";
	for (auto e : HashSet)
		cout << e << " ";
	cout << endl;
	return 0;
}

图示

显然,哈希表 实现的 unordered_set / unordered_map 遍历结果为 无序

哈希表 究竟比 红黑树 强多少?

4.3、性能对比

下面是性能测试代码,包含 大量重复、部分重复、完全有序 三组测试用例,分别从 插入、查找、删除 三个维度进行对比

注:测试性能用的是 Release 版,这里的基础数据量为 100 w

#include <iostream>
#include <vector>
#include <set>
#include <unordered_set>

using namespace std;

int main()
{
	const size_t N = 1000000;

	unordered_set<int> us;
	set<int> s;

	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; ++i)
	{
		//v.push_back(rand());	//大量重复
		//v.push_back(rand()+i);	//部分重复
		//v.push_back(i);	//完全有序
	}

	size_t begin1 = clock();
	for (auto e : v)
	{
		s.insert(e);
	}
	size_t end1 = clock();
	cout << "set insert:" << end1 - begin1 << endl;

	size_t begin2 = clock();
	for (auto e : v)
	{
		us.insert(e);
	}
	size_t end2 = clock();
	cout << "unordered_set insert:" << end2 - begin2 << endl;


	size_t begin3 = clock();
	for (auto e : v)
	{
		s.find(e);
	}
	size_t end3 = clock();
	cout << "set find:" << end3 - begin3 << endl;

	size_t begin4 = clock();
	for (auto e : v)
	{
		us.find(e);
	}
	size_t end4 = clock();
	cout << "unordered_set find:" << end4 - begin4 << endl << endl;

	cout << s.size() << endl;
	cout << us.size() << endl << endl;;

	size_t begin5 = clock();
	for (auto e : v)
	{
		s.erase(e);
	}
	size_t end5 = clock();
	cout << "set erase:" << end5 - begin5 << endl;

	size_t begin6 = clock();
	for (auto e : v)
	{
		us.erase(e);
	}
	size_t end6 = clock();
	cout << "unordered_set erase:" << end6 - begin6 << endl << endl;
	
	return 0;
}

插入大量重复数据

图示

插入数据 大量重复 ---- 结果:

  • 插入:哈希 比 红黑 快 88%
  • 查找:哈希 比 红黑 快 100%
  • 删除:哈希 比 红黑 快 37%

插入部分重复数据

图示

插入数据 部分重复 ---- 结果:

  • 插入:哈希 与 红黑 差不多
  • 查找:哈希 比 红黑 快 100%
  • 删除:哈希 比 红黑 快 41%

插入大量重复数据

图示

插入数据 完全有序 ---- 结果:

  • 插入:哈希 比 红黑 慢 52%
  • 查找:哈希 比 红黑 快 100%
  • 删除:哈希 比 红黑 慢 58%

总的来说,在数据 随机 的情况下,哈希各方面都比红黑强,在数据 有序 的情况下,红黑更胜一筹

单就 查找 这一个方面来说:哈希 一骑绝尘,远远的将红黑甩在了身后


🌆总结

以上就是本次关于 C++【初识哈希】的全部内容了,在本文中,我们主要学习了哈希的相关知识,包括哈希思想、哈希函数、哈希冲突及其解决方法,最后还学习了 C++11 中基于哈希表的新容器,见识了哈希表查找的快,不是一般的快。在下一篇文章中,我们将会对哈希表进行模拟实现,同时也会用一张哈希表同时封装实现 unordered_setunordered_map


星辰大海

相关文章推荐

C++ 进阶知识

C++【一棵红黑树封装 set 和 map】

C++【红黑树】

C++【AVL树】

C++【set 和 map 学习及使用】

C++【二叉搜索树】

C++【多态】

C++【继承】

STL 之 泛型思想

C++【模板进阶】

C++【模板初阶】

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

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

相关文章

曹操最有名的4首诗

在中国帝王级的人物中间&#xff0c;真正称得上诗人的&#xff0c;曹操必定是最杰出一个。 曹操简介&#xff1a; 曹操&#xff08;155年&#xff0d;220年正月庚子&#xff09;&#xff0c;字孟德&#xff0c;一名吉利&#xff0c;小字阿瞒&#xff0c;沛国谯&#xff08;今…

Redis实现用户签到

用户签到 现在许多的APP或者网上应用为了提高用户的活跃度&#xff0c;都会设置一个每日签到的活动&#xff0c;签到完成后会给予一定的奖励&#xff0c;从而达到其目的。 而记录用户的签到信息可以使用如下一张数据库表来进行记录。 在用户数量并不多的情况下&#xff0c;数…

排序算法--冒泡排序(Java语言)

冒泡排序&#xff08;Bubble Sort&#xff09;是啥&#xff1f; 冒泡排序是一种简单的排序算法。它重复地走访过要排序的元素列&#xff0c;依次比较两个相邻的元素&#xff0c;如果他们的顺序&#xff08;如从大到小、首字母从A到Z&#xff09;错误就把他们交换过来。走访元素…

Android:Lifecycle

整体架构 LifecycleOwner Lifecycle持有者 在ComponentActivity中new出来&#xff0c;所以Activity和Fragment自带 实现了LifecycleOwner接口的类 可以获得生命周期信息类Lifecycle Fragment和Activity都已经实现了该接口 Lifecycle Registry 生命周期注册器 可以提交生命周…

栈板识别的思考

0.啰嗦几句 最近公司又变动了&#xff0c;所以又做了一个关于视觉的项目。简单说就是栈板定位&#xff0c;主要应用在AGV叉车上&#xff0c;当然这一套流程基本适用于所有的视觉项目。主要是看算法的设计和一些人为的经验。 1.结果图 可以看到在图像上3个托盘都分割出来了&am…

青少年机器人技术一级核心知识点:机械结构及模型(三)

随着科技的不断进步&#xff0c;机器人技术已经成为了一个重要的领域。在这个领域中&#xff0c;机械结构是机器人设计中至关重要的一部分&#xff0c;它决定了机器人的形态、运动方式和工作效率。对于青少年机器人爱好者来说&#xff0c;了解机械结构的基础知识&#xff0c;掌…

2024考研408-计算机组成原理第七章-输入输出系统学习笔记

文章目录 前言一、IO系统基本概念1.1、输入/输出系统基本概念1.1.1、现代计算机的结构1.1.2、常见的I/O设备1.1.3、主机与I/O设备如何进行交互&#xff1f;&#xff08;认识I/O接口&#xff09;1.1.4、I/O系统的基本组成&#xff08;I/O硬件、I/O软件&#xff09;本节回顾 1.2、…

标配8155,智驾延续「高低配」,小鹏G6能否挽回颜面?

对于小鹏汽车来说&#xff0c;G6就是最后的尊严。 高工智能汽车研究院监测数据显示&#xff0c;2023年1-5月&#xff0c;小鹏汽车交付量为3.22万辆&#xff0c;同比上年同期下滑39.81%&#xff0c;是「蔚小理」三家新势力中唯一下滑的一家。 去年上市的G9&#xff08;何小鹏口中…

基于声音信号的工业设备异常检测

异常检测主要目标是将异常事件与正常事件区分开来&#xff0c;因此才有了“异常”一词。本文将介绍基于声音信号的工业机械异常检测&#xff0c;使用的数据集是MIMII声音数据集&#xff0c;该数据集很容易在网上获得。 异常检测的任务可以通过多种方式实现。其中最简单的一种方…

Hadoop基础——HDFS知识点梳理

HDFS基础知识 1. 介绍一下HDFS组成架构&#xff1f; 组成部分&#xff1a; HDFS Client,NameNode,DataNodeSecondary NameNode( HA模式下是 StandBy NameNode) Client: 客户端 文件切分&#xff0c;文件上传HDFS时&#xff0c;client将文件切分成一个一个的block&#xff0…

玩转中文AI模型攻略——ModaHub魔搭社区攻略

目录 1. 注册和登录&#xff1a; 2. 浏览和搜索&#xff1a; 3. ModaHub排行榜&#xff1a; 4. AI应用导航&#xff1a; 5. 向量数据库&#xff1a; 6. 企业Chatbot&#xff1a; ModaHub魔搭社区是一个中文AI模型开源社区&#xff0c;旨在为开发者和研究者提供一个交流、…

抓住出海中东热潮,茄子科技(海外SHAREit Group)有效助力游戏企业出海

近年来&#xff0c;随着国际形势的紧张&#xff0c;不少中国出海企业在海外市场营收受到了不同程度的影响。为摆脱这一困境&#xff0c;大家开始将目光瞄准了海外新兴市场&#xff0c;其中中东以其高速的增长&#xff0c;量利双收的回报赢得了不少中国出海企业的青睐。 与此同…

FastDDS源码剖析:源码结构分析

include: 包含Fast-DDS的头文件。这些头文件定义了库使用的公共API和数据结构。 src: 包含Fast-DDS库的源代码文件。这包括各种组件的实现&#xff0c;例如发布者、订阅者、主题和RTPS协议。 cpp: c实现Fast-DDS的主要源代码。 pcp -fastdds: Fast-DDS公共API的实现。 rtps: …

【AI】清华开源中英双语对话模型ChatGLM2-6B本地安装笔记

清华开源中英双语对话模型ChatGLM2-6B本地安装笔记 首先&#xff0c;直接上资源&#xff0c;网盘中是ChatGLM2-6B源码及模型文件&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1DciporsVT-eSiVIAeU-YmQ 提取码&#xff1a;cssa 官方的Readme已经很详尽了&#xff0…

Unreal Engine游戏引擎

Unreal Engine&#xff08;下文简称为UE&#xff09;&#xff0c;是一款由Epic Games开发的游戏引擎&#xff0c;用于创建电子游戏、虚拟现实和增强现实应用、数字孪生等内容。UE支持实时渲染、高品质的图形效果和物理模拟、可扩展的蓝图视觉脚本语言&#xff0c;以及跨平台和多…

Appium Server GUI + Appium Inspector 定位UI元素

Appium Server GUI 下载 Appium Server GUI 安装包并完成安装&#xff0c;完成安装后启动&#xff0c;点击 startServer 快速启动 Appium 服务。 Appium Inspector 下载 Appium Inspector 安装包并完成安装&#xff0c;完成安装后启动&#xff0c;填入以下内容。 Remote Hos…

蓝牙运动耳机推荐、最好用的蓝牙运动耳机排名

在现代快节奏的生活中&#xff0c;健康和锻炼成为越来越多人追求的目标。无论是户外跑步、健身房锻炼&#xff0c;还是徒步旅行&#xff0c;一个好的运动耳机是不可或缺的伴侣。它能为您提供高品质的音乐享受&#xff0c;同时保持舒适的佩戴体验&#xff0c;让您在运动中时刻保…

基于Python和MD5算法的公寓管理系统设计与实现-计算机毕设 附源码77723

基于Python和MD5算法的公寓管理系统设计与实现 摘 要 本论文主要论述了如何使用Python开发一个公寓管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述公寓管理系统的当前背景以及…

jetson nano如何读取pwm波信号?通过编码器读取车速,使用sbus串口读取,解决方案

jetson nano如何读取pwm波信号&#xff1f; 通常我们采用的是通过gpio读取&#xff0c;但是linux系统的时钟特别的不准导致计算的不对。 所以我推荐用sbus 用外围硬件&#xff0c;有pwm转sbus设备&#xff0c; 这个设备可以将pwm波转换位串口数据&#xff0c;我们只需要将输出的…

GPT上车只为人车交互?车企还在憋大招

ChatGPT改造汽车的序幕已经拉开&#xff0c;奔驰率先出手。 不久前&#xff0c;梅赛德斯-奔驰将ChatGPT集成到了车机上&#xff0c;开启了为期三个月的测试&#xff0c;结果显示&#xff0c;它的语音助手不仅可以完成简单地指令&#xff0c;还能进行连续多轮对话&#xff0c;理…