繁体   English   中英

如何就地反转由数组表示的排列

[英]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;
}

我真的希望你自己编码,我会这样做:

取两个变量,一个指向数组的开头另一个最后交换两个位置的元素递增开始和递减结束指针分别重复这些步骤,同时开始指针小于结束指针

Edit1:给你: https ://www.geeksforgeeks.org/inverse-permutation/

没有其他解决方案适用于您的情况

我了解您正在寻找一种不同的方法,但让我们在这里讨论一下可行性:

您需要存储要替换的值,以供将来参考,否则您将忘记它! 有人可能会争辩说,为了方便起见,我们应该保留一个“已访问”标志,但这只会使代码更加丑陋、复杂和复杂,并没有真正的帮助。 所以,在我看来,除了给定的解决方案之外,根本不切实际!

对于初学者,有执行任务的标准算法std::reverse

这是一个演示程序。

#include <iostream>
#include <iterator>
#include <algorithm>

int main()
{
    int arr[] =  { 2, 3, 4, 5, 1 };

    for ( const auto &item : arr ) std::cout << item << ' ';
    std::cout << '\n';

    std::reverse( std::begin( arr ), std::end( arr ) );

    for ( const auto &item : arr ) std::cout << item << ' ';
    std::cout << '\n';
}

它的输出是

2 3 4 5 1 
1 5 4 3 2 

如果您要专门为数组编写自己的函数,则可以按以下方式查找

#include <iostream>
#include <utility>

template <typename T>
void reverse( T *a, size_t n )
{
    for ( size_t i = 0; i < n / 2; i++ )
    {
        std::swap( a[i], a[n - i - 1] );
    }
}

int main()
{
    int arr[] =  { 2, 3, 4, 5, 1 };
    const size_t N = sizeof( arr ) / sizeof( *arr );

    for ( const auto &item : arr ) std::cout << item << ' ';
    std::cout << '\n';

    ::reverse( arr, N );

    for ( const auto &item : arr ) std::cout << item << ' ';
    std::cout << '\n';
}

其输出与上图相同,即

2 3 4 5 1 
1 5 4 3 2 

我发现了这篇最近的论文https://arxiv.org/pdf/1901.01926.pdf ,其中所有内容都很好地排版并解释了。 显然,时间和空间复杂度之间总是存在折衷,并且没有算法(还没有?)是 O(n)-in-time 和 in-place。 (该论文声称是第一个次二次确定性就地算法。)

您发布的算法未在论文中列出,时间上为 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;
    }
}

然后是从论文中的伪代码翻译而来的表中列为“folklore”的算法(论文中的清单 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 %    

这是我的方法,我通过将等于数组大小的偏移量添加到新设置的值来排列循环并标记已完成的操作。 只有在所有循环都被置换后,我才会删除偏移量。 这就像我能做到的一样简单。

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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM