- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在尝试实现 Karmarkar-Karp 启发式数字分区算法的 k 分区版本。但我正在为它的第二阶段而苦苦挣扎,其中数字分区是根据所得的差异集重建的。
我能找到的唯一用一些伪代码彻底描述第二阶段的来源是论文的第 58 页:Load balancing of multiphysics simulations by multi-criteria graph partitioning .
给定多重集 S = [1,7,5,10,9,3] 将其划分为三个 (k=3) 个子集,该算法将经历 2 个阶段。
它首先按降序对 S 进行排序:
S = [10,9,7,5,3,1]
然后,S 中的每个数字都被转换成一个大小为 k 的元组,第一个条目是数字本身,其他的都是零:
M = [[10,0,0],[9,0,0],[7,0,0],[5,0,0],[3,0,0],[1,0 ,0]]
这将创建矩阵 M。
在每个步骤中,(a, b, c) 和 (x, y, z) 从 M 中移除,并在适当位置形成一个新元组:E := (a + x, b + y, c + z).元组按其最小值归一化并按降序排序,然后按顺序将其插入 M 中。在每一步,算法都会记住旧值数组([a, x]、[b, y]、[c, z])和用于归一化的数字,方法是将它们放入两个堆栈中。
M = [[10,0,0],[9,0,0],[7,0,0],[5,0,0],[3,0,0],[1,0,0]]
Old values: []
Norm. numbers: []
M = [[10,9,0],[7,0,0],[5,0,0],[3,0,0],[1,0,0]]
Old values: [([10,0],[0,0],[0,9])]
Norm. numbers: [0]
M = [[5,0,0],[3,0,0],[3,2,0],[1,0,0]]
Old values: [([10,0],[9,0],[0,7]), ([10,0],[0,0],[0,9])]
Norm. numbers: [7,0]
M = [[5,3,0],[3,2,0],[1,0,0]]
Old values: [([5,0],[0,0],[0,3]), ([10,0],[9,0],[0,7]), ([10,0],[0,0],[0,9])]
Norm. numbers: [0,7,0]
M = [[2,2,0],[1,0,0]]
Old values: [([5,0],[3,2],[0,3]), ([5,0],[0,0],[0,3]), ([10,0],[9,0],[0,7]), ([10,0],[0,0],[0,9])]
Norm. numbers: [3,0,7,0]
M = [[1,1,0]]
Old values: [([2,0],[2,0],[0,1]), ([5,0],[3,2],[0,3]), ([5,0],[0,0],[0,3]), ([10,0],[9,0],[0,7]), ([10,0],[0,0],[0,9])]
Norm. numbers: [1,3,0,7,0]
第二阶段根据生成的差异集、内存的旧值和规范化数字构建分区。
在每一步它弹出内存的旧值数组和归一化数:
M = [[1],[1],[0]]
Old values: [[2,0],[2,0],[0,1]]
Norm. number: 1
对于旧值数组中的每个元组,它计算一个值以在 M 中搜索:
[2,0]: 2 + 0 - 1 = 1
然后在 M 中搜索包含该值的分区并将该值替换为元组:
[[2,0],[1],[0]]
[2,0]: 2 + 0 - 1 = 1
=> [[2,0],[2,0],[0]]
在每次迭代中,它永远不会将两个元组放入同一个分区中。所以 [0,1] 进入最后一个分区:
[0,1]: 0 + 1 - 1 = 0
=> [[2,0],[2,0],[0,1]]
等等:
M = [[2,0],[2,0],[0,1]]
Old values: [[5,0],[3,2],[0,3]]
Norm. number: 3
M = [[0,0,5],[0,3,2],[1,0,3]]
Old values: [[5,0],[0,0],[0,3]]
Norm. number: 0
问题来了:
M = [[0,0,0,5],[3,2,0,0],[1,0,0,3]]
Old values: [[10,0],[9,0],[0,7]]
Norm. number: 7
[10,0]: 10 + 0 - 7 = 3
=> [[0,0,0,5],[10,0,2,0,0],[1,0,0,3]]
[9,0]: 9 + 0 - 7 = 2
=> ???
我已经在本次迭代中将 [10,0] 元组放入 [3,2,0,0] 分区中,因此我无法将另一个元组放入其中。但是 M 没有另一个包含值 2 的分区。
如果在出现这种情况时我允许将多个元组放入同一个分区,那么我得到的结果分区远非最佳:
[[0,0,0,0,5,7],[0,0,0,0,0,0,10,9],[1,0,0,3]]
我想我可以使用最大二分匹配来解决这个问题,但感觉像是一个肮脏的 hack。
我是否遗漏了一些重要的步骤,或者是否有更好的方法来重建分区?
David Eisenstat 在 C++ 中提供了算法的优雅且快速的 OOP 实现。
但为了我的工作,我需要用 PHP 重写它。实现的直接翻译被证明内存效率低且速度慢。由于将每个数字包装到一个 Subset 对象中,因此内存效率低下。而且它很慢,因为在 PHP 中对对象数组进行排序比对数字数组进行排序要慢一个数量级。例如,使用这种方法将包含 500000 个数字的数组划分为 100 个子集需要 138 秒,而我的贪心算法实现只用了 9 秒。
因此,在分析器中使用了两天后,我使用数组重写了 PHP 实现。它看起来很丑陋,但它在 k 很低时比我实现的贪心算法表现得更好,而在 k 很高时也没有那么慢:
K: 2
Greedy: 330 (seconds)
Karmarkar–Karp: 6
K: 4
Greedy: 177
Karmarkar–Karp: 6
K: 8
Greedy: 85
Karmarkar–Karp: 6
K: 16
Greedy: 43
Karmarkar–Karp: 8
K: 32
Greedy: 21
Karmarkar–Karp: 11
K: 64
Greedy: 11
Karmarkar–Karp: 20
K: 128
Greedy: 8
Karmarkar–Karp: 33
K: 256
Greedy: 3
Karmarkar–Karp: 61
我希望 David Eisenstat 的回答和我的解决方案都有用。
<?php
function greedy(array $array, int $k) : array
{
$result = new \Ds\PriorityQueue();
for ($i = 0; $i < $k; $i++)
{
$result->push([], 0);
}
sort($array, SORT_NUMERIC);
while (!empty($array))
{
$number = array_pop($array);
$smallestSumArray = $result->pop();
$smallestSumArray [] = $number;
$sum = array_sum($smallestSumArray);
$result->push($smallestSumArray, -$sum);
}
return $result->toArray();
}
function karmarkarKarp(array $array, int $k) : array
{
$id = PHP_INT_MIN;
$heap = new \Ds\PriorityQueue();
$idToNumbers = new \Ds\Map();
$idToSum = new \Ds\Map();
/**
* Convert every number into an ID that is connected with a numbers array using $idToNumbers map
* and with a sum using $idToSum map
*/
foreach ($array as $number)
{
$idToNumbers[$id] = new \Ds\Stack([$number]);
$idToSum[$id] = $number;
$heap->push([$id], $number);
++$id;
}
//Partitioning:
$sumToId = [];
while ($heap->count() > 1)
{
/** @var array $a */
$a = $heap->pop();
/** @var array $b */
$b = $heap->pop();
for ($i = 0; $i < $k; $i++)
{
$reverseI = $k - 1 - $i;
if (!isset($a[$i]) && !isset($b[$reverseI])) // Instead of filling k-tuple with zeroes just check that a position is set
{
continue;
}
if (!isset($a[$i]) || !isset($b[$reverseI]))
{
$Ai = $a[$i] ?? $b[$reverseI];
unset($a[$i], $b[$reverseI]);
$sum = $idToSum[$Ai];
isset($sumToId[$sum]) ? $sumToId[$sum] []= $Ai : $sumToId[$sum] = [$Ai];
continue;
}
/** @var int $Ai */
$Ai = $a[$i];
/** @var int $Bk */
$Bk = $b[$reverseI];
unset($a[$i], $b[$reverseI]);
$aNumbers = $idToNumbers[$Ai];
$bNumbers = $idToNumbers[$Bk];
while (!$bNumbers->isEmpty())
{
$aNumbers->push($bNumbers->pop());
}
$sum = $idToSum[$Ai] + $idToSum[$Bk];
$idToSum[$Ai] = $sum;
isset($sumToId[$sum]) ? $sumToId[$sum] []= $Ai : $sumToId[$sum] = [$Ai];
$idToNumbers->remove($Bk);
$idToSum->remove($Bk);
}
krsort($sumToId); // It's faster than using usort() to sort $a by sums in $idToSum map
$a = array_merge(...$sumToId);
$sumToId = [];
$difference = $idToSum[$a[0]] - (isset($a[$k -1]) ? $idToSum[$a[$k -1]] : 0);
$heap->push($a, $difference);
}
//Preparing the resulting array:
/** @var array $last */
$last = $heap->pop();
array_walk($last, function (&$item) use ($idToNumbers) {
/** @var \Ds\Stack $numbersStack */
$numbersStack = $idToNumbers[$item];
$item = [];
while (!$numbersStack->isEmpty())
{
$item []= $numbersStack->pop();
}
});
return $last;
}
$randomArray = [];
for ($i = 0; $i < 500000; $i++)
{
$randomArray []= random_int(1, 6000);
}
for ($k = 2; $k <= 256; $k *= 2)
{
echo PHP_EOL . 'K: ' . $k;
$time = time();
$result = greedy($randomArray, $k);
echo PHP_EOL . 'Greedy: ' . (time() - $time);
gc_mem_caches();
$time = time();
$result = karmarkarKarp($randomArray, $k);
echo PHP_EOL . 'Karmarkar–Karp: ' . (time() - $time) . PHP_EOL;
gc_mem_caches();
}
最佳答案
我将使用一个阶段来实现此算法,添加数据结构以跟踪与每个总和相关联的子集。在 C++ 中:
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <iostream>
#include <list>
#include <memory>
#include <vector>
namespace {
using Number = std::uint64_t;
class Subset {
public:
Subset() : numbers_{}, sum_{} {}
explicit Subset(const Number number) : numbers_{number}, sum_{number} {}
Subset(Subset&&) = default;
Subset& operator=(Subset&&) = default;
const std::list<Number>& numbers() const { return numbers_; }
Number sum() const { return sum_; }
void Merge(Subset other) {
numbers_.splice(numbers_.end(), other.numbers_);
sum_ += other.sum_;
}
private:
Subset(const Subset&) = delete;
Subset& operator=(const Subset&) = delete;
std::list<Number> numbers_;
Number sum_;
};
std::ostream& operator<<(std::ostream& stream, const Subset& subset) {
stream << '[';
if (!subset.numbers().empty()) {
auto it{subset.numbers().begin()};
stream << *it;
for (++it; it != subset.numbers().end(); ++it) {
stream << ',' << *it;
}
}
stream << ']';
return stream;
}
struct SubsetSumGreater {
bool operator()(const Subset& a, const Subset& b) const {
return a.sum() > b.sum();
}
};
class Partition {
public:
Partition(const Number number, const std::size_t k) : subsets_(k) {
assert(k > 0);
subsets_.front().Merge(Subset{number});
}
Partition(Partition&&) = default;
Partition& operator=(Partition&&) = default;
const std::vector<Subset>& subsets() const { return subsets_; }
Number difference() const {
return subsets_.front().sum() - subsets_.back().sum();
}
void Merge(Partition other) {
assert(subsets_.size() == other.subsets_.size());
auto it{subsets_.begin()};
auto other_it{other.subsets_.rbegin()};
while (it != subsets_.end() && other_it != other.subsets_.rend()) {
it->Merge(std::move(*other_it));
++it;
++other_it;
}
std::sort(subsets_.begin(), subsets_.end(), SubsetSumGreater{});
}
private:
Partition(const Partition&) = delete;
Partition& operator=(const Partition&) = delete;
std::vector<Subset> subsets_;
};
std::ostream& operator<<(std::ostream& stream, const Partition& partition) {
stream << '[';
if (!partition.subsets().empty()) {
auto it{partition.subsets().begin()};
stream << *it;
for (++it; it != partition.subsets().end(); ++it) {
stream << ',' << *it;
}
}
stream << ']';
return stream;
}
struct DifferenceLess {
bool operator()(const Partition& a, const Partition& b) const {
return a.difference() < b.difference();
}
};
Partition KarmarkarKarp(const std::vector<Number>& numbers,
const std::size_t k) {
assert(!numbers.empty());
assert(k > 0);
std::vector<Partition> heap;
heap.reserve(numbers.size());
for (const Number number : numbers) {
heap.emplace_back(number, k);
}
std::make_heap(heap.begin(), heap.end(), DifferenceLess{});
while (heap.size() > 1) {
std::pop_heap(heap.begin(), heap.end(), DifferenceLess{});
Partition partition{std::move(heap.back())};
heap.pop_back();
std::pop_heap(heap.begin(), heap.end(), DifferenceLess{});
heap.back().Merge(std::move(partition));
std::push_heap(heap.begin(), heap.end(), DifferenceLess{});
}
return std::move(heap.front());
}
} // namespace
int main() { std::cout << KarmarkarKarp({1, 7, 5, 10, 9, 3}, 3) << '\n'; }
关于algorithm - 如何在 Karmarkar-Karp 启发式多路分区算法中重建分区?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68421928/
我是一名优秀的程序员,十分优秀!