如何就地反转由数组表示的排列
How to invert a permutation represented by an array in-place
我试图编写一个函数来反转数组并打印其逆排列,而无需创建新数组。
给定一个大小为 n 的整数数组,范围从 1 到 n,我们需要找到该数组的逆排列。
逆排列是一种排列,您可以通过在数组中元素值指定的位置插入元素的位置来获得。
我编写了可以反转给出输出数组的代码,但我必须创建一个新数组才能做到这一点。如何就地做?
#include<iostream>
using namespace std;
void inverse(int arr[], int size) {
int arr2[size];
for (int i = 0; i < size; i++)
arr2[arr[i] - 1] = i + 1;
for (int i = 0; i < size; i++)
cout << arr2[i] << " ";
}
int main() {
int arr[] = {2, 3, 4, 5, 1};
int size = sizeof(arr) / sizeof(arr[0]);
inverse(arr, size);
return 0;
}
我真的希望你自己编码,这是我的做法:
取两个变量,一个指向数组的开头,另一个指向最后 交换两个位置的元素 分别递增开始和递减结束指针 当开始指针小于结束指针时重复这些步骤
编辑1: 给你: https://www.geeksforgeeks.org/inverse-permutation/
没有其他解决方案适合您的情况
我知道您正在寻找不同的方法,但让我们在这里讨论可行性:
您需要存储要替换的值以供将来参考,否则您将失去对它的跟踪! 有人可能会争辩说,为了方便起见,我们应该保留一个"访问"标志,但这只会使代码更加丑陋、复杂和复杂,并没有真正的帮助。因此,在我看来,给出以外的解决方案根本不实用!
这是我的方法,我排列循环并通过向新设置的值添加等于数组大小的偏移量来标记已完成的操作。只有在所有周期都排列完毕后,我才会删除偏移量。它就像我能做到的一样简单。
void index_value_permutation(size_t *a, size_t n)
{
size_t i, i1, i2, ic=0;
start:
// Look for next cycle
for (; ic < n; ic++)
if (a[ic] != ic && a[ic] < n)
break;
i = ic;
ic++;
// If a cycle was found
if (i < n)
{
i1 = a[i];
// Permutate this cycle
while (a[i1] < n)
{
i2 = a[i1]; // save the original value
a[i1] = i + n; // add n to signal it as being permutated
i = i1;
i1 = i2;
}
goto start;
}
// Remove the n offset
for (i=0; i < n; i++)
if (a[i] >= n)
a[i] -= n;
}
我发现这篇最近的论文 https://arxiv.org/pdf/1901.01926.pdf,其中所有内容都很好地排版和解释。 显然,时间和空间复杂性之间总是存在折衷,并且没有算法(还?)是O(n)在时间和就地的。 (该论文声称是第一个亚二次确定性就地算法。
您发布的算法未在论文中列出,并且在时间上为O(n),在空间上为O(n)(即不合适)。
我将其发布在这里以供参考,也是为了检查其他实现的正确性。 为了简单起见,我使用了零基索引。
// O(n) out-of-place algorithm
template<class It, class Size, class ItOut>
void inverse_permutation_n(It first, Size n, ItOut d_first){
for(Size i = 0; i != n; ++i){
d_first[first[i]] = i;
}
}
然后是算法,该算法在从论文中的伪代码翻译而来的表格中被列为"民间传说"(论文中的清单 3)。
#include<algorithm> // for std::min
template<class It, class Index>
void reverse_cycle(It t, Index start){
auto cur = t[start];
auto prev = start;
while( cur != start ){
auto next = t[cur];
t[cur] = prev;
prev = cur;
cur = next;
}
t[start] = prev;
}
template<class It, class Index>
auto cycle_leader(It t, Index start){
auto cur = t[start];
auto smallest = start;
while(cur != start){
smallest = std::min(smallest, cur);
cur = t[cur];
}
return smallest;
}
// O(n²) in-place algorithm
template<class It, class Size>
void inverse_permutation_n(It first, Size n){
for(Size i = 0; i != n; ++i){
if( cycle_leader(first, i) == i ) reverse_cycle(first, i);
}
}
上述算法在平均情况下是 O(n²) 时间。 原因是对于每个点,您必须遵循长度为O(n)的循环才能找到领导者。
然后,您可以通过在循环领导者搜索中放置快捷方式来在此基础上进行构建,一旦周期领导者小于起点,搜索就会返回 false。
template<class It, class Index>
auto cycle_leader_shortcut(It t, Index start){
auto cur = t[start];
while(cur != start){
if(cur < start) return false;
cur = t[cur];
}
return true;
}
// O(n log n) in-place algorithm
template<class It, class Size>
void inverse_permutation_shortcut_n(It first, Size n){
for(Size i = 0; i != n; ++i){
if( cycle_leader_shortcut(first, i) ) reverse_cycle(first, i);
}
}
幸运的是,这个算法是O(N log N)(我认为是平均的)。 原因是随着序列中的点的增加,周期迭代会变短,因为一个点更有可能具有已经反转的低值。
这是基准和结果:
#include<numeric> // for iota
#include<random>
// initialize rng
std::random_device rd;
std::mt19937 g(rd());
static void BM_inverse_permutation(benchmark::State& state){
// allocate memory and initialize test buffer and reference solution
auto permutation = std::vector<std::size_t>(state.range(0));
std::iota(permutation.begin(), permutation.end(), 0);
auto reference_inverse_permutation = std::vector<std::size_t>(permutation.size());
for(auto _ : state){
state.PauseTiming(); // to random shuffle and calculate reference solution
std::shuffle(permutation.begin(), permutation.end(), g);
// inverse_permutation_n(permutation.cbegin(), permutation.size(), reference_inverse_permutation.begin());
benchmark::DoNotOptimize(permutation.data());
benchmark::ClobberMemory();
state.ResumeTiming();
inverse_permutation_n(permutation.begin(), permutation.size());
benchmark::DoNotOptimize(permutation.data());
benchmark::ClobberMemory();
// state.PauseTiming(); // to check that the solution is correct
// if(reference_inverse_permutation != permutation) throw std::runtime_error{""};
// state.ResumeTiming();
}
state.SetItemsProcessed(state.iterations()*permutation.size() );
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_inverse_permutation)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(benchmark::oNSquared);
static void BM_inverse_permutation_shortcut(benchmark::State& state){
// allocate memory and initialize test buffer and reference solution
auto permutation = std::vector<std::size_t>(state.range(0));
std::iota(permutation.begin(), permutation.end(), 0);
auto reference_inverse_permutation = std::vector<std::size_t>(permutation.size());
for(auto _ : state){
state.PauseTiming(); // to random shuffle and calculate reference solution
std::shuffle(permutation.begin(), permutation.end(), g);
// inverse_permutation_n(permutation.cbegin(), permutation.size(), reference_inverse_permutation.begin());
benchmark::DoNotOptimize(permutation.data());
benchmark::ClobberMemory();
state.ResumeTiming();
inverse_permutation_shortcut_n(permutation.begin(), permutation.size());
benchmark::DoNotOptimize(permutation.data());
benchmark::ClobberMemory();
// state.PauseTiming(); // to check that the solution is correct
// if(reference_inverse_permutation != permutation) throw std::runtime_error{""};
// state.ResumeTiming();
}
state.SetItemsProcessed(state.iterations()*permutation.size() );
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_inverse_permutation_shortcut)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(benchmark::oNLogN);
BENCHMARK_MAIN();
$ c++ a.cpp -O3 -DNDEBUG -lbenchmark && ./a.out
2021-03-30 21:16:55
Running ./a.out
Run on (12 X 4600 MHz CPU s)
CPU Caches:
L1 Data 32K (x6)
L1 Instruction 32K (x6)
L2 Unified 256K (x6)
L3 Unified 12288K (x1)
Load Average: 1.26, 1.80, 1.76
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
-----------------------------------------------------------------------------------------------
Benchmark Time CPU Iterations UserCounters...
-----------------------------------------------------------------------------------------------
BM_inverse_permutation/8 476 ns 478 ns 1352259 items_per_second=16.7276M/s
BM_inverse_permutation/16 614 ns 616 ns 1124905 items_per_second=25.9688M/s
BM_inverse_permutation/32 1106 ns 1107 ns 630398 items_per_second=28.9115M/s
BM_inverse_permutation/64 2929 ns 2931 ns 238236 items_per_second=21.835M/s
BM_inverse_permutation/128 10748 ns 10758 ns 64708 items_per_second=11.898M/s
BM_inverse_permutation/256 41556 ns 41582 ns 16600 items_per_second=6.15656M/s
BM_inverse_permutation/512 164006 ns 164023 ns 4245 items_per_second=3.12151M/s
BM_inverse_permutation/1024 621341 ns 620840 ns 1056 items_per_second=1.64938M/s
BM_inverse_permutation/2048 2468060 ns 2466060 ns 293 items_per_second=830.474k/s
BM_inverse_permutation/4096 10248540 ns 10244982 ns 93 items_per_second=399.805k/s
BM_inverse_permutation/8192 55926122 ns 55926230 ns 10 items_per_second=146.479k/s
BM_inverse_permutation_BigO 0.82 N^2 0.82 N^2
BM_inverse_permutation_RMS 18 % 18 %
BM_inverse_permutation_shortcut/8 499 ns 501 ns 1193871 items_per_second=15.9827M/s
BM_inverse_permutation_shortcut/16 565 ns 567 ns 1225056 items_per_second=28.2403M/s
BM_inverse_permutation_shortcut/32 740 ns 742 ns 937909 items_per_second=43.1509M/s
BM_inverse_permutation_shortcut/64 1121 ns 1121 ns 619016 items_per_second=57.0729M/s
BM_inverse_permutation_shortcut/128 1976 ns 1977 ns 355982 items_per_second=64.745M/s
BM_inverse_permutation_shortcut/256 3644 ns 3645 ns 191387 items_per_second=70.2375M/s
BM_inverse_permutation_shortcut/512 7282 ns 7288 ns 95434 items_per_second=70.2481M/s
BM_inverse_permutation_shortcut/1024 14732 ns 14752 ns 47417 items_per_second=69.4165M/s
BM_inverse_permutation_shortcut/2048 30590 ns 30398 ns 23079 items_per_second=67.3728M/s
BM_inverse_permutation_shortcut/4096 64374 ns 64039 ns 10766 items_per_second=63.9613M/s
BM_inverse_permutation_shortcut/8192 196961 ns 195786 ns 3646 items_per_second=41.8416M/s
BM_inverse_permutation_shortcut_BigO 1.74 NlgN 1.73 NlgN
BM_inverse_permutation_shortcut_RMS 27 % 27 %
- 寻找一种更好的方法来表示无符号字符数组
- 数组指针表示法C++(移动数组时)
- 在 C++ 中将整数数组转换为位集表示形式的最佳方法?
- 表示带有输入的数组中的元素数?
- N 维数组的大 O 表示法
- 为什么使用 size_t 来索引/表示数组的大小?
- 如何更好地表示 6 个整数键而不是作为 6 维数组的索引?
- 十六进制 QString 表示为无符号字符数组
- 如何将升压数组表示为类型的指针?
- 如何就地反转由数组表示的排列
- C++:如何解释图像的字节数组表示
- C/C++ int[] vs int*(指针与数组表示法).有什么区别
- C++ 一种方法,该方法将十进制值作为用布尔数组表示的二进制的整数返回
- 具有多维数组表示法的指针算术
- C++指针与数组表示法
- 在函数声明中,传递固定大小的数组表示什么
- 如何在 CUDA 中从稀疏数组表示形式变为密集数组表示形式
- 使用Cilk数组表示法和STL向量
- 尝试在此测试分数程序上使用指针表示法而不是数组表示法,但我被困住了
- 最小堆通过二叉树的数组表示;MoveDown函数无限循环