From bf92b888f1ebff4fccfc83f1ff32a45e306c20bc Mon Sep 17 00:00:00 2001 From: krahets Date: Wed, 14 Jun 2023 01:57:44 +0800 Subject: [PATCH] build --- chapter_graph/graph_operations.md | 18 +- chapter_hashing/hash_collision.md | 875 +++++++++++++++++++++++++++++- chapter_hashing/hash_map.md | 244 +++++---- chapter_hashing/summary.md | 2 +- overrides/home.html | 280 ++++++++++ 5 files changed, 1271 insertions(+), 148 deletions(-) create mode 100644 overrides/home.html diff --git a/chapter_graph/graph_operations.md b/chapter_graph/graph_operations.md index c5f3dc756..19b42cdb9 100644 --- a/chapter_graph/graph_operations.md +++ b/chapter_graph/graph_operations.md @@ -947,11 +947,11 @@ comments: true /* 打印邻接表 */ public void print() { System.out.println("邻接表 ="); - for (Map.Entry> entry : adjList.entrySet()) { + for (Map.Entry> pair : adjList.entrySet()) { List tmp = new ArrayList<>(); - for (Vertex vertex : entry.getValue()) + for (Vertex vertex : pair.getValue()) tmp.add(vertex.val); - System.out.println(entry.getKey().val + ": " + tmp + ","); + System.out.println(pair.getKey().val + ": " + tmp + ","); } } } @@ -1444,11 +1444,11 @@ comments: true /* 打印邻接表 */ public void print() { Console.WriteLine("邻接表 ="); - foreach (KeyValuePair> entry in adjList) { + foreach (KeyValuePair> pair in adjList) { List tmp = new List(); - foreach (Vertex vertex in entry.Value) + foreach (Vertex vertex in pair.Value) tmp.Add(vertex.val); - Console.WriteLine(entry.Key.val + ": [" + string.Join(", ", tmp) + "],"); + Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); } } } @@ -1523,12 +1523,12 @@ comments: true /* 打印邻接表 */ public func print() { Swift.print("邻接表 =") - for entry in adjList { + for pair in adjList { var tmp: [Int] = [] - for vertex in entry.value { + for vertex in pair.value { tmp.append(vertex.val) } - Swift.print("\(entry.key.val): \(tmp),") + Swift.print("\(pair.key.val): \(tmp),") } } } diff --git a/chapter_hashing/hash_collision.md b/chapter_hashing/hash_collision.md index 2bb99edb6..38d503d44 100644 --- a/chapter_hashing/hash_collision.md +++ b/chapter_hashing/hash_collision.md @@ -18,15 +18,15 @@ comments: true 因此,**当哈希表内的冲突总体较为严重时,编程语言通常通过扩容哈希表来缓解冲突**。类似于数组扩容,哈希表扩容需将所有键值对从原哈希表迁移至新哈希表,开销较大。 -编程语言通常使用「负载因子 Load Factor」来衡量哈希冲突的严重程度,**定义为哈希表中元素数量除以桶数量**,常作为哈希表扩容的触发条件。在 Java 中,当负载因子 $> 0.75$ 时,系统会将 HashMap 容量扩展为原先的 $2$ 倍。 +编程语言通常使用「负载因子 Load Factor」来衡量哈希冲突的严重程度,**定义为哈希表中元素数量除以桶数量**,常作为哈希表扩容的触发条件。在 Java 中,当负载因子超过 $ 0.75$ 时,系统会将 HashMap 容量扩展为原先的 $2$ 倍。 ## 6.2.2.   链式地址 在原始哈希表中,每个桶仅能存储一个键值对。**链式地址将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中**。 -![链式地址](hash_collision.assets/hash_collision_chaining.png) +![链式地址哈希表](hash_collision.assets/hash_collision_chaining.png) -

Fig. 链式地址

+

Fig. 链式地址哈希表

链式地址下,哈希表的操作方法包括: @@ -39,46 +39,879 @@ comments: true - **占用空间增大**,由于链表或二叉树包含节点指针,相比数组更加耗费内存空间; - **查询效率降低**,因为需要线性遍历链表来查找对应元素; -为了提高操作效率,**可以将链表转换为「AVL 树」或「红黑树」**,将查询操作的时间复杂度优化至 $O(\log n)$ 。 +以下给出了链式地址哈希表的简单实现,需要注意: + +- 为了使得代码尽量简短,我们使用列表(动态数组)代替链表。换句话说,哈希表(数组)包含多个桶,每个桶都是一个列表。 +- 以下代码实现了哈希表扩容方法。具体来看,当负载因子超过 $0.75$ 时,我们将哈希表扩容至 $2$ 倍。 + +=== "Java" + + ```java title="hash_map_chaining.java" + /* 键值对 */ + class Pair { + public int key; + public String val; + + public Pair(int key, String val) { + this.key = key; + this.val = val; + } + } + + /* 链式地址哈希表 */ + class HashMapChaining { + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + List> buckets; // 桶数组 + + /* 构造方法 */ + public HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2 / 3.0; + extendRatio = 2; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + } + + /* 哈希函数 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + double loadFactor() { + return (double) size / capacity; + } + + /* 查询操作 */ + String get(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // 遍历桶,若找到 key 则返回对应 val + for (Pair pair : bucket) { + if (pair.key == key) { + return pair.val; + } + } + // 若未找到 key 则返回 null + return null; + } + + /* 添加操作 */ + void put(int key, String val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + List bucket = buckets.get(index); + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for (Pair pair : bucket) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // 若无该 key ,则将键值对添加至尾部 + Pair pair = new Pair(key, val); + bucket.add(pair); + size++; + } + + /* 删除操作 */ + void remove(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // 遍历桶,从中删除键值对 + for (Pair pair : bucket) { + if (pair.key == key) + bucket.remove(pair); + } + size--; + } + + /* 扩容哈希表 */ + void extend() { + // 暂存原哈希表 + List> bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (List bucket : bucketsTmp) { + for (Pair pair : bucket) { + put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + void print() { + for (List bucket : buckets) { + List res = new ArrayList<>(); + for (Pair pair : bucket) { + res.add(pair.key + " -> " + pair.val); + } + System.out.println(res); + } + } + } + ``` + +=== "C++" + + ```cpp title="hash_map_chaining.cpp" + /* 键值对 */ + struct Pair { + public: + int key; + string val; + Pair(int key, string val) { + this->key = key; + this->val = val; + } + }; + + /* 链式地址哈希表 */ + class HashMapChaining { + private: + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + vector> buckets; // 桶数组 + + public: + /* 构造方法 */ + HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3), extendRatio(2) { + buckets.resize(capacity); + } + + /* 哈希函数 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + double loadFactor() { + return (double)size / (double)capacity; + } + + /* 查询操作 */ + string get(int key) { + int index = hashFunc(key); + // 遍历桶,若找到 key 则返回对应 val + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + return pair->val; + } + } + // 若未找到 key 则返回 nullptr + return nullptr; + } + + /* 添加操作 */ + void put(int key, string val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + pair->val = val; + return; + } + } + // 若无该 key ,则将键值对添加至尾部 + buckets[index].push_back(new Pair(key, val)); + size++; + } + + /* 删除操作 */ + void remove(int key) { + int index = hashFunc(key); + auto &bucket = buckets[index]; + // 遍历桶,从中删除键值对 + for (int i = 0; i < bucket.size(); i++) { + if (bucket[i]->key == key) { + Pair *tmp = bucket[i]; + bucket.erase(bucket.begin() + i); // 从中删除键值对 + delete tmp; // 释放内存 + size--; + return; + } + } + } + + /* 扩容哈希表 */ + void extend() { + // 暂存原哈希表 + vector> bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets.clear(); + buckets.resize(capacity); + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (auto &bucket : bucketsTmp) { + for (Pair *pair : bucket) { + put(pair->key, pair->val); + } + } + } + + /* 打印哈希表 */ + void print() { + for (auto &bucket : buckets) { + cout << "["; + for (Pair *pair : bucket) { + cout << pair->key << " -> " << pair->val << ", "; + } + cout << "]\n"; + } + } + }; + ``` + +=== "Python" + + ```python title="hash_map_chaining.py" + class Pair: + """键值对""" + + def __init__(self, key: int, val: str): + self.key = key + self.val = val + + class HashMapChaining: + """链式地址哈希表""" + + def __init__(self): + """构造方法""" + self.size = 0 # 键值对数量 + self.capacity = 4 # 哈希表容量 + self.load_thres = 2 / 3 # 触发扩容的负载因子阈值 + self.extend_ratio = 2 # 扩容倍数 + self.buckets = [[] for _ in range(self.capacity)] # 桶数组 + + def hash_func(self, key: int) -> int: + """哈希函数""" + return key % self.capacity + + def load_factor(self) -> float: + """负载因子""" + return self.size / self.capacity + + def get(self, key: int) -> str: + """查询操作""" + index = self.hash_func(key) + bucket = self.buckets[index] + # 遍历桶,若找到 key 则返回对应 val + for pair in bucket: + if pair.key == key: + return pair.val + # 若未找到 key 则返回 None + return None + + def put(self, key: int, val: str): + """添加操作""" + # 当负载因子超过阈值时,执行扩容 + if self.load_factor() > self.load_thres: + self.extend() + index = self.hash_func(key) + bucket = self.buckets[index] + # 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for pair in bucket: + if pair.key == key: + pair.val = val + return + # 若无该 key ,则将键值对添加至尾部 + pair = Pair(key, val) + bucket.append(pair) + self.size += 1 + + def remove(self, key: int): + """删除操作""" + index = self.hash_func(key) + bucket = self.buckets[index] + # 遍历桶,从中删除键值对 + for pair in bucket: + if pair.key == key: + bucket.remove(pair) + self.size -= 1 + return + + def extend(self): + """扩容哈希表""" + # 暂存原哈希表 + buckets = self.buckets + # 初始化扩容后的新哈希表 + self.capacity *= self.extend_ratio + self.buckets = [[] for _ in range(self.capacity)] + self.size = 0 + # 将键值对从原哈希表搬运至新哈希表 + for bucket in buckets: + for pair in bucket: + self.put(pair.key, pair.val) + + def print(self): + """打印哈希表""" + for bucket in self.buckets: + res = [] + for pair in bucket: + res.append(str(pair.key) + " -> " + pair.val) + print(res) + ``` + +=== "Go" + + ```go title="hash_map_chaining.go" + [class]{pair}-[func]{} + + [class]{hashMapChaining}-[func]{} + ``` + +=== "JavaScript" -## 6.2.3.   开放寻址 + ```javascript title="hash_map_chaining.js" + [class]{Pair}-[func]{} + + [class]{HashMapChaining}-[func]{} + ``` + +=== "TypeScript" + + ```typescript title="hash_map_chaining.ts" + [class]{Pair}-[func]{} + + [class]{HashMapChaining}-[func]{} + ``` + +=== "C" + + ```c title="hash_map_chaining.c" + [class]{pair}-[func]{} + + [class]{hashMapChaining}-[func]{} + ``` + +=== "C#" -「开放寻址」方法不引入额外的数据结构,而是通过“多次探测”来解决哈希冲突,**探测方式主要包括线性探测、平方探测、多次哈希**。 + ```csharp title="hash_map_chaining.cs" + [class]{Pair}-[func]{} + + [class]{HashMapChaining}-[func]{} + ``` + +=== "Swift" -### 线性探测 + ```swift title="hash_map_chaining.swift" + [class]{Pair}-[func]{} + + [class]{HashMapChaining}-[func]{} + ``` + +=== "Zig" + + ```zig title="hash_map_chaining.zig" + [class]{Pair}-[func]{} + + [class]{HashMapChaining}-[func]{} + ``` + +=== "Dart" + + ```dart title="hash_map_chaining.dart" + [class]{Pair}-[func]{} + + [class]{HashMapChaining}-[func]{} + ``` + +!!! tip -「线性探测」采用固定步长的线性查找来解决哈希冲突。 + 为了提高效率,**我们可以将链表转换为「AVL 树」或「红黑树」**,从而将查询操作的时间复杂度优化至 $O(\log n)$ 。 -**插入元素**:若出现哈希冲突,则从冲突位置向后线性遍历(步长通常为 $1$ ),直至找到空位,将元素插入其中。 +## 6.2.3.   开放寻址 + +开放寻址法不引入额外的数据结构,而是通过“多次探测”来解决哈希冲突,**探测方式主要包括线性探测、平方探测、多次哈希**。 -**查找元素**:在出现哈希冲突时,使用相同步长进行线性查找,可能遇到以下两种情况。 +### 线性探测 -1. 找到对应元素,返回 value 即可; -2. 若遇到空位,说明目标键值对不在哈希表中; +线性探测采用固定步长的线性查找来解决哈希冲突。 + +- **插入元素**:通过哈希函数计算数组索引,若发现桶内已有元素,则从冲突位置向后线性遍历(步长通常为 $1$ ),直至找到空位,将元素插入其中。 +- **查找元素**:若发现哈希冲突,则使用相同步长向后线性遍历,直到找到对应元素,返回 value 即可;或者若遇到空位,说明目标键值对不在哈希表中,返回 $\text{None}$ 。 ![线性探测](hash_collision.assets/hash_collision_linear_probing.png)

Fig. 线性探测

-线性探测存在以下缺陷: +然而,线性探测存在以下缺陷: -- **不能直接删除元素**。删除元素会在数组内产生一个空位,查找其他元素时,该空位可能导致程序误判元素不存在(即上述第 `2.` 种情况)。因此,需要借助一个标志位来标记已删除元素。 +- **不能直接删除元素**。删除元素会在数组内产生一个空位,查找其他元素时,该空位可能导致程序误判元素不存在。因此,需要借助一个标志位来标记已删除元素。 - **容易产生聚集**。数组内连续被占用位置越长,这些连续位置发生哈希冲突的可能性越大,进一步促使这一位置的“聚堆生长”,最终导致增删查改操作效率降低。 -### 多次哈希 +如以下代码所示,为开放寻址(线性探测)哈希表的简单实现,重点包括: + +- 我们使用一个固定的键值对实例 `removed` 来标记已删除元素。也就是说,当一个桶为 $\text{None}$ 或 `removed` 时,这个桶都是空的,可用于放置键值对。 +- 被标记为已删除的空间是可以再次被使用的。当插入元素时,若通过哈希函数找到了被标记为已删除的索引,则可将该元素放置到该索引。 +- 在线性探测时,我们从当前索引 `index` 向后遍历;而当越过数组尾部时,需要回到头部继续遍历。 + +=== "Java" + + ```java title="hash_map_open_addressing.java" + /* 键值对 */ + class Pair { + public int key; + public String val; + + public Pair(int key, String val) { + this.key = key; + this.val = val; + } + } + + /* 开放寻址哈希表 */ + class HashMapOpenAddressing { + private int size; // 键值对数量 + private int capacity; // 哈希表容量 + private double loadThres; // 触发扩容的负载因子阈值 + private int extendRatio; // 扩容倍数 + private Pair[] buckets; // 桶数组 + private Pair removed; // 删除标记 + + /* 构造方法 */ + public HashMapOpenAddressing() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = new Pair[capacity]; + removed = new Pair(-1, "-1"); + } + + /* 哈希函数 */ + public int hashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + public double loadFactor() { + return (double) size / capacity; + } + + /* 查询操作 */ + public String get(int key) { + int index = hashFunc(key); + // 线性探测,从 index 开始向后遍历 + for (int i = 0; i < capacity; i++) { + // 计算桶索引,越过尾部返回头部 + int j = (index + i) % capacity; + // 若遇到空桶,说明无此 key ,则返回 null + if (buckets[j] == null) + return null; + // 若遇到指定 key ,则返回对应 val + if (buckets[j].key == key && buckets[j] != removed) + return buckets[j].val; + } + return null; + } + + /* 添加操作 */ + public void put(int key, String val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + // 线性探测,从 index 开始向后遍历 + for (int i = 0; i < capacity; i++) { + // 计算桶索引,越过尾部返回头部 + int j = (index + i) % capacity; + // 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 + if (buckets[j] == null || buckets[j] == removed) { + buckets[j] = new Pair(key, val); + size += 1; + return; + } + // 若遇到指定 key ,则更新对应 val + if (buckets[j].key == key) { + buckets[j].val = val; + return; + } + } + } + + /* 删除操作 */ + public void remove(int key) { + int index = hashFunc(key); + // 线性探测,从 index 开始向后遍历 + for (int i = 0; i < capacity; i++) { + // 计算桶索引,越过尾部返回头部 + int j = (index + i) % capacity; + // 若遇到空桶,说明无此 key ,则直接返回 + if (buckets[j] == null) { + return; + } + // 若遇到指定 key ,则标记删除并返回 + if (buckets[j].key == key) { + buckets[j] = removed; + size -= 1; + return; + } + } + } + + /* 扩容哈希表 */ + public void extend() { + // 暂存原哈希表 + Pair[] bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = new Pair[capacity]; + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (Pair pair : bucketsTmp) { + if (pair != null && pair != removed) { + put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + public void print() { + for (Pair pair : buckets) { + if (pair != null) { + System.out.println(pair.key + " -> " + pair.val); + } else { + System.out.println("null"); + } + } + } + } + ``` + +=== "C++" + + ```cpp title="hash_map_open_addressing.cpp" + /* 键值对 */ + struct Pair { + int key; + string val; + + Pair(int k, string v) : key(k), val(v) { + } + }; + + /* 开放寻址哈希表 */ + class HashMapOpenAddressing { + private: + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + vector buckets; // 桶数组 + Pair *removed; // 删除标记 + + public: + /* 构造方法 */ + HashMapOpenAddressing() { + // 构造方法 + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = vector(capacity, nullptr); + removed = new Pair(-1, "-1"); + } + + /* 哈希函数 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + double loadFactor() { + return static_cast(size) / capacity; + } + + /* 查询操作 */ + string get(int key) { + int index = hashFunc(key); + // 线性探测,从 index 开始向后遍历 + for (int i = 0; i < capacity; i++) { + // 计算桶索引,越过尾部返回头部 + int j = (index + i) % capacity; + // 若遇到空桶,说明无此 key ,则返回 nullptr + if (buckets[j] == nullptr) + return nullptr; + // 若遇到指定 key ,则返回对应 val + if (buckets[j]->key == key && buckets[j] != removed) + return buckets[j]->val; + } + return nullptr; + } + + /* 添加操作 */ + void put(int key, string val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) + extend(); + int index = hashFunc(key); + // 线性探测,从 index 开始向后遍历 + for (int i = 0; i < capacity; i++) { + // 计算桶索引,越过尾部返回头部 + int j = (index + i) % capacity; + // 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 + if (buckets[j] == nullptr || buckets[j] == removed) { + buckets[j] = new Pair(key, val); + size += 1; + return; + } + // 若遇到指定 key ,则更新对应 val + if (buckets[j]->key == key) { + buckets[j]->val = val; + return; + } + } + } + + /* 删除操作 */ + void remove(int key) { + int index = hashFunc(key); + // 线性探测,从 index 开始向后遍历 + for (int i = 0; i < capacity; i++) { + // 计算桶索引,越过尾部返回头部 + int j = (index + i) % capacity; + // 若遇到空桶,说明无此 key ,则直接返回 + if (buckets[j] == nullptr) + return; + // 若遇到指定 key ,则标记删除并返回 + if (buckets[j]->key == key) { + delete buckets[j]; // 释放内存 + buckets[j] = removed; + size -= 1; + return; + } + } + } + + /* 扩容哈希表 */ + void extend() { + // 暂存原哈希表 + vector bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = vector(capacity, nullptr); + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (Pair *pair : bucketsTmp) { + if (pair != nullptr && pair != removed) { + put(pair->key, pair->val); + } + } + } + + /* 打印哈希表 */ + void print() { + for (auto &pair : buckets) { + if (pair != nullptr) { + cout << pair->key << " -> " << pair->val << endl; + } else { + cout << "nullptr" << endl; + } + } + } + }; + ``` + +=== "Python" + + ```python title="hash_map_open_addressing.py" + class Pair: + """键值对""" + + def __init__(self, key: int, val: str): + self.key = key + self.val = val + + class HashMapOpenAddressing: + """开放寻址哈希表""" + + def __init__(self): + """构造方法""" + self.size = 0 # 键值对数量 + self.capacity = 4 # 哈希表容量 + self.load_thres = 2 / 3 # 触发扩容的负载因子阈值 + self.extend_ratio = 2 # 扩容倍数 + self.buckets: list[Pair | None] = [None] * self.capacity # 桶数组 + self.removed = Pair(-1, "-1") # 删除标记 + + def hash_func(self, key: int) -> int: + """哈希函数""" + return key % self.capacity + + def load_factor(self) -> float: + """负载因子""" + return self.size / self.capacity + + def get(self, key: int) -> str: + """查询操作""" + index = self.hash_func(key) + # 线性探测,从 index 开始向后遍历 + for i in range(self.capacity): + # 计算桶索引,越过尾部返回头部 + j = (index + i) % self.capacity + # 若遇到空桶,说明无此 key ,则返回 None + if self.buckets[j] is None: + return None + # 若遇到指定 key ,则返回对应 val + if self.buckets[j].key == key and self.buckets[j] != self.removed: + return self.buckets[j].val + + def put(self, key: int, val: str): + """添加操作""" + # 当负载因子超过阈值时,执行扩容 + if self.load_factor() > self.load_thres: + self.extend() + index = self.hash_func(key) + # 线性探测,从 index 开始向后遍历 + for i in range(self.capacity): + # 计算桶索引,越过尾部返回头部 + j = (index + i) % self.capacity + # 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 + if self.buckets[j] in [None, self.removed]: + self.buckets[j] = Pair(key, val) + self.size += 1 + return + # 若遇到指定 key ,则更新对应 val + if self.buckets[j].key == key: + self.buckets[j].val = val + return + + def remove(self, key: int): + """删除操作""" + index = self.hash_func(key) + # 线性探测,从 index 开始向后遍历 + for i in range(self.capacity): + # 计算桶索引,越过尾部返回头部 + j = (index + i) % self.capacity + # 若遇到空桶,说明无此 key ,则直接返回 + if self.buckets[j] is None: + return + # 若遇到指定 key ,则标记删除并返回 + if self.buckets[j].key == key: + self.buckets[j] = self.removed + self.size -= 1 + return + + def extend(self): + """扩容哈希表""" + # 暂存原哈希表 + buckets_tmp = self.buckets + # 初始化扩容后的新哈希表 + self.capacity *= self.extend_ratio + self.buckets = [None] * self.capacity + self.size = 0 + # 将键值对从原哈希表搬运至新哈希表 + for pair in buckets_tmp: + if pair not in [None, self.removed]: + self.put(pair.key, pair.val) + + def print(self) -> None: + """打印哈希表""" + for pair in self.buckets: + if pair is not None: + print(pair.key, "->", pair.val) + else: + print("None") + ``` + +=== "Go" + + ```go title="hash_map_open_addressing.go" + [class]{pair}-[func]{} + + [class]{hashMapOpenAddressing}-[func]{} + ``` + +=== "JavaScript" + + ```javascript title="hash_map_open_addressing.js" + [class]{Pair}-[func]{} + + [class]{HashMapOpenAddressing}-[func]{} + ``` + +=== "TypeScript" + + ```typescript title="hash_map_open_addressing.ts" + [class]{Pair}-[func]{} + + [class]{HashMapOpenAddressing}-[func]{} + ``` + +=== "C" + + ```c title="hash_map_open_addressing.c" + [class]{pair}-[func]{} + + [class]{hashMapOpenAddressing}-[func]{} + ``` + +=== "C#" + + ```csharp title="hash_map_open_addressing.cs" + [class]{Pair}-[func]{} + + [class]{HashMapOpenAddressing}-[func]{} + ``` + +=== "Swift" + + ```swift title="hash_map_open_addressing.swift" + [class]{Pair}-[func]{} + + [class]{HashMapOpenAddressing}-[func]{} + ``` + +=== "Zig" -顾名思义,「多次哈希」方法是使用多个哈希函数 $f_1(x)$ , $f_2(x)$ , $f_3(x)$ , $\cdots$ 进行探测。 + ```zig title="hash_map_open_addressing.zig" + [class]{Pair}-[func]{} -**插入元素**:若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推,直到找到空位后插入元素。 + [class]{HashMapOpenAddressing}-[func]{} + ``` + +=== "Dart" + + ```dart title="hash_map_open_addressing.dart" + [class]{Pair}-[func]{} + + [class]{HashMapOpenAddressing}-[func]{} + ``` + +### 多次哈希 -**查找元素**:在相同的哈希函数顺序下进行查找,存在以下两种情况: +顾名思义,多次哈希方法是使用多个哈希函数 $f_1(x)$ , $f_2(x)$ , $f_3(x)$ , $\cdots$ 进行探测。 -1. 如果找到目标元素,则返回之; -2. 若遇到空位或已尝试所有哈希函数,则说明哈希表中不存在该元素; +- **插入元素**:若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推,直到找到空位后插入元素。 +- **查找元素**:在相同的哈希函数顺序下进行查找,直到找到目标元素时返回;或遇到空位或已尝试所有哈希函数,说明哈希表中不存在该元素,则返回 $\text{None}$ 。 与线性探测相比,多次哈希方法不易产生聚集,但多个哈希函数会增加额外的计算量。 -!!! note "哈希表设计方案" +!!! note "编程语言的选择" Java 采用「链式地址」。自 JDK 1.8 以来,当 HashMap 内数组长度达到 64 且链表长度达到 8 时,链表会被转换为红黑树以提升查找性能。 diff --git a/chapter_hashing/hash_map.md b/chapter_hashing/hash_map.md index af61b2d70..978775a2d 100755 --- a/chapter_hashing/hash_map.md +++ b/chapter_hashing/hash_map.md @@ -12,16 +12,20 @@ comments: true

Fig. 哈希表的抽象表示

-除哈希表外,我们还可以使用数组或链表实现查询功能,各项操作的时间复杂度如下表所示。 +除哈希表外,我们还可以使用数组或链表实现元素查询,其中: -在哈希表中增删查改的时间复杂度都是 $O(1)$ ,全面胜出!因此,哈希表常用于对查找效率要求较高的场景。 +- 查询元素需要遍历所有元素,使用 $O(n)$ 时间; +- 添加元素仅需添加至尾部即可,使用 $O(1)$ 时间; +- 删除元素需要先查询再删除,使用 $O(n)$ 时间; + +然而,在哈希表中进行增删查的时间复杂度都是 $O(1)$ 。哈希表全面胜出!因此,哈希表常用于对查找效率要求较高的场景。
| | 数组 | 链表 | 哈希表 | | -------- | ------ | ------ | ------ | | 查找元素 | $O(n)$ | $O(n)$ | $O(1)$ | -| 插入元素 | $O(1)$ | $O(1)$ | $O(1)$ | +| 添加元素 | $O(1)$ | $O(1)$ | $O(1)$ | | 删除元素 | $O(n)$ | $O(n)$ | $O(1)$ |
@@ -436,12 +440,12 @@ comments: true 首先考虑最简单的情况,**仅使用一个数组来实现哈希表**。通常,我们将数组中的每个空位称为「桶 Bucket」,用于存储键值对。 -我们将键值对 key, value 封装成一个类 `Entry` ,并将所有 `Entry` 放入数组中。这样,数组中的每个 `Entry` 都具有唯一的索引。为了建立 key 和索引之间的映射关系,我们需要使用「哈希函数 Hash Function」。 +我们将键值对 key, value 封装成一个类 `Pair` ,并将所有 `Pair` 放入数组中。这样,数组中的每个 `Pair` 都具有唯一的索引。为了建立 key 和索引之间的映射关系,我们需要使用「哈希函数 Hash Function」。 设哈希表的数组为 `buckets` ,哈希函数为 `f(x)` ,那么查询操作的步骤如下: 1. 输入 `key` ,通过哈希函数计算出索引 `index` ,即 `index = f(key)` ; -2. 通过索引在数组中访问到键值对 `entry` ,即 `entry = buckets[index]` ,然后从 `entry` 中获取对应的 `value` ; +2. 通过索引在数组中访问到键值对 `pair` ,即 `pair = buckets[index]` ,然后从 `pair` 中获取对应的 `value` ; 以学生数据 `key 学号 -> value 姓名` 为例,我们可以设计如下哈希函数: @@ -458,12 +462,12 @@ $$ === "Java" ```java title="array_hash_map.java" - /* 键值对 int->String */ - class Entry { + /* 键值对 */ + class Pair { public int key; public String val; - public Entry(int key, String val) { + public Pair(int key, String val) { this.key = key; this.val = val; } @@ -471,7 +475,7 @@ $$ /* 基于数组简易实现的哈希表 */ class ArrayHashMap { - private List buckets; + private List buckets; public ArrayHashMap() { // 初始化数组,包含 100 个桶 @@ -490,7 +494,7 @@ $$ /* 查询操作 */ public String get(int key) { int index = hashFunc(key); - Entry pair = buckets.get(index); + Pair pair = buckets.get(index); if (pair == null) return null; return pair.val; @@ -498,7 +502,7 @@ $$ /* 添加操作 */ public void put(int key, String val) { - Entry pair = new Entry(key, val); + Pair pair = new Pair(key, val); int index = hashFunc(key); buckets.set(index, pair); } @@ -511,19 +515,19 @@ $$ } /* 获取所有键值对 */ - public List entrySet() { - List entrySet = new ArrayList<>(); - for (Entry pair : buckets) { + public List pairSet() { + List pairSet = new ArrayList<>(); + for (Pair pair : buckets) { if (pair != null) - entrySet.add(pair); + pairSet.add(pair); } - return entrySet; + return pairSet; } /* 获取所有键 */ public List keySet() { List keySet = new ArrayList<>(); - for (Entry pair : buckets) { + for (Pair pair : buckets) { if (pair != null) keySet.add(pair.key); } @@ -533,7 +537,7 @@ $$ /* 获取所有值 */ public List valueSet() { List valueSet = new ArrayList<>(); - for (Entry pair : buckets) { + for (Pair pair : buckets) { if (pair != null) valueSet.add(pair.val); } @@ -542,7 +546,7 @@ $$ /* 打印哈希表 */ public void print() { - for (Entry kv : entrySet()) { + for (Pair kv : pairSet()) { System.out.println(kv.key + " -> " + kv.val); } } @@ -552,12 +556,12 @@ $$ === "C++" ```cpp title="array_hash_map.cpp" - /* 键值对 int->String */ - struct Entry { + /* 键值对 */ + struct Pair { public: int key; string val; - Entry(int key, string val) { + Pair(int key, string val) { this->key = key; this->val = val; } @@ -566,12 +570,12 @@ $$ /* 基于数组简易实现的哈希表 */ class ArrayHashMap { private: - vector buckets; + vector buckets; public: ArrayHashMap() { // 初始化数组,包含 100 个桶 - buckets = vector(100); + buckets = vector(100); } ~ArrayHashMap() { @@ -591,7 +595,7 @@ $$ /* 查询操作 */ string get(int key) { int index = hashFunc(key); - Entry *pair = buckets[index]; + Pair *pair = buckets[index]; if (pair == nullptr) return nullptr; return pair->val; @@ -599,7 +603,7 @@ $$ /* 添加操作 */ void put(int key, string val) { - Entry *pair = new Entry(key, val); + Pair *pair = new Pair(key, val); int index = hashFunc(key); buckets[index] = pair; } @@ -613,20 +617,20 @@ $$ } /* 获取所有键值对 */ - vector entrySet() { - vector entrySet; - for (Entry *pair : buckets) { + vector pairSet() { + vector pairSet; + for (Pair *pair : buckets) { if (pair != nullptr) { - entrySet.push_back(pair); + pairSet.push_back(pair); } } - return entrySet; + return pairSet; } /* 获取所有键 */ vector keySet() { vector keySet; - for (Entry *pair : buckets) { + for (Pair *pair : buckets) { if (pair != nullptr) { keySet.push_back(pair->key); } @@ -637,7 +641,7 @@ $$ /* 获取所有值 */ vector valueSet() { vector valueSet; - for (Entry *pair : buckets) { + for (Pair *pair : buckets) { if (pair != nullptr) { valueSet.push_back(pair->val); } @@ -647,7 +651,7 @@ $$ /* 打印哈希表 */ void print() { - for (Entry *kv : entrySet()) { + for (Pair *kv : pairSet()) { cout << kv->key << " -> " << kv->val << endl; } } @@ -657,8 +661,8 @@ $$ === "Python" ```python title="array_hash_map.py" - class Entry: - """键值对 int->String""" + class Pair: + """键值对""" def __init__(self, key: int, val: str): self.key = key @@ -670,7 +674,7 @@ $$ def __init__(self): """构造方法""" # 初始化数组,包含 100 个桶 - self.buckets: list[Entry | None] = [None] * 100 + self.buckets: list[Pair | None] = [None] * 100 def hash_func(self, key: int) -> int: """哈希函数""" @@ -680,26 +684,26 @@ $$ def get(self, key: int) -> str: """查询操作""" index: int = self.hash_func(key) - pair: Entry = self.buckets[index] + pair: Pair = self.buckets[index] if pair is None: return None return pair.val - def put(self, key: int, val: str) -> None: + def put(self, key: int, val: str): """添加操作""" - pair = Entry(key, val) + pair = Pair(key, val) index: int = self.hash_func(key) self.buckets[index] = pair - def remove(self, key: int) -> None: + def remove(self, key: int): """删除操作""" index: int = self.hash_func(key) # 置为 None ,代表删除 self.buckets[index] = None - def entry_set(self) -> list[Entry]: + def entry_set(self) -> list[Pair]: """获取所有键值对""" - result: list[Entry] = [] + result: list[Pair] = [] for pair in self.buckets: if pair is not None: result.append(pair) @@ -721,7 +725,7 @@ $$ result.append(pair.val) return result - def print(self) -> None: + def print(self): """打印哈希表""" for pair in self.buckets: if pair is not None: @@ -731,21 +735,21 @@ $$ === "Go" ```go title="array_hash_map.go" - /* 键值对 int->String */ - type entry struct { + /* 键值对 */ + type pair struct { key int val string } /* 基于数组简易实现的哈希表 */ type arrayHashMap struct { - buckets []*entry + buckets []*pair } /* 初始化哈希表 */ func newArrayHashMap() *arrayHashMap { // 初始化数组,包含 100 个桶 - buckets := make([]*entry, 100) + buckets := make([]*pair, 100) return &arrayHashMap{buckets: buckets} } @@ -767,7 +771,7 @@ $$ /* 添加操作 */ func (a *arrayHashMap) put(key int, val string) { - pair := &entry{key: key, val: val} + pair := &pair{key: key, val: val} index := a.hashFunc(key) a.buckets[index] = pair } @@ -780,8 +784,8 @@ $$ } /* 获取所有键对 */ - func (a *arrayHashMap) entrySet() []*entry { - var pairs []*entry + func (a *arrayHashMap) pairSet() []*pair { + var pairs []*pair for _, pair := range a.buckets { if pair != nil { pairs = append(pairs, pair) @@ -826,7 +830,7 @@ $$ ```javascript title="array_hash_map.js" /* 键值对 Number -> String */ - class Entry { + class Pair { constructor(key, val) { this.key = key; this.val = val; @@ -849,15 +853,15 @@ $$ /* 查询操作 */ get(key) { let index = this.#hashFunc(key); - let entry = this.#buckets[index]; - if (entry === null) return null; - return entry.val; + let pair = this.#buckets[index]; + if (pair === null) return null; + return pair.val; } /* 添加操作 */ set(key, val) { let index = this.#hashFunc(key); - this.#buckets[index] = new Entry(key, val); + this.#buckets[index] = new Pair(key, val); } /* 删除操作 */ @@ -902,10 +906,10 @@ $$ /* 打印哈希表 */ print() { - let entrySet = this.entries(); - for (const entry of entrySet) { - if (!entry) continue; - console.info(`${entry.key} -> ${entry.val}`); + let pairSet = this.entries(); + for (const pair of pairSet) { + if (!pair) continue; + console.info(`${pair.key} -> ${pair.val}`); } } } @@ -915,7 +919,7 @@ $$ ```typescript title="array_hash_map.ts" /* 键值对 Number -> String */ - class Entry { + class Pair { public key: number; public val: string; @@ -927,7 +931,7 @@ $$ /* 基于数组简易实现的哈希表 */ class ArrayHashMap { - private readonly buckets: (Entry | null)[]; + private readonly buckets: (Pair | null)[]; constructor() { // 初始化数组,包含 100 个桶 @@ -942,15 +946,15 @@ $$ /* 查询操作 */ public get(key: number): string | null { let index = this.hashFunc(key); - let entry = this.buckets[index]; - if (entry === null) return null; - return entry.val; + let pair = this.buckets[index]; + if (pair === null) return null; + return pair.val; } /* 添加操作 */ public set(key: number, val: string) { let index = this.hashFunc(key); - this.buckets[index] = new Entry(key, val); + this.buckets[index] = new Pair(key, val); } /* 删除操作 */ @@ -961,8 +965,8 @@ $$ } /* 获取所有键值对 */ - public entries(): (Entry | null)[] { - let arr: (Entry | null)[] = []; + public entries(): (Pair | null)[] { + let arr: (Pair | null)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { arr.push(this.buckets[i]); @@ -995,10 +999,10 @@ $$ /* 打印哈希表 */ public print() { - let entrySet = this.entries(); - for (const entry of entrySet) { - if (!entry) continue; - console.info(`${entry.key} -> ${entry.val}`); + let pairSet = this.entries(); + for (const pair of pairSet) { + if (!pair) continue; + console.info(`${pair.key} -> ${pair.val}`); } } } @@ -1007,7 +1011,13 @@ $$ === "C" ```c title="array_hash_map.c" - [class]{entry}-[func]{} + /* 键值对 int->string */ + struct pair { + int key; + char *val; + }; + + typedef struct pair pair; [class]{arrayHashMap}-[func]{} ``` @@ -1016,10 +1026,10 @@ $$ ```csharp title="array_hash_map.cs" /* 键值对 int->string */ - class Entry { + class Pair { public int key; public string val; - public Entry(int key, string val) { + public Pair(int key, string val) { this.key = key; this.val = val; } @@ -1027,7 +1037,7 @@ $$ /* 基于数组简易实现的哈希表 */ class ArrayHashMap { - private List buckets; + private List buckets; public ArrayHashMap() { // 初始化数组,包含 100 个桶 buckets = new(); @@ -1045,14 +1055,14 @@ $$ /* 查询操作 */ public string? get(int key) { int index = hashFunc(key); - Entry? pair = buckets[index]; + Pair? pair = buckets[index]; if (pair == null) return null; return pair.val; } /* 添加操作 */ public void put(int key, string val) { - Entry pair = new Entry(key, val); + Pair pair = new Pair(key, val); int index = hashFunc(key); buckets[index] = pair; } @@ -1065,19 +1075,19 @@ $$ } /* 获取所有键值对 */ - public List entrySet() { - List entrySet = new(); - foreach (Entry? pair in buckets) { + public List pairSet() { + List pairSet = new(); + foreach (Pair? pair in buckets) { if (pair != null) - entrySet.Add(pair); + pairSet.Add(pair); } - return entrySet; + return pairSet; } /* 获取所有键 */ public List keySet() { List keySet = new(); - foreach (Entry? pair in buckets) { + foreach (Pair? pair in buckets) { if (pair != null) keySet.Add(pair.key); } @@ -1087,7 +1097,7 @@ $$ /* 获取所有值 */ public List valueSet() { List valueSet = new(); - foreach (Entry? pair in buckets) { + foreach (Pair? pair in buckets) { if (pair != null) valueSet.Add(pair.val); } @@ -1096,7 +1106,7 @@ $$ /* 打印哈希表 */ public void print() { - foreach (Entry kv in entrySet()) { + foreach (Pair kv in pairSet()) { Console.WriteLine(kv.key + " -> " + kv.val); } } @@ -1106,8 +1116,8 @@ $$ === "Swift" ```swift title="array_hash_map.swift" - /* 键值对 int->String */ - class Entry { + /* 键值对 */ + class Pair { var key: Int var val: String @@ -1119,7 +1129,7 @@ $$ /* 基于数组简易实现的哈希表 */ class ArrayHashMap { - private var buckets: [Entry?] = [] + private var buckets: [Pair?] = [] init() { // 初始化数组,包含 100 个桶 @@ -1143,7 +1153,7 @@ $$ /* 添加操作 */ func put(key: Int, val: String) { - let pair = Entry(key: key, val: val) + let pair = Pair(key: key, val: val) let index = hashFunc(key: key) buckets[index] = pair } @@ -1156,14 +1166,14 @@ $$ } /* 获取所有键值对 */ - func entrySet() -> [Entry] { - var entrySet: [Entry] = [] + func pairSet() -> [Pair] { + var pairSet: [Pair] = [] for pair in buckets { if let pair = pair { - entrySet.append(pair) + pairSet.append(pair) } } - return entrySet + return pairSet } /* 获取所有键 */ @@ -1190,8 +1200,8 @@ $$ /* 打印哈希表 */ func print() { - for entry in entrySet() { - Swift.print("\(entry.key) -> \(entry.val)") + for pair in pairSet() { + Swift.print("\(pair.key) -> \(pair.val)") } } } @@ -1200,13 +1210,13 @@ $$ === "Zig" ```zig title="array_hash_map.zig" - // 键值对 int->String - const Entry = struct { + // 键值对 + const Pair = struct { key: usize = undefined, val: []const u8 = undefined, - pub fn init(key: usize, val: []const u8) Entry { - return Entry { + pub fn init(key: usize, val: []const u8) Pair { + return Pair { .key = key, .val = val, }; @@ -1252,7 +1262,7 @@ $$ // 添加操作 pub fn put(self: *Self, key: usize, val: []const u8) !void { - var pair = Entry.init(key, val); + var pair = Pair.init(key, val); var index = hashFunc(key); self.buckets.?.items[index] = pair; } @@ -1265,7 +1275,7 @@ $$ } // 获取所有键值对 - pub fn entrySet(self: *Self) !*std.ArrayList(T) { + pub fn pairSet(self: *Self) !*std.ArrayList(T) { var entry_set = std.ArrayList(T).init(self.mem_allocator); for (self.buckets.?.items) |item| { if (item == null) continue; @@ -1296,7 +1306,7 @@ $$ // 打印哈希表 pub fn print(self: *Self) !void { - var entry_set = try self.entrySet(); + var entry_set = try self.pairSet(); defer entry_set.deinit(); for (entry_set.items) |item| { std.debug.print("{} -> {s}\n", .{item.key, item.val}); @@ -1309,16 +1319,16 @@ $$ === "Dart" ```dart title="array_hash_map.dart" - /* 键值对 int -> String */ - class Entry { + /* 键值对 */ + class Pair { int key; String val; - Entry(this.key, this.val); + Pair(this.key, this.val); } /* 基于数组简易实现的哈希表 */ class ArrayHashMap { - late List _buckets; + late List _buckets; ArrayHashMap() { // 初始化数组,包含 100 个桶 @@ -1334,7 +1344,7 @@ $$ /* 查询操作 */ String? get(int key) { final int index = _hashFunc(key); - final Entry? pair = _buckets[index]; + final Pair? pair = _buckets[index]; if (pair == null) { return null; } @@ -1343,7 +1353,7 @@ $$ /* 添加操作 */ void put(int key, String val) { - final Entry pair = Entry(key, val); + final Pair pair = Pair(key, val); final int index = _hashFunc(key); _buckets[index] = pair; } @@ -1355,20 +1365,20 @@ $$ } /* 获取所有键值对 */ - List entrySet() { - List entrySet = []; - for (final Entry? pair in _buckets) { + List pairSet() { + List pairSet = []; + for (final Pair? pair in _buckets) { if (pair != null) { - entrySet.add(pair); + pairSet.add(pair); } } - return entrySet; + return pairSet; } /* 获取所有键 */ List keySet() { List keySet = []; - for (final Entry? pair in _buckets) { + for (final Pair? pair in _buckets) { if (pair != null) { keySet.add(pair.key); } @@ -1379,7 +1389,7 @@ $$ /* 获取所有值 */ List values() { List valueSet = []; - for (final Entry? pair in _buckets) { + for (final Pair? pair in _buckets) { if (pair != null) { valueSet.add(pair.val); } @@ -1389,7 +1399,7 @@ $$ /* 打印哈希表 */ void printHashMap() { - for (final Entry kv in entrySet()) { + for (final Pair kv in pairSet()) { print("${kv.key} -> ${kv.val}"); } } diff --git a/chapter_hashing/summary.md b/chapter_hashing/summary.md index be60f982a..e1bbaef10 100644 --- a/chapter_hashing/summary.md +++ b/chapter_hashing/summary.md @@ -6,7 +6,7 @@ comments: true - 哈希表能够在 $O(1)$ 时间内将键 key 映射到值 value,效率非常高。 - 常见的哈希表操作包括查询、添加与删除键值对、遍历键值对等。 -- 哈希函数将 key 映射为数组索引(桶),以便访问对应的值 value 。 +- 哈希函数将 key 映射为数组索引(桶索引),从而访问对应的值 value 。 - 两个不同的 key 可能在经过哈希函数后得到相同的索引,导致查询结果出错,这种现象被称为哈希冲突。 - 缓解哈希冲突的方法主要有扩容哈希表和优化哈希表的表示方法。 - 负载因子定义为哈希表中元素数量除以桶数量,反映了哈希冲突的严重程度,常用作触发哈希表扩容的条件。与数组扩容类似,哈希表扩容操作也会产生较大的开销。 diff --git a/overrides/home.html b/overrides/home.html new file mode 100644 index 000000000..24fbf86a9 --- /dev/null +++ b/overrides/home.html @@ -0,0 +1,280 @@ + + +{% extends "main.html" %} +{% block tabs %} +{{ super() }} + + + +
+
+
+
+ +
+
+

UP42 Python SDK

+

Access UP42's geospatial collections and processing workflows via Python.

+ + Get started + + + Go to GitHub + +
+
+
+
+ + +
+
+

+ + + + UP42 in Python +

+

Use UP42 via Python: order geospatial data, run analytic workflows, and + generate insights.

+
+
+

+ + + Python ecosystem +

+

Use UP42 together with your preferred Python libraries.

+
+
+

+ + Visualizations +

+

Interactive maps and visualizations. Ideal to use with Jupyter notebooks.

+
+
+ +
+
+
+
+ + +{% endblock %} +{% block content %}{% endblock %} +{% block footer %}{% endblock %} \ No newline at end of file