【问题标题】:Shifting elements in an array C++移动数组中的元素 C++
【发布时间】:2009-10-23 21:47:31
【问题描述】:

我为我的堆栈对象类开发了一种称为“旋转”的方法。我所做的是,如果堆栈包含元素:{0,2,3,4,5,6,7},我需要前后旋转元素。

如果我需要向前旋转 2 个元素,那么我们将在数组中有 {3,4,5,6,7,0,2}。如果我需要向后旋转或 -3 个元素,那么,查看原始数组将是 {5,6,7,0,2,3,4}

所以我开发的方法工作正常。它只是非常低效的IMO。我想知道是否可以使用 mod 运算符来包装数组?或者如果它们是我还没有意识到的无用代码,等等。

我想我的问题是,我怎样才能简化这个方法?例如使用更少的代码。 :-)

void stack::rotate(int r)
{
    int i = 0;
    while ( r > 0 ) // rotate postively.
    {
        front.n = items[top+1].n;
        for ( int j = 0; j < bottom; j++ )
        {
            items[j] = items[j+1];                                  
        }
        items[count-1].n = front.n;
        r--;
    }

    while ( r < 0 )  // rotate negatively.
    {
        if ( i == top+1 )
        {
            front.n = items[top+1].n;  
            items[top+1].n = items[count-1].n; // switch last with first
        }

        back.n = items[++i].n; // second element is the new back
        items[i].n = front.n; 
        if ( i == bottom )
        {
            items[count-1].n = front.n; // last is first
            i = 0;  
            r++;   
            continue;
        }
        else
        {
            front.n = items[++i].n;
            items[i].n  = back.n;
            if ( i == bottom )
            {
                i = 0;
                r++; 
                continue;
            }
        }
    }
}

【问题讨论】:

  • 你打算怎么用这个?您是一次访问一个结果,还是想要传递整个数组?
  • 这是一个控制台应用程序。我只是逐个打印每个元素。
  • 使用链表会让事情变得容易很多。
  • 是否考虑过使用 STL 算法 std::rotate ?

标签: c++ algorithm


【解决方案1】:

您可以更改“开始”的定义,而不是移动堆栈中的所有项目。有一个表示堆栈中第一项的索引,开始时为 0,当您想要旋转堆栈时,您可以使用模运算对其进行加减运算。

请注意,如果您采用这种方法,则不应让您的类的用户访问底层数组(不是您真的无论如何都应该...)。

【讨论】:

  • 有时你只需要有一个更高的观点,而不是直接去实现。
【解决方案2】:

好吧,由于这是对数组的抽象,您可以将“零”索引存储为抽象的成员,并根据第一个元素的这个抽象概念对数组进行索引。大概……

class WrappedArray
{
  int length;
  int first;
  T *array;

  T get(int index)
  {
    return array[(first + index) % length];
  }

  int rotateForwards()
  {
    first++;
    if (first == length)
      first = 0;
  }
}

【讨论】:

    【解决方案3】:

    你已经得到了几个合理的答案,但也许再有一个不会有什么坏处。我的第一反应是让你的堆栈成为一个 std::deque 的包装器,在这种情况下,将一个元素从一端移动到另一端很便宜 (O(1))。

    【讨论】:

    • 或者您可以使用 valarray 并使用内置的 cshift 成员函数。
    • valarray 适用于旋转部分,但不适用于添加或删除项目。 valarray::resize() 覆盖当前数组中的所有值...
    【解决方案4】:

    您在此处所追求的是一个循环列表。
    如果您坚持将项目存储在数组中,只需使用顶部偏移量和大小进行访问。这种方法使得在达到分配大小后插入元素变得昂贵(重新分配、复制)。这可以通过使用双向链表(ala std::list)和迭代器来解决,但是对堆栈的任意访问将是 O(n)。

    【讨论】:

      【解决方案5】:

      下面的函数rotate是基于提醒的(你的意思是在'mod'操作下吗?)

      效率也很高。

      // Helper function.
      // Finds GCD. 
      // See http://en.wikipedia.org/wiki/Euclidean_algorithm#Implementations
      int gcd(int a, int b) {return b == 0 ? a : gcd(b, a % b);}
      
      // Number of assignments of elements in algo is
      // equal to (items.size() + gcd(items.size(),r)).
      void rotate(std::vector<int>& items, int r) {
        int size = (int)items.size();
        if (size <= 1) return; // nothing to do
        r = (r % size + size) % size; // fits r into [0..size)
        int num_cycles = gcd(size, r);
        for (int first_index = 0; first_index < num_cycles; ++first_index) {
          int mem = items[first_index]; // assignment of items elements
          int index = (first_index + r) % size, index_prev = first_index;
          while (index != first_index) {
            items[index_prev] = items[index]; // assignment of items elements
            index_prev = index;
            index = (index + r) % size;
          };
          items[index_prev] = mem; // assignment of items elements
        }
      }
      

      当然,如果您可以按照其他答案中的描述更改数据结构,您可以获得更有效的解决方案。

      【讨论】:

      • 本特利的书中称其为“杂耍方法”。根据这本书,这种方法虽然在理论上是最优的,但在实践中却是效率最低的。此方法的性能受到其不良缓存行为的负面影响。性能结果可能已经过时,但仍然值得考虑。
      • 是的,你是对的。我提供这种方法基于以下原因: 1. 灯罩询问基于mod的方法。 2. 我假设数据结构不能改变。 3.提供易于使用的答案(现成的代码,不需要修改任何东西或找书看)。实际上不同的答案提供了不同的方法来解决原始问题。
      【解决方案6】:

      现在,通常的“它已经在 Boost 中”回答:有一个 Boost.CircularBuffer

      【讨论】:

      • 我很惊讶地看到std::rotate() 在算法中可以与任何具有ForwardIterators 的东西一起使用(尽管可能效率不高)。我需要复习&lt;algorithm&gt;...
      【解决方案7】:

      如果出于某种原因您希望对数组元素执行实际的物理旋转,您可能会在 Jon Bentley 的“Programming Pearls”中找到几个替代解决方案(第 2 列,2.3 原始的力量 )。实际上,网络搜索Rotating Algorithms 'Programming Pearls' 会告诉你一切。您现在使用的文字方法几乎没有实际价值。

      如果您想尝试自己解决问题,尝试以不同的方式看待问题可能会有所帮助。您会看到,“旋转数组”实际上与“交换数组的两个不相等部分”是一回事。用后者的术语思考这个问题可能会导致您找到新的解决方案:)

      例如,

      • 反转方法。反转整个数组中元素的顺序。然后将两个部分独立反转。你完成了。

      例如,假设我们要将abcdefg 向右旋转 2

      abcdefg -> 反转整体 -> gfedcba -> 反转两部分 -> fgabcde

      附: Slides 用于“编程珍珠”的那一章。请注意,在 Bentley 的实验中,上述算法被证明是非常有效的(在三个测试中)。

      【讨论】:

        【解决方案8】:

        我不明白变量 front 和 back 是什么意思,以及为什么需要 .n。无论如何,这是我所知道的用于旋转数组元素的最短代码,也可以在 Bentley 的书中找到。

        #include <algorithm>
        
        std::reverse(array    , array + r   );
        std::reverse(array + r, array + size);
        std::reverse(array    , array + size);
        

        【讨论】:

        • 在 MSVC++ 实现中,std::rotate 专门用于双向迭代器。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多