如何在不到 O(N*M) 的时间内加入两个列表?
How can I join two lists in less than O(N*M)?
假设我们有两个 table(如 SQL table),其中一个的主键是另一个的外键。我应该编写一个简单的算法来模拟这两个 table 的连接。我想遍历第一个 table 中主键列中的每个元素,在第二个循环中检查外键是否匹配,然后将其存储在外部数组或列表中。但是,这需要 O(N*M),我需要找到更好的东西。教科书中有暗示它涉及散列,但是,我不确定这里如何实现散列或如何使它变得更好?
正在编辑以添加示例:
Table Employees
|ID (Primary Key)| Name | Department|
|:---------------|:----:|----------:|
| 1 | Jack | IT |
| 2 | Amy | Finance |
Table Transactions
|Sold Product| Sold By(Foreign Key)| Sold To|
|:-----------|:-------------------:|-------:|
| TV | 1 | Mary |
| Radio | 1 | Bob |
| Mobile | 2 | Lisa |
我想做的是编写一个算法,使用哈希将这两个 table 连接起来,而不是任何复杂的东西,只是一个关于如何做到这一点的简单想法。
将child table的主键和外键读取到一个映射中,其中键是外键,值是主键。请记住,如果这是一对多关系,一个外键可以映射到多个主键。
现在遍历母亲的主键 table 并为每个主键检查它是否存在于映射中。如果是这样,您添加一个与数组有关系的行的主键元组(或者您想要保存它)。
时间复杂度为O(n + m)
。遍历每个 table 的行一次。由于映射中的查找是常量,我们不需要添加它。
Space 复杂度为 O(m)
,其中 m
是 child table 中的行数。与原始解决方案相比,这是您使用的一些额外 space 来提高时间复杂度。
使用主要 table 元素填充 HashMap,使用它们的键作为映射键,使用整个对象作为值。这只需要在主 table 上传递 1 次,并且添加到 HashMap 的每个操作都在恒定时间 O(1) 内发生。这类似于创建数据库索引。
迭代子项 table,通过从地图中获取父项,在恒定时间 O(1) 内“加入”到父行。
每个“table”的总运行时间为 1 次,因此 O(n + m)。
代码可能类似于:
class Parent {
int id;
// other fields, getters etc
}
class Child {
int parentId;
// other fields, getters etc
}
void join(List<Parent> parents, List<Child> children) {
Map<Integer, Parent> parentMap = parents.stream()
.collect(toMap(Parent::getKey, p -> p)); // FYI toMap collects to a HashMap
for (Child child : children) {
Parent parent = parentMap.get(child.getParentId());
// not sure what “join” means, but you now have child and its parent
}
}
试试这个。
record Employees(int id, String name, String department) {}
record Transactions(String soldProduct, int soldBy, String soldTo) {}
record Joined(String soldProduct, int soldBy, String soldTo, String name, String department) {}
public static void main(String[] args) {
List<Employees> employees = List.of(
new Employees(1, "Jack", "IT"),
new Employees(2, "Amy", "Finance"));
List<Transactions> transactions = List.of(
new Transactions("TV", 1, "Mary"),
new Transactions("Radio", 1, "Bob"),
new Transactions("Mobile", 2, "Lisa"));
Map<Integer, Employees> mapEmployee = employees.stream()
.collect(Collectors.toMap(Employees::id, Function.identity()));
List<Joined> joined = transactions.stream()
.map(t -> {
Employees e = mapEmployee.get(t.soldBy());
return new Joined(t.soldProduct(), t.soldBy(), t.soldTo(), e.name(), e.department());
})
.toList();
for (Joined e : joined)
System.out.println(e);
}
输出:
Joined[soldProduct=TV, soldBy=1, soldTo=Mary, name=Jack, department=IT]
Joined[soldProduct=Radio, soldBy=1, soldTo=Bob, name=Jack, department=IT]
Joined[soldProduct=Mobile, soldBy=2, soldTo=Lisa, name=Amy, department=Finance]
假设我们有两个 table(如 SQL table),其中一个的主键是另一个的外键。我应该编写一个简单的算法来模拟这两个 table 的连接。我想遍历第一个 table 中主键列中的每个元素,在第二个循环中检查外键是否匹配,然后将其存储在外部数组或列表中。但是,这需要 O(N*M),我需要找到更好的东西。教科书中有暗示它涉及散列,但是,我不确定这里如何实现散列或如何使它变得更好?
正在编辑以添加示例:
Table Employees
|ID (Primary Key)| Name | Department|
|:---------------|:----:|----------:|
| 1 | Jack | IT |
| 2 | Amy | Finance |
Table Transactions
|Sold Product| Sold By(Foreign Key)| Sold To|
|:-----------|:-------------------:|-------:|
| TV | 1 | Mary |
| Radio | 1 | Bob |
| Mobile | 2 | Lisa |
我想做的是编写一个算法,使用哈希将这两个 table 连接起来,而不是任何复杂的东西,只是一个关于如何做到这一点的简单想法。
将child table的主键和外键读取到一个映射中,其中键是外键,值是主键。请记住,如果这是一对多关系,一个外键可以映射到多个主键。
现在遍历母亲的主键 table 并为每个主键检查它是否存在于映射中。如果是这样,您添加一个与数组有关系的行的主键元组(或者您想要保存它)。
时间复杂度为O(n + m)
。遍历每个 table 的行一次。由于映射中的查找是常量,我们不需要添加它。
Space 复杂度为 O(m)
,其中 m
是 child table 中的行数。与原始解决方案相比,这是您使用的一些额外 space 来提高时间复杂度。
使用主要 table 元素填充 HashMap,使用它们的键作为映射键,使用整个对象作为值。这只需要在主 table 上传递 1 次,并且添加到 HashMap 的每个操作都在恒定时间 O(1) 内发生。这类似于创建数据库索引。
迭代子项 table,通过从地图中获取父项,在恒定时间 O(1) 内“加入”到父行。
每个“table”的总运行时间为 1 次,因此 O(n + m)。
代码可能类似于:
class Parent {
int id;
// other fields, getters etc
}
class Child {
int parentId;
// other fields, getters etc
}
void join(List<Parent> parents, List<Child> children) {
Map<Integer, Parent> parentMap = parents.stream()
.collect(toMap(Parent::getKey, p -> p)); // FYI toMap collects to a HashMap
for (Child child : children) {
Parent parent = parentMap.get(child.getParentId());
// not sure what “join” means, but you now have child and its parent
}
}
试试这个。
record Employees(int id, String name, String department) {}
record Transactions(String soldProduct, int soldBy, String soldTo) {}
record Joined(String soldProduct, int soldBy, String soldTo, String name, String department) {}
public static void main(String[] args) {
List<Employees> employees = List.of(
new Employees(1, "Jack", "IT"),
new Employees(2, "Amy", "Finance"));
List<Transactions> transactions = List.of(
new Transactions("TV", 1, "Mary"),
new Transactions("Radio", 1, "Bob"),
new Transactions("Mobile", 2, "Lisa"));
Map<Integer, Employees> mapEmployee = employees.stream()
.collect(Collectors.toMap(Employees::id, Function.identity()));
List<Joined> joined = transactions.stream()
.map(t -> {
Employees e = mapEmployee.get(t.soldBy());
return new Joined(t.soldProduct(), t.soldBy(), t.soldTo(), e.name(), e.department());
})
.toList();
for (Joined e : joined)
System.out.println(e);
}
输出:
Joined[soldProduct=TV, soldBy=1, soldTo=Mary, name=Jack, department=IT]
Joined[soldProduct=Radio, soldBy=1, soldTo=Bob, name=Jack, department=IT]
Joined[soldProduct=Mobile, soldBy=2, soldTo=Lisa, name=Amy, department=Finance]