You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

寻找无需O(n)额外内存的置换循环分解算法(用于原地矩阵转置)

优化矩阵转置置换的循环分解(无需额外标记数组)

这确实是原地矩阵转置里的一个关键问题——既要找到置换循环的起始点,又不想用O(hw)的标记数组占用内存,完全理解你的需求!

首先,我们先明确你提到的置换本质:s(i) = (i//w) + (i%w)*h 其实是把原h×w矩阵的行优先索引,映射到转置后w×h矩阵的行优先索引,这个置换的循环结构决定了原地转置时需要交换的元素组。

方法一:基于循环最小元素的无标记遍历

核心思路是:每个循环有且仅有一个最小元素,我们只需要遍历所有索引,当且仅当前索引是它所在循环的最小元素时,将其加入cycles数组。这样就不需要额外的visited数组来标记已处理元素。

具体代码如下:

std::vector<size_t> cycles;
const size_t total = h * w;

for (size_t i = 0; i < total; ++i) {
    size_t current = i;
    bool is_smallest = true;
    
    // 沿着置换链遍历,检查是否存在比i更小的元素
    do {
        if (current < i) {
            is_smallest = false;
            break; // 发现更小的元素,说明这个循环已经被处理过了
        }
        current = (current / w) + (current % w) * h;
    } while (current != i);
    
    if (is_smallest) {
        cycles.push_back(i);
    }
}

原理说明

  • 对于每个索引i,我们沿着置换的链条走一遍,只要中途遇到比i小的元素,就说明这个循环的起始点已经被更小的那个元素处理过了,直接跳过。
  • 只有当遍历完整个循环都没找到比i小的元素时,i就是这个循环的最小元素,也就是我们需要的起始点。

优缺点

  • 优点:完全不需要额外标记数组,实现简单直观。
  • 缺点:最坏情况下时间复杂度是O(n²)(比如当整个置换是一个大循环时,每个元素都要遍历整个循环才能判断)。这种情况在h和w互质且几乎没有自循环元素时会出现。

方法二:利用数论性质的O(n)时间优化

如果想要在O(n)时间内完成,我们可以利用置换的数学性质来直接定位循环起始点,避开遍历整个循环的开销。

首先,先找出所有自循环元素(即满足s(i)=i的元素):
自循环的条件是(i//w) + (i%w)*h = i。设i对应的二维坐标为(r, c)(r = i//w,c = i%w),代入得:
c*h + r = r*w + c → c*(h-1) = r*(w-1)

设d = gcd(h-1, w-1),h-1 = d*a,w-1 = d*b(此时gcd(a,b)=1),方程简化为c*a = r*b。由于a和b互质,b必须整除c,a必须整除r。设c = b*k,r = a*k,其中k的取值要满足0 ≤ r < h且0 ≤ c < w,即k < min(h/a, w/b)。这些k对应的i = r*w + c = k*(a*w + b)就是所有自循环元素,直接加入cycles。

接下来处理非自循环的循环:
对于非自循环元素,我们可以利用置换的循环周期性质。当h和w的最大公约数为g = gcd(h, w)时,整个置换可以分解为g组独立的循环,每组循环的元素满足i ≡ k mod g(k从0到g-1)。对于每组k,我们只需要找到该组中最小的非自循环元素作为起始点加入cycles即可。

具体代码实现需要结合gcd计算,这里给出核心逻辑:

#include <algorithm> // for gcd (C++17+)

std::vector<size_t> cycles;
const size_t total = h * w;
const size_t g = std::gcd(h, w);
const size_t d = std::gcd(h-1, w-1);
const size_t a = (h-1)/d;
const size_t b = (w-1)/d;

// 先添加所有自循环元素
for (size_t k = 0;; ++k) {
    size_t r = a * k;
    size_t c = b * k;
    if (r >= h || c >= w) break;
    size_t i = r * w + c;
    cycles.push_back(i);
}

// 添加非自循环的循环起始点
std::vector<bool> is_self(total, false);
for (size_t i : cycles) is_self[i] = true; // 这里用了小空间,远小于O(total)

for (size_t k = 0; k < g; ++k) {
    // 找该组中最小的非自循环元素
    for (size_t i = k; i < total; i += g) {
        if (!is_self[i]) {
            cycles.push_back(i);
            break;
        }
    }
}

原理说明

  • 自循环元素通过解方程直接生成,无需遍历。
  • 非自循环元素按mod g分组,每组对应一个独立循环,只需找到每组第一个非自循环元素作为起始点即可。
  • 这里用了一个小的is_self数组来标记自循环元素,但其大小是自循环元素的数量,远小于h*w,内存开销可以忽略。

优缺点

  • 优点:时间复杂度O(n),内存开销极小。
  • 缺点:需要理解置换的数论性质,实现相对复杂。

两种方法都能生成你需要的cycles数组,具体选择取决于你的场景:如果矩阵规模不大,方法一足够简单好用;如果处理大规模矩阵,方法二更高效。

内容的提问来源于stack exchange,提问作者thebensky

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 05:47:42