【问题标题】:Is there an efficient data structure for row and column swapping?是否有用于行和列交换的有效数据结构?
【发布时间】:2011-08-15 07:19:24
【问题描述】:

我有一个数字矩阵,我希望能够:

  1. 交换行
  2. 交换列

如果我要使用指向行的指针数组,那么我可以轻松地在 O(1) 中的行之间切换,但交换一列是 O(N),其中 N 是行数。

我有一种明显的感觉,没有一种双赢的数据结构可以为两种操作提供 O(1),尽管我不确定如何证明这一点。还是我错了?

【问题讨论】:

    标签: data-structures computer-science


    【解决方案1】:

    完全没有想到这一点:

    我认为您使用指向行的指针的想法是正确的开始。然后,为了能够“交换”列,我只需要另一个具有列数大小的数组,并在每个字段中存储列当前物理位置的索引。

    m = 
    [0] -> 1 2 3
    [1] -> 4 5 6
    [2] -> 7 8 9 
    
    c[] {0,1,2}
    

    现在要交换第 1 列和第 2 列,您只需将 c 更改为 {0,2,1}

    当你想阅读第 1 行时,你会这样做

    for (i=0; i < colcount; i++) {
       print m[1][c[i]];
    }
    

    【讨论】:

      【解决方案2】:

      这里只是一个随机的(没有经验知道这有多好用,而且这是一个没有咖啡的深夜):

      我在想的是矩阵的内部是一个哈希表而不是一个数组。

      数组中的每个单元格都包含三个信息:

      1. 单元格所在的行
      2. 单元格所在的列
      3. 单元格的值

      在我看来,这很容易用元组 ((i, j), v) 表示,其中 (i, j) 表示单元格的位置(第 i 行,第 j 列),而 v

      这将是矩阵的某种正常表示。但是,让我们在这里抽象一下这些想法。而不是 i 将行表示为一个位置(即 0 之前 1 之前 2 之前 3 等等),让我们将i 视为它对应行的某种规范标识符。让我们为j 做同样的事情。 (虽然在最一般的情况下,ij 可以不受限制,让我们假设一个简单的情况,对于 M x N,它们将保持在 [0..M] 和 [0..N] 范围内矩阵,但不表示单元格的实际坐标)。

      现在,我们需要一种方法来跟踪行的标识符以及与该行关联的当前索引。这显然需要一个键/值数据结构,但是由于索引的数量是固定的(矩阵通常不会增长/收缩),并且只处理整数索引,我们可以将其实现为一个固定的一维数组。对于 M 行的矩阵,我们可以有(在 C 中):

      int RowMap[M];
      

      对于第 m 行,RowMap[m] 给出当前矩阵中行的标识符。

      我们将对列使用相同的内容:

      int ColumnMap[N];
      

      其中ColumnMap[n] 是第n 列的标识符。

      现在回到我一开始提到的哈希表:

      既然我们有完整的信息(矩阵的大小),我们应该能够生成一个完美的散列函数(没有冲突)。这是一种可能性(对于中等大小的数组):

      int Hash(int row, int column)
      {
          return row * N + column;
      }
      

      如果这是哈希表的哈希函数,对于大多数大小的数组,我们应该得到零冲突。这允许我们在 O(1) 时间内从哈希表中读取/写入数据。

      最酷的部分是将每行/列的索引与哈希表中的标识符接口:

      // row and column are given in the usual way, in the range [0..M] and [0..N]
      // These parameters are really just used as handles to the internal row and
      // column indices
      int MatrixLookup(int row, int column)
      {
          // Get the canonical identifiers of the row and column, and hash them.
          int canonicalRow = RowMap[row];
          int canonicalColumn = ColumnMap[column];
          int hashCode = Hash(canonicalRow, canonicalColumn);
      
          return HashTableLookup(hashCode);
      }
      

      现在,由于矩阵的接口只使用这些句柄,而不是内部标识符,所以行或列的swap 操作对应于RowMapColumnMap 数组中的简单更改:

      // This function simply swaps the values at
      // RowMap[row1] and RowMap[row2]
      void MatrixSwapRow(int row1, int row2)
      {
          int canonicalRow1 = RowMap[row1];
          int canonicalRow2 = RowMap[row2];
      
          RowMap[row1] = canonicalRow2
          RowMap[row2] = canonicalRow1;
      }
      
      // This function simply swaps the values at
      // ColumnMap[row1] and ColumnMap[row2]
      void MatrixSwapColumn(int column1, int column2)
      {
          int canonicalColumn1 = ColumnMap[column1];
          int canonicalColumn2 = ColumnMap[column2];
      
          ColumnMap[row1] = canonicalColumn2
          ColumnMap[row2] = canonicalColumn1;
      }
      

      应该就是这样 - 一个具有 O(1) 访问和变异的矩阵,以及 O(1) 行交换和 O(1) 列交换。当然,即使是 O(1) 的哈希访问也会比基于数组的访问的 O(1) 慢,并且会使用更多的内存,但至少行/列之间是相等的。

      当谈到你如何实现你的矩阵时,我试图尽可能地不可知论,所以我写了一些 C。如果你更喜欢另一种语言,我可以改变它(如果你能理解,那将是最好的),但我认为这是非常自我描述的,虽然我不能确保就 C 而言它是正确的,因为我实际上是一个 C++ 人,现在正试图表现得像一个 C 人(我有没有提到我没有喝咖啡?)。就个人而言,使用完整的 OO 语言编写会使入口设计更加公正,并且还可以使代码更加美观,但就像我说的,这是一个快速改进的实现。

      【讨论】:

      • 对于大多数数组,这可能比转置矩阵和memcpy-交换行要慢几个数量级。
      • @Brian:实际上,就交换而言,它非常快——它只是整数之间的交换操作!就其余代码而言,它也可以非常快,因为我们可以轻松实现一个非常专门的哈希表——该表只是一个二维数组。因此,我的代码的性能版本中的访问归结为 4 个数组访问(一个用于RowMap,一个用于ColumnMap,两个用于哈希表),而不是直接数组的 2 个数组访问。当然,速度要慢两倍,但对于更复杂的操作,使用我的代码可以快得多。
      • 我不认为 C 中的标准二维数组访问是通过两个单独的步骤完成的。 arr[y][x] 可以优化为 arr[(y * sizeof x) + x] 并且 sizeof x 甚至在编译时是已知的。但现在我们谈论的是微小的性能差异。
      • @Brian:非常正确。我太习惯在其他语言中使用交错数组,其中二维数组(静态或动态)实际上是指向数组的指针数组。
      猜你喜欢
      • 2017-04-18
      • 1970-01-01
      • 1970-01-01
      • 2011-02-02
      • 1970-01-01
      • 2013-03-05
      • 2022-06-10
      • 2020-03-30
      • 1970-01-01
      相关资源
      最近更新 更多