【问题标题】:Move all odd positioned element to left half and even positioned to right half in-place将所有奇数定位元素移动到左半边,偶数定位到右半边原地
【发布时间】:2012-09-02 13:12:23
【问题描述】:

给定一个包含正整数和负整数的数组,将所有奇数索引元素移至左侧,将偶数索引元素移至右侧。

问题的难点在于在保持秩序的同时就地完成。

例如

7, 5, 6, 3, 8, 4, 2, 1

输出应该是:

5, 3, 4, 1, 7, 6, 8, 2

如果顺序无关紧要,我们可以使用快速排序的 partition() 算法。

O(N)怎么办?

【问题讨论】:

  • O(n) 空间+时间使用两个列表是微不足道的。我假设您正在寻找 O(1) 空间?
  • @amit,是的。就地意味着 O(1) 空间。
  • 本文介绍O(N) in-place算法:arxiv.org/abs/0805.1598
  • 为什么 7 向右移动?左边的奇数应该留在左边吗?你对输出顺序有什么打算?这个问题一点都不清楚。
  • @AdrianMcCarthy:问题不在于奇数/偶数,而是关于奇数/偶数定位的元素。 7 在偶数位置,所以它向右。 cmets中讨论了重新排列奇数/偶数的问题。

标签: performance algorithm data-structures time-complexity in-place


【解决方案1】:
  1. 获取大小为 3k+1
  2. 的最大子数组
  3. 对该子数组的各个部分应用循环领导算法,从位置 1、3、9、... 3 开始k-1:将元素移动到子数组中的正确位置(子数组左侧的偶数索引元素,右侧的奇数索引元素),被替换的元素也应该移动到它的正确位置,等等,直到这个过程回到起始位置。 This paper 给出了数论解释,为什么选择这样的起始位置会将子数组打乱到正确的顺序。
  4. 使用步骤 1 和 2 递归处理数组的其余部分。
  5. 现在我们只需要将重新订购的部件连接在一起。从整个数组末尾的较小子数组开始。要交换子数组的一半,请使用反向算法:reverse(reverse(a), reverse(b));或者,对于大小相等的子数组,使用成对交换。
  6. 现在所有偶数位置的元素都在左侧。为了使它们在右边,根据需要,将元素 i 和 i+N/2 交换为所有 i = 0 .. N/2-1。

算法就地,时间复杂度O(N)。

例子:

0 1 2 3 4  5 6 7 8 9   10 11 (the original array)
0 1 2 3 4  5 6 7 8 9 # 10 11 (split to sub-arrays)
0 2 4 3 8  1 6 5 7 9 # 10 11 (first cycle leader iteration, starting with 1)
0 2 4 6 8  1 3 5 7 9 # 10 11 (second cycle leader iteration, starting with 3)
0 2 4 6 8  9 7 5 3 1 # 10 11(2nd half of 1st part& 1st half of 2nd part reversed)
0 2 4 6 8 10 1 3 5 7    9 11 (both halves reversed together)

此算法的变体,不需要第 5 步:

  • 在第 1 步中,获取大小为 3k-1 的最大子数组。
  • 在步骤 2 中,将偶数索引元素移动到子数组的右侧,奇数索引元素 - 移动到左侧。使用起始位置 0, 2, 8, ... 3k-1-1 进行循环引导算法。

这里是不同的 O(N log N) 就地算法,不需要数论证明:

  1. 将您的数组重新解释为单元素 2*2 矩阵的序列,转置这些矩阵。
  2. 将结果重新解释为二元素 2*2 矩阵的序列并将它们转置。
  3. 在矩阵大小小于数组大小时继续。
  4. 现在我们只需要将重新排序的部分连接在一起(与之前的算法完全相同)。
  5. 交换数组左右两半的元素(与之前的算法完全相同)。

例子:

0  1   2 3   4 5   6 7  (the original array)
[0 2] [1 3] [4 6] [5 7] (first transposition)
[0 2] [4 6] [1 3] [5 7] (second transposition)

这个问题只是In-place matrix transposition的一个特例。

【讨论】:

  • 您能否解释一下0 1 2 3 4 5 6 7 在第一次循环领导者迭代后如何转换为0 2 4 3 1 5 6 7
  • @Cupidvogel: 1 到正确的位置并替换 4。然后 4 替换 2。然后 2 到之前被 1 占用的空位。所有其他元素都保留在它们的位置。
  • 我刚刚注意到原始数组需要转换:0->4,4->6,6->7,7->3,3->1,1->0 和 '2->5, 5->2'。根据您建议的论文,任何新排列都是此类循环领导者轮换的不相交集。您的步骤 2-4 适用于何处?其次,是否有任何保证这些旋转不会持续相当长的时间(这里只有 6 和 2 组的 2 组)使其不再保持线性?
  • @Cupidvogel:算法应该将偶数定位的元素移动到左边(这意味着我们需要在最后进行额外的块交换以满足这个问题的要求)。此外,算法仅适用于大小 3^k - 1,这就是需要步骤 3..4 的原因。旋转不会持续很长时间,因为每个元素只移动一次,一旦回到起始位置我们就会停止。
  • 2 个问题。我刚刚注意到,即使对于长度不是3^k-1 形式的数组,也只有第 1 步就足够了。例如,在长度为 10 的数组中 '4,7,2,9,8,5,1,3,6,5, to convert it into the array 7 9 5 3 5 4 2 8 1 6, only 1 cycle is required: 0->5,5 ->2,2->6,6->8,8->9,9->4,4->7,7->3,3->1 & 1->0, as summarized by i -> L /2 + i/2` 当i%2 == 0,否则i -> i/2。此处不需要步骤 3-4。对于给定的数组,需要两组不相交的循环。那么什么时候只有 1 个循环就足够了,什么时候需要超过 1 个呢?如果需要超过 1 个,如何知道呢?
【解决方案2】:

我尝试按照 Evgeny Kluev 所说的来实现,结果如下:

#pragma once

#include <iterator>
#include <algorithm>
#include <type_traits>
#include <limits>
#include <deque>
#include <utility>

#include <cassert>

template< typename Iterator >
struct perfect_shuffle_permutation
{

    static_assert(std::is_same< typename std::iterator_traits< Iterator >::iterator_category, std::random_access_iterator_tag >::value,
                  "!");

    using difference_type = typename std::iterator_traits< Iterator >::difference_type;
    using value_type = typename std::iterator_traits< Iterator >::value_type;

    perfect_shuffle_permutation()
    {
        for (difference_type power3_ = 1; power3_ < std::numeric_limits< difference_type >::max() / 3; power3_ *= 3) {
            powers3_.emplace_back(power3_ + 1);
        }
        powers3_.emplace_back(std::numeric_limits< difference_type >::max());
    }

    void
    forward(Iterator _begin, Iterator _end) const
    {
        return forward(_begin, std::distance(_begin, _end));
    }

    void
    backward(Iterator _begin, Iterator _end) const
    {
        return backward(_begin, std::distance(_begin, _end));
    }

    void
    forward(Iterator _begin, difference_type const _size) const
    {
        assert(0 < _size);
        assert(_size % 2 == 0);
        difference_type const left_size_ = *(std::upper_bound(powers3_.cbegin(), powers3_.cend(), _size) - 1);
        cycle_leader_forward(_begin, left_size_);
        difference_type const rest_ = _size - left_size_;
        if (rest_ != 0) {
            Iterator middle_ = _begin + left_size_;
            forward(middle_, rest_);
            std::rotate(_begin + left_size_ / 2, middle_, middle_ + rest_ / 2);
        }
    }

    void
    backward(Iterator _begin, difference_type const _size) const
    {
        assert(0 < _size);
        assert(_size % 2 == 0);
        difference_type const left_size_ = *(std::upper_bound(powers3_.cbegin(), powers3_.cend(), _size) - 1);
        std::rotate(_begin + left_size_ / 2, _begin + _size / 2, _begin + (_size + left_size_) / 2);
        cycle_leader_backward(_begin, left_size_);
        difference_type const rest_ = _size - left_size_;
        if (rest_ != 0) {
            Iterator middle_ = _begin + left_size_;
            backward(middle_, rest_);
        }
    }

private :

    void
    cycle_leader_forward(Iterator _begin, difference_type const _size) const
    {
        for (difference_type leader_ = 1; leader_ != _size - 1; leader_ *= 3) {
            permutation_forward permutation_(leader_, _size);
            Iterator current_ = _begin + leader_;
            value_type first_ = std::move(*current_);
            while (++permutation_) {
                assert(permutation_ < _size);
                Iterator next_ = _begin + permutation_;
                *current_ = std::move(*next_);
                current_ = next_;
            }
            *current_ = std::move(first_);
        }
    }

    void
    cycle_leader_backward(Iterator _begin, difference_type const _size) const
    {
        for (difference_type leader_ = 1; leader_ != _size - 1; leader_ *= 3) {
            permutation_backward permutation_(leader_, _size);
            Iterator current_ = _begin + leader_;
            value_type first_ = std::move(*current_);
            while (++permutation_) {
                assert(permutation_ < _size);
                Iterator next_ = _begin + permutation_;
                *current_ = std::move(*next_);
                current_ = next_;
            }
            *current_ = std::move(first_);
        }
    }

    struct permutation_forward
    {

        permutation_forward(difference_type const _leader, difference_type const _size)
            : leader_(_leader)
            , current_(_leader)
            , half_size_(_size / 2)
        { ; }

        bool
        operator ++ ()
        {
            if (current_ < half_size_) {
                current_ += current_;
            } else {
                current_ = 1 + (current_ - half_size_) * 2;
            }
            return (current_ != leader_);
        }

        operator difference_type () const
        {
            return current_;
        }

    private :

        difference_type const leader_;
        difference_type current_;
        difference_type const half_size_;

    };

    struct permutation_backward
    {

        permutation_backward(difference_type const _leader, difference_type const _size)
            : leader_(_leader)
            , current_(_leader)
            , half_size_(_size / 2)
        { ; }

        bool
        operator ++ ()
        {
            if ((current_ % 2) == 0) {
                current_ /= 2;
            } else {
                current_ = (current_ - 1) / 2 + half_size_;
            }
            return (current_ != leader_);
        }

        operator difference_type () const
        {
            return current_;
        }

    private :

        difference_type const leader_;
        difference_type current_;
        difference_type const half_size_;

    };

    std::deque< difference_type > powers3_;

};

【讨论】:

  • 我认为这种实现具有 O(N log N) 复杂性,因为您从较大的一个开始交换子数组(因此您在每次迭代中移动一个不断增长的子数组)。
  • 另一种方法是保持所有子数组的长度,然后使用反向函数进行反向运动。但这违反了 O(log log n) 内存的要求
  • 或者,我们可以每退一步计算下一个小子数组的(增长)长度——这将是 O (log n log n) 时间。
  • 请告诉我如何以 O(n) 时间复杂度和 O(log log n) 内存执行步骤 4?
  • 您刚刚展示了两种不违反任何要求的方法。第二个,每一步的长度重新计算,需要 O((log N)^3) 额外的时间。但是is小于O(N),所以整个算法仍然有O(N)的时间复杂度。第一个,“保留所有子数组的长度”应该更正:只保留每个大小(0 .. 3)的子数组的数量,即2 * log N位。您可以将这些位打包成一对整数。
【解决方案3】:

我修改了代码here得到了这个算法:

void PartitionIndexParity(T arr[], size_t n)
{
    using std::swap;
    for (size_t shift = 0, k; shift != n; shift += k)
    {
        k = (size_t)pow(3, ceil(log(n - shift) / log(3)) - 1) + 1;
        for (size_t i = 1; i < k; i *= 3)  // cycle-leader algorithm
        {
            size_t j = i;
            do { swap(arr[(j = j / 2 + (j % 2) * (k / 2)) + shift], arr[i + shift]); } while (j != i);
        }

        for (size_t b = shift / 2, m = shift, e = shift + (k - k / 2), i = m; shift != 0 && k != 0; )  // or just use std::rotate(arr, b, m, e)
        {
            swap(arr[b++], arr[i++]);
            if (b == m && i == e) { break; }
            if (b == m) { m = i; }
            else if (i == e) { i = m; }
        }
    }
}

【讨论】:

    【解决方案4】:

    这是 Peiyush Jain 算法的 Java 实现:

    import java.util.Arrays;
    
    public class InShuffle {
        public static void main(String[] args) {
            Integer[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
    
            inShuffle(nums);
    
            System.out.println(Arrays.toString(nums));
        }
    
        public static <T> void inShuffle(T[] array) {
            if (array == null) {
                return;
            }
    
            inShuffle(array, 0, array.length - 1);
        }
    
        private static <T> void inShuffle(T[] array, int startIndex, int endIndex) {
            while (endIndex - startIndex + 1 > 1) {
                int size = endIndex - startIndex + 1;
                int n = size / 2;
                int k = (int)Math.floor(Math.log(size + 1) / Math.log(3));
                int m = (int)(Math.pow(3, k) - 1) / 2;
    
                rotateRight(array, startIndex + m, startIndex + n + m - 1, m);
    
                for (int i = 0, cycleStartIndex = 0; i < k; ++i, cycleStartIndex = cycleStartIndex * 3 + 2) {
                    permuteCycle(array, startIndex, cycleStartIndex, 2 * m + 1);
                }
    
                endIndex = startIndex + 2 * n - 1;
                startIndex = startIndex + 2 * m;
            }
        }
    
        private static <T> void rotateRight(T[] array, int startIndex, int endIndex, int amount) {
            reverse(array, startIndex, endIndex - amount);
            reverse(array, endIndex - amount + 1, endIndex);
            reverse(array, startIndex, endIndex);
        }
    
        private static <T> void reverse(T[] array, int startIndex, int endIndex) {
           for (int leftIndex = startIndex, rightIndex = endIndex; leftIndex < rightIndex; ++leftIndex, --rightIndex) {
               swap(array, leftIndex, rightIndex);
           }
        }
    
        private static <T> void swap(T[] array, int index1, int index2) {
            T temp = array[index1];
            array[index1] = array[index2];
            array[index2] = temp;
        }
    
        private static <T> void permuteCycle(T[] array, int offset, int startIndex, int mod) {
            for (int i = ((2 * startIndex + 2) % mod) - 1; i != startIndex; i = ((2 * i + 2) % mod) - 1) {
                swap(array, offset + i, offset + startIndex);
            }
        }
    }
    

    做一个洗牌很简单:

    public static <T> void outShuffle(T[] array) {
        if (array == null) {
           return;
        }
    
        inShuffle(array, 1, array.length - 1);
    }
    

    【讨论】:

      【解决方案5】:
      public class OddToLeftEvenToRight {
      
          private static void doIt(String input){
      
              char[] inp = input.toCharArray();
              int len = inp.length;
      
              for(int j=1; j< len; j++)
              {
                  for(int i=j; i<len-j; i+=2)
                  {
      
                      swap(inp, i, i+1);
      
                  }
              }
      
              System.out.print(inp);
      
          }
      
          private static void swap(char[] inp, int i, int j) {
      
              char tmp = inp[i];
              inp[i]= inp[j];
              inp[j]=tmp;
      
          }
      
          public static void main(String[] args)
          {
      
              doIt("a1b");
          }
      
      }
      

      这个程序在 O(n^2) 内完成。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-01-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-26
        相关资源
        最近更新 更多