unordered_mapset

文章目录

  • Ⅰ. 使用
    • a. unordered_map
    • b. unordered_set
  • Ⅱ. 哈希表
    • a. 闭散列:线性探测
      • 1.仿函数,2.插入,3.删除,4.查找,5.扩容
    • b. 开散列:哈希桶
      • 1.仿函数,2.插入,3.删除,4.查找,5.扩容
  • Ⅲ. 封装实现
    • a. HashTable的封装完善
    • b. unordered_set
    • c. unordered_map

前言:unordered_map & unordered_set 是一对关联式容器,与map,set功能上类似,分别是KV,K的模型,其底层的实现是哈希表,所不同的是里边的存储是无序的

Ⅰ. 使用

a. unordered_map

函数名称函数功能
empty()返回表是否为空
size()返回存表中数据的个数
[ K ] & at( K )返回对应的V
find(K)返回迭代器
count(K)返回表中K的个数
insert({K,V})插入一个键值对,返回一个pair<Iterator,bool>
erase(K)删除KV并返回删除K前K的个数
void test_unordered_map1()
{unordered_map<int, int> um;// 1.有名对象构造pair<int, int> e(1, 1);um.insert(e);// 2.匿名对象构造um.insert(pair<int,int>(2,2));// 3.仿函数构造um.insert(make_pair(3, 3));// 4.多参数的隐式类型转换构造um.insert({ 4,4 });// 迭代器遍历unordered_map<int, int>::iterator it = um.begin();while (it != um.end()){cout << it->first << " " << it->second << endl;}cout << endl;// 迭代器遍历for(auto e: um){cout<<e.first<<" "<<e.second()<<endl;}
}
void test_unordered_map2()
{// 最厉害的为属 [ ]unordered_map<string, int> um;string s[] = { "安徽","河北","河南","河南","广东","河南","河南","广东" ,"河南","河南","广东" };for (auto& str : s){um[str]++;}// 这样就把城市的总次数统计出来了for (auto e : um){cout << e.first << " " << e.second << endl;}
}

b. unordered_set

unordered_set的使用和unordere_map的使用基本上类似,感兴趣的可以去看一下文档

C++文档unordered_set
C++文档unordered_map

Ⅱ. 哈希表

哈希表底层就是vector实现的
1. 每个值里边存放一个 pair,还有一个表示 状态信息 的 枚举常量,就是线性探测哈希表
2. vector里边每个值都挂一个 链表 ,就是哈希桶
3. 有了红黑树,为什么还要有哈希表呢?因为哈希表的 插入,删除,查找 三个操作在理论上都是O(1) 的时间复杂度
(数据碰撞除外),因为采用了值与位置的映射的存储关系,而红黑树的插入,查找,删除 理论上是O(logN) (不考
虑旋转变色)

a. 闭散列:线性探测

1.仿函数,2.插入,3.删除,4.查找,5.扩容

  1. 仿函数 :将一个对象的所有成员进行转换,转为size_t类型,这样方便进行下标的取模操作进而进行哈希映射,比如string 每个字符串ASC码相加返回,负数直接转化为无符号的整形就变为了整数,再比如一个Person类,可以对每个人的名字,身份证,年龄整合返回,这样的两个人对应数组的映射下标的很小概率相同

  2. 插入:将所给的K值与数组下标产生映射关系,将此值存入到此下标处,一般K都是字符串类,整数,都是整形家族的类型,此时就可以使用 %,用仿函数 将此K转为 size_t 类型,然后模上表的长度,找到映射位置,也有可能此处的位置被占,比如 19 9 都去 模表长为10 的位置,那么此时就需要从此下标处开始继续往后找到第一个没有被占用的下标位置,将值放入,并将状态设置为EXIST

  3. 查找:先使用仿函数 映射出下标,可能该处就是所要查找的值 直接返回,要么就进行线性探测继续往后找,状态为空,找不到,状态不为空先判断,判断一下是否是 存在状态 且 里边的值 为所要查找的值,若是,找到了返回,若不是,继续往后找

  4. 删除:调用查找函数,用函数调用返回的指针,将此处的状态改为删除状态,里边的数据不用抹除,因为我们进行的各种操作先判断的就是状态,而且如果抹除该设为多少呢?(抹除为几都不行,因为你下次插入的可能就是这个值)

  5. 扩容机制:当_n 除 表的长度超过 0.7的时候就开始扩容,大于0.7下一次的插入很容易造成哈希碰撞,小与0.7空间利用不充分;开一个新的vector,大小设为原来的二倍,将状态为存在的值,重新取模进行新表的映射,然后于旧的vector交换

#include<vector>enum STate
{EMPTY,EXIST,DELETE,
};template<class K, class V>
struct HashData
{pair<K, V> _kv;STate _state = EXIST;
};template<class K>
struct Hash
{size_t operator()(const K& k){return (size_t)k;}
}; 
template<>
struct Hash<string>
{size_t operator()(const string& s){size_t ret = 0;for (auto ch : s)ret = ret * 131 + ch;return ret;}
};template<class K,class V,class Hash = Hash<K>>
class HashTable
{
public:HashTable(size_t n = 10){_tables.resize(n);}bool insert(const pair<K, V>& kv){if(find(kv.first))return false;if (_n * 10 / _tables.size() >= 7){HashTable<K, V, Hash> newhash(_tables.size() * 2);for (size_t i = 0; i < _tables.size(); ++i){if (_tables[hashi]._state == EXIST)newhash.insert(_tables[hashi]._kv);}_tables.swap(newhash._tables);}Hash hs;size_t hashi = hs(kv.first) % _tables.size();while (_tables[hashi]._state == EXIST){++hashi;hashi %= _tables.size();}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;}HashData<K,V>* find(const K& k){Hash hs;size_t hashi = hs(k) % _tables.size();while (_tables[hashi]._state != EMPTY){if (_tables[hahsi]._kv.first == k && _tables[hashi]._state == EXIST){return &_tables[hashi];}else{++hashi;hashi %= _tables.size();}}return nullptr;}bool erase(const K& k){HashData<K, V>* ret = find(k);if (ret){ret->_state = DELETE;--_n;return true;}else{return false;}}
private:vector<HashData> _tables;size_t _n = 0;
};

b. 开散列:哈希桶

1.仿函数,2.插入,3.删除,4.查找,5.扩容

  1. 仿函数:同线性探测

  2. 插入:用仿函数映射出下标,找到该链表,对链表进行头插(可随便插入,但是头插的效率高)

  3. 删除:用仿函数先映射出下标,找到处于哪个位置的链表,遍历该链表进行此节点的前后链接关系

  4. 查找:用仿函数映射出下标,接下来进行链表的查找

  5. 扩容机制:当所有节点的个数总和 与 vector的大小一样时(负载因子为1),就开始扩容,将每个节点取下来重新映射关系,因为 vector 的空间大小 增大,所以要%一个新表的长度

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 Mod = KOfMod<K>>
class Hash
{typedef HashNode<K, V> Node;
public:Hash(size_t n = 10){_v.resize(n, nullptr);}~Hash(){for (int i = 0; i < _v.size(); i++){Node* cur = _v[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_v[i] = nullptr;}}bool insert(const pair<K, V>& kv){if (find(kv.first)) return false;Mod mod;if (_n == _v.size()){vector<Node*> newv(_n * 2, nullptr);for (int i = 0; i < _n; i++){Node* cur = _v[i];while (cur){Node* next = cur->_next;size_t hashi = mod(cur->_kv.first) % newv.size();cur->_next = newv[hashi];newv[hashi] = cur;cur = next;}_v[i] = nullptr;}_v.swap(newv);}size_t hashi = mod(kv.first) % _v.size();Node* newnode = new Node(kv);newnode->_next = _v[hashi];_v[hashi] = newnode;_n++;return true;}Node* find(const K& k){Mod mod;size_t hashi = mod(k) % _v.size();Node* cur = _v[hashi];while (cur){if (cur->_kv.first == k){return cur;}cur = cur->_next;}return nullptr;}bool erase(const K& k){Mod mod;size_t hashi = mod(k) % _v.size();Node* prev = nullptr;Node* cur = _v[hashi];while (cur){if (cur->_kv.first == k){Node* next = cur->_next;if (prev == nullptr){_v[hashi] = next;}else{prev->_next = next;}delete cur;return true;}else{prev = cur;cur = cur->_next;}}return false;}private:vector<Node*> _v;size_t _n = 0;
};

Ⅲ. 封装实现

a. HashTable的封装完善

#pragma once#include<vector>
#include<string>namespace hash_bucket
{template<class K>struct Hash{size_t operator()(const K& k){return (size_t)k;}};template<>struct Hash <std::string>{size_t operator()(const std::string& str){size_t ret = 0;for (auto ch : str){ret = ret * 131 + ch;}return ret;}};template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data),_next(nullptr){}};template<class K, class T, class KeyOfT, class Hash>class HashTable;// 哈希表的前置声明template<class K, class T,class Ref,class Ptr, class KeyOfT, class Hash>struct __HashTable_Iterator{typedef HashNode<T> Node;typedef __HashTable_Iterator<K, T,Ref,Ptr, KeyOfT, Hash> Self;Node* _node;const HashTable<K, T, KeyOfT, Hash>* _pht;__HashTable_Iterator(Node* node,const HashTable<K, T, KeyOfT, Hash>*pht):_node(node),_pht(pht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){KeyOfT kot;Hash hs;if (_node->_next){_node = _node->_next;}else{size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();++hashi;for (; hashi < _pht->_tables.size(); hashi++){Node* cur = _pht->_tables[hashi];if (cur){_node = cur;break;}}if (hashi == _pht->_tables.size()){_node = nullptr;}}return *this;}bool operator!=(const Self& s){return _node != s._node;}};template<class K,class T,class KeyOfT, class Hash>class HashTable{typedef HashNode<T> Node;public:template<class K, class T,class Ref,class Ptr, class KeyOfT, class Hash>friend struct __HashTable_Iterator;// 将迭代器设为 友元类,方便找位于表中的位置 实现++typedef __HashTable_Iterator<K, T, T&, T*, KeyOfT, Hash> iterator;typedef __HashTable_Iterator<K, T,const T&,const T*, KeyOfT, Hash> const_iterator;iterator begin(){for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return iterator(cur, this);}}return end();}iterator end(){return iterator(nullptr, this);}const_iterator begin() const{for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return const_iterator(cur, this);}}return end();}const_iterator end() const{return const_iterator(nullptr, this);}HashTable(size_t n = 10){_tables.resize(n, nullptr);}pair<iterator,bool> insert(const T& data){KeyOfT kot;iterator ret = find(kot(data));if (ret!=end())return make_pair(ret,false);Hash hs;if (_n == _tables.size()){std::vector<Node*> newtables(_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 = hs(kot(data)) % newtables.size();cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}size_t hashi = hs(kot(data)) % _tables.size();Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(iterator(newnode,this), true);}iterator find(const K& k){KeyOfT kot;Hash hs;size_t hashi = hs(k) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == k){return iterator(cur,this);}cur = cur->_next;}return end();}bool erase(const K& k){KeyOfT kot;Hash hs;size_t hashi = hs(k) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == k){Node* next = cur->_next;if (prev == nullptr){_tables[hashi] = next;}else{prev->_next = next;}delete cur;return true;}else{prev = cur;cur = cur->_next;}}return false;}private:std::vector<Node*> _tables;size_t _n = 0;};
}

b. unordered_set

#pragma once#include"HashTable.h"
namespace zy
{template<class K>class unordered_set{struct SetKeyOfT{const K& operator()(const K& k){return k;}};public:typedef typename hash_bucket::HashTable<K,const K, SetKeyOfT, hash_bucket::Hash<K>>::iterator iterator;typedef typename hash_bucket::HashTable<K,const K, SetKeyOfT, hash_bucket::Hash<K>>::const_iterator const_iterator;iterator begin(){return _hs.begin();}iterator end(){return _hs.end();}const_iterator begin() const{return _hs.begin();}const_iterator end() const{return _hs.end();}pair<iterator,bool> insert(const K& k){return _hs.insert(k);}iterator find(const K& k){return _hs.find(k);}bool erase(const K& k){return _hs.erase(k);}private:hash_bucket::HashTable<K,const K,SetKeyOfT,hash_bucket::Hash<K>> _hs;};void test_unordered_set1(){unordered_set<int> s;s.insert(1001);s.insert(11);s.insert(12);s.insert(23);}void test_unordered_set2(){string s[] = { "sort","left","right" };unordered_set<string> st;for (auto e : s){st.insert(e);}st.insert("parent");st.insert( "parent");}void test_unordered_set3(){unordered_set<int> s;s.insert(1);s.insert(12);s.insert(13);s.insert(21);unordered_set<int>::iterator cit1 = s.begin();unordered_set<int>::const_iterator cit2 = s.begin();}
}

c. unordered_map

#pragma once#include"HashTable.h"
namespace zy
{template<class K , class V>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename hash_bucket::HashTable<K, std::pair<const K, V>, MapKeyOfT, hash_bucket::Hash<K>>::iterator iterator;typedef typename hash_bucket::HashTable<K, std::pair<const K, V>, MapKeyOfT, hash_bucket::Hash<K>>::const_iterator const_iterator;iterator begin(){return _hs.begin();}iterator end(){return _hs.end();}const_iterator begin() const{return _hs.begin();}const_iterator end() const{return _hs.end();}pair<iterator, bool> insert(const pair<K, V>& kv){return _hs.insert(kv);}iterator find(const K& k){return _hs.find(k);}bool erase(const K& k){return _hs.erase(k);}V& operator[](const K& k){pair<iterator, bool> ret = _hs.insert(make_pair(k, V()));return ret.first->second;}private:hash_bucket::HashTable<K, std::pair<const K, V>,MapKeyOfT,hash_bucket::Hash<K>> _hs;};void test_unordered_map1(){int a[] = { 10001,12,13,25 };unordered_map<int, int> m;for (auto e : a){m.insert({ e,e });}m.insert({ 12,12 });for (auto e : m){cout << e.first << " " << e.second << endl;}}void test_unordered_map2(){string s[] = { "sort","left","right" };unordered_map<string, int> m;for (auto e : s){m.insert({ e,1 });}m.insert({ "parent",1 });m.insert({ "parent",1});for (auto e : m){cout << e.first << " " << e.second << endl;}}void func(const unordered_map<int, int>& m){unordered_map<int, int>::const_iterator it = m.begin();while (it != m.end()){cout << it->first << " " << it->second << endl;++it;}}void test_unordered_map3(){int a[] = { 10001,12,13,25 };unordered_map<int, int> m;for (auto e : a){m.insert({ e,e });}m.insert({ 12,12 });func(m);}void test_unordered_map4(){string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };unordered_map<string, int> countMap;for (auto& e : arr){countMap[e]++;}unordered_map<string, int>::iterator it = countMap.begin();while (it != countMap.end()){cout << it->first << ":" << it->second << endl;++it;}}
}

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

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

相关文章

释放Mac潜能,选择Magic Disk Cleaner for Mac

想要让Mac运行更加流畅、性能更加出色吗&#xff1f;那就选择Magic Disk Cleaner for Mac吧&#xff01; Magic Disk Cleaner for Mac v2.7.7激活版下载 这款软件是Mac用户的得力助手&#xff0c;它拥有强大的扫描和清理功能&#xff0c;能够迅速找出并删除硬盘上的无用文件和垃…

如何查看哪些组策略应用于你的电脑和用户帐户?这里有详细步骤

如果你希望在电脑上查看所有有效的组策略设置,以下是操作方法。 什么是Windows中的组策略 在Windows世界中,组策略为网络管理员提供了一种将特定设置分配给用户组或计算机组的方法。然后,无论何时组中的用户登录到联网的PC,或无论何时启动组中的PC,都会应用这些设置。 …

零基础学Java第二十三天之网络编程

网络编程 1. 网络编程 实现多台计算机之间实现数据的共享和传递&#xff0c;网络应用程序主要组成为&#xff1a; 网络编程IO流多线程 2. 网络编程三要素 网络通讯的模型&#xff1a;请求-响应&#xff0c;客户端-服务端 三要素&#xff1a;IP地址&#xff0c;端口&#…

头歌OpenGauss数据库-I.复杂查询第7关:李老师教的课程成绩最低的学生

任务描述 本关任务:根据提供的表和数据,查询选修“李四”老师所授课程的学生中,成绩最低的学生信息(具体输出信息请查看测试说明)及其成绩。 student表数据: s_ids_names_sex01Mia女02Riley男03Aria女04Lucas女05Oliver男06Caden男07Lily女08Jacob男course表数据: c_idc…

谷歌Google广告投放优势和注意事项!

谷歌Google作为全球最大的搜索引擎&#xff0c;谷歌不仅拥有庞大的用户基础&#xff0c;还提供了高度精准的广告投放平台&#xff0c;让广告主能够高效触达目标受众&#xff0c;实现品牌曝光、流量增长乃至销售转化的多重目标&#xff0c;云衔科技以专业服务助力您谷歌Google广…

【MySQL】库的基础操作

&#x1f30e;库的操作 文章目录&#xff1a; 库的操作 创建删除数据库 数据库编码集和校验集 数据库的增删查改       数据库查找       数据库修改 备份和恢复 查看数据库连接情况 总结 前言&#xff1a;   数据库操作是软件开发中不可或缺的一部分&#xff0…

pod进阶—资源限制以及探针检查

一、资源限制 1.1 资源限制定义&#xff1a; 当定义Pod时可以选择性的为每个容器设定所需要的资源数量。最常见的可设定资源是CPU和内存大小&#xff0c;以及其他类型的资源。 1.2 资源限制request和limit资源约束 ①当为 Pod 中的容器指定了 request 资源时&#x…

百度文心大模型宣布两大主力模型ENIRE Speed、ENIRE Lite免费用

今日&#xff0c;百度智能云宣布文心大模型的两款主力模型ENIRE Speed、ENIRE Lite全面免费&#xff0c;即刻生效。这两款大模型今年3月发布&#xff0c;均支持8K和128k上下文长度。据了解&#xff0c;ERNIE Lite是百度自研的轻量级大语言模型&#xff0c;兼顾优异的模型效果与…

透视App投放效果,Xinstall助力精准分析,让每一分投入都物超所值!

在移动互联网时代&#xff0c;App的推广与投放成为了每一个开发者和广告主必须面对的问题。然而&#xff0c;如何精准地掌握投放效果&#xff0c;让每一分投入都物超所值&#xff0c;却是一个令人头疼的难题。今天&#xff0c;我们就来谈谈如何通过Xinstall这个专业的App全渠道…

【代码】Goc小游戏

1.口算测试 int a,b,lv1,score0,nd,yu,da,day,j,s_j0,cuo0; int main(){p.picL(1,"boy0.png").picL(2,"boy1.png").picL(3,"boy2.png").picL(4,"boy3.png");p.rr(1000,1000,14).speed(10).up().hide();p.moveTo(0,150).text("加…

372. 超级次方

题目 你的任务是计算 ab 对 1337 取模&#xff0c;a 是一个正整数&#xff0c;b 是一个非常大的正整数且会以数组形式给出。 示例 1&#xff1a; 输入&#xff1a;a 2, b [3] 输出&#xff1a;8 示例 2&#xff1a; 输入&#xff1a;a 2, b [1,0] 输出&#xff1a;1024…

【二叉树】LeetCode.144:二叉树的前序遍历(小细节把握)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;初阶初阶结构刷题 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 1.题目描述&#xff1a;​编辑 2.问题分析&#xff1a; &#x1f354;函数解读&#xff1a; …

粤嵌—2024/5/23—不同路径 ||(✔)

代码实现&#xff1a; int uniquePathsWithObstacles(int **obstacleGrid, int obstacleGridSize, int *obstacleGridColSize) {int x obstacleGridSize, y obstacleGridColSize[0];int dp[x][y];memset(dp, 0, sizeof(int) * x * y);for (int j 0; j < y && obs…

MySQL为什么会选错索引

有的时候&#xff0c;我们加了索引&#xff0c;也不一定最终查询语句就能用上索引&#xff0c;因为Innodb要不要使用索引&#xff0c;该使用哪个索引是优化器决定的&#xff0c;它是根据成本&#xff08;代价&#xff09;预估来选择的&#xff0c;他会倾向于选择一个成本最低的…

下载 Hugging Face 中的模型文件

下载 Hugging Face 中的模型文件 1. Hugging Face Hub2. ggerganov/whisper.cpp3. 点击图标下载文件4. Clone this model repository5. Using the Hugging Face Client Library6. Using GitReferences 1. Hugging Face Hub The Hugging Face Hub is a platform with over 350k…

JavaSE 学习记录

1. Java 内存 2. this VS super this和super是两个关键字&#xff0c;用于引用当前对象和其父类对象 this 关键字&#xff1a; this 关键字用于引用当前对象&#xff0c;即调用该关键字的方法所属的对象。 主要用途包括&#xff1a; 在类的实例方法中&#xff0c;通过 this …

wetool企业版使用教程及下载方式 微兔该如何使用 wetool还能用吗 wetool扳手工具wetool操作方法难吗 wetool有哪些功能

今天给大家推荐一款我们目前在使用的电脑群发工具掘金小蜜&#xff0c;不仅可以无限多开&#xff0c;方便你同时管理多个账号&#xff0c;群发功能更是十分强大&#xff0c;轻松释放你的双手。 掘金小蜜&#xff08;只支持Win7及以上操作系统&#xff0c;没有推Mac版和手机客户…

数据集001:安全帽检测数据集 (Helmet Detection) (含数据集下载链接)

安全帽检测 安全帽识别是一个目标检测任务&#xff0c;及时排查安全帽佩戴的规范性并给予提醒&#xff0c;可以大大降低施工安全隐患。这是CV领域入门级的项目&#xff0c;能快速了解从数据预处理、模型构建、训练到部署的整体流程。 数据集格式 数据集中包含了5000张已经标注…

Flutter 中的 ClipRect 小部件:全面指南

Flutter 中的 ClipRect 小部件&#xff1a;全面指南 在Flutter中&#xff0c;ClipRect是一个布局小部件&#xff0c;它使用矩形裁剪其子组件的可见部分。这意味着超出ClipRect定义的矩形区域的子组件部分将被隐藏。ClipRect通常用于实现自定义的滚动效果、动画或者仅仅是为了限…

Google发布的CAT3D,在1分钟内,能够从任意数量的真实或生成的图像创建3D场景。

给定任意数量的输入图像&#xff0c;使用以这些图像为条件的多视图扩散模型来生成场景的新视图。生成的视图被输入到强大的 3D 重建管道&#xff0c;生成可以交互渲染的 3D 表示。总处理时间&#xff08;包括视图生成和 3D 重建&#xff09;仅需一分钟。 相关链接 论文&#x…