如何在之字形排序中找到ith项目?

时间:2015-02-11 15:58:06

标签: algorithm math

上周的一个问题是在n×m矩阵上定义了之字形排序并询问了how to list the elements in that order

我的问题是如何快速找到之字形排序中的第i项?也就是说,没有遍历矩阵(对于大的n和m来说太慢)。

例如,如图所示n = m = 8,(x,y)描述(行,列)

f(0) = (0, 0)
f(1) = (0, 1)
f(2) = (1, 0)
f(3) = (2, 0)
f(4) = (1, 1)
...
f(63) = (7, 7)

具体问题:百万分之一矩阵的之字形排序中的第十亿(1e10)项目是什么?

3 个答案:

答案 0 :(得分:9)

  1. 假设所需元素位于矩阵的上半部分。对角线的长度为1, 2, 3 ..., n

  2. 让我们找到所需的对角线。它满足以下属性: sum(1, 2 ..., k) >= possum(1, 2, ..., k - 1) < pos1, 2, ..., k的总和为k * (k + 1) / 2。所以我们只需找到k这样的最小整数k * (k + 1) / 2 >= pos。我们可以使用二分搜索或明确地解决这个二次不等式。

  3. 当我们知道k时,我们只需找到此对角线的pos - (k - 1) * k / 2元素。我们知道它的起始位置和移动位置(向上或向下,取决于k的奇偶校验),因此我们可以使用简单的公式找到所需的单元格。

  4. 此解决方案具有O(1)O(log n)时间复杂度(取决于我们是使用二进制搜索还是在步骤2中明确解决不等式)。

    如果所需元素位于矩阵的下半部分,我们可以为pos' = n * n - pos + 1解决此问题,然后使用对称性来解决原始问题。

    我在此解决方案中使用了基于1的索引,使用基于0的索引可能需要在某处添加+1或-1,但解决方案的想法是相同的。

    如果矩阵是矩形而不是正方形,我们需要考虑这样一个事实:对角线的长度是这样的:1, 2, 3, ..., m, m, m, .., m, m - 1, ..., 1(如果m <= n)当我们搜索k时,因此,如果k * (k + 1) / 2k <= m,则总和变为k * (k + 1) / 2 + m * (k - m)

答案 1 :(得分:3)

import math, random

def naive(n, m, ord, swap = False):
    dx = 1
    dy = -1
    if swap:
        dx, dy = dy, dx
    cur = [0, 0]
    for i in range(ord):
        cur[0] += dy
        cur[1] += dx
        if cur[0] < 0 or cur[1] < 0 or cur[0] >= n or cur[1] >= m:
            dx, dy = dy, dx

        if cur[0] >= n:
            cur[0] = n - 1
            cur[1] += 2
        if cur[1] >= m:
            cur[1] = m - 1
            cur[0] += 2
        if cur[0] < 0: cur[0] = 0
        if cur[1] < 0: cur[1] = 0


    return cur

def fast(n, m, ord, swap = False):
    if n < m:
        x, y = fast(m, n, ord, not swap)
        return [y, x]

    alt = n * m - ord - 1
    if alt < ord:
        x, y = fast(n, m, alt, swap if (n + m) % 2 == 0 else not swap)
        return [n - x - 1, m - y - 1]

    if ord < (m * (m + 1) / 2):
        diag = int((-1 + math.sqrt(1 + 8 * ord)) / 2)
        parity = (diag + (0 if swap else 1)) % 2
        within = ord - (diag * (diag + 1) / 2)
        if parity: return [diag - within, within]
        else: return [within, diag - within]
    else:
        ord -= (m * (m + 1) / 2)
        diag = int(ord / m)
        within = ord - diag * m
        diag += m
        parity = (diag + (0 if swap else 1)) % 2
        if not parity:
            within = m - within - 1
        return [diag - within, within]

if __name__ == "__main__":
    for i in range(1000):
        n = random.randint(3, 100)
        m = random.randint(3, 100)
        ord = random.randint(0, n * m - 1)
        swap = random.randint(0, 99) < 50
        na = naive(n, m, ord, swap)
        fa = fast(n, m, ord, swap)
        assert na == fa, "(%d, %d, %d, %s) ==> (%s), (%s)" % (n, m, ord, swap, na, fa)

    print fast(1000000, 1000000, 9999999999, False)
    print fast(1000000, 1000000, 10000000000, False)

所以第100个元素(序号为9999999999的元素)和第10个元素(序号为10^10的元素)是:

[20331, 121089]
[20330, 121090]

答案 2 :(得分:0)

分析解决方案

在一般情况下,您的矩阵将分为3个区域:

  • 初始三角形 t1
  • 倾斜部分 mid ,其中对角线具有恒定长度
  • 最后一个三角形 t2

让我们将 p 称为对角线的指数 我们想要定义两个函数 x(p) y(p),它们为您提供 p th的列和行单元格。

初始三角形

让我们看看初始的三角形部分 t1 ,其中每个新对角线比前一个长一个单位。

现在让我们调用 d 保持单元格的对角线的索引,并且 在 [0..p-1]

<0> <0> >

我们有 p = Sp + k 0&lt; = k&lt; = d
Sp = d(d + 1)/ 2

如果我们解决 d ,它会带来 d²+ d-2p = 0 ,这是一个二次方程式,我们只保留正根:
d =( - 1 + sqrt(1 + 8 * p))/ 2

现在我们想要最接近 d 的最高整数值,即 floor(d)

最后,我们有 p = d + k d = floor(( - 1 + sqrt(1 + 8 * p))/ 2) k = p - d( d + 1)/ 2

我们打电话给 如果 d 奇数,则 o(d)等于 1 的函数,否则为0,并且   e(d)等于 1 的函数,如果 d 甚至,否则为0。

我们可以像这样计算 x(p) y(p)

d = floor((-1+sqrt(1+8*p))/2)  
k = p - d(d+1)/2  
o = d % 2  
e = 1 - o
x = e*d + (o-e)*k  
y = o*d + (e-o)*k
  

偶数和奇数函数用于试图挽救一些清晰度,但你可以替换为    e(p) 1 - o(p),对 x y

中间部分

让我们考虑最小的矩阵维度 s ,即 s = min(m,n)
之前的公式一直保持到 x y (以先到者为准)达到值 s

所有 i p 的上限,例如 x(i)&lt; = s和y(i)&lt; = s >在 [0..p]
(即由p索引的单元在初始三角形t1内)由
给出 p t1 = s(s + 1)/ 2

对于 p&gt; = p t1 ,对角线长度保持等于 s ,直到我们到达第二个三角形 t2

mid 内,我们有:
p = s(s + 1)/ 2 + ds + k [0..s []中 k 。 产量:
d = floor((p - s(s + 1)/ 2)/ s)
k = p - ds

然后我们可以使用相同的偶数/奇数技巧来计算 x(p) y(p)

p -= s(s+1)/2
d = floor (p / s)
k = p - d*s
o = (d+s) % 2  
e = 1 - o
x = o*s + (e-o)*k
y = e*s + (o-e)*k
if (n > m)
    x += d+e
    y -= e
else
    y += d+o
    x -= o

最终三角形

使用symetry,我们可以计算 p t2 = m * n - s(s + 1)/ 2

我们现在面临与 t1 几乎相同的问题,除了对角线可能与 t1 反方向(如果 n + m 为奇数)。

使用对称技巧,我们可以像这样计算 x(p) y(p)

        p = n*m -1 - p
        d = floor((-1+sqrt(1+8*p))/2)
        k = p - d*(d+1)/2
        o = (d+m+n) % 2  
        e = 1 - $o;
        x = n-1 - (o*d + (e-o)*k)
        y = m-1 - (e*d + (o-e)*k)  

全部放在一起

这是一个示例c ++实现。

  • 我使用纯粹懒惰的64位整数。大多数可以用32位值替换。
  • 通过预先计算更多系数,可以使计算更有效。
  • 代码的很大一部分可以分解,但我怀疑这是值得的。

由于这只是一个快速而肮脏的概念证明,我没有对其进行优化。

#include <cstdio>    // printf
#include <algorithm> // min
using namespace std;

typedef long long tCoord;

void panic(const char * msg) 
{ 
    printf("PANIC: %s\n", msg);
    exit(-1);
}

struct tPoint {
    tCoord x, y;
    tPoint(tCoord x = 0, tCoord y = 0) : x(x), y(y) {}
    tPoint operator+(const tPoint & p) const { return{ x + p.x, y + p.y }; }
    bool operator!=(const tPoint & p) const { return x != p.x || y != p.y; }
};

class tMatrix {
    tCoord n, m; // dimensions
    tCoord s;    // smallest dimension
    tCoord pt1, pt2; // t1 / mid / t2 limits for p
public:
    tMatrix(tCoord n, tCoord m) : n(n), m(m)
    {
        s = min(n, m);
        pt1 = (s*(s + 1)) / 2;
        pt2 = n*m - pt1;
    }

    tPoint diagonal_cell(tCoord p)
    {
        tCoord x, y;

        if (p < pt1) // inside t1
        {
            tCoord d = (tCoord)floor((-1 + sqrt(1 + 8 * p)) / 2);
            tCoord k = p - (d*(d + 1)) / 2;
            tCoord o = d % 2;
            tCoord e = 1 - o;
            x = o*d + (e - o)*k;
            y = e*d + (o - e)*k;
        }
        else if (p < pt2) // inside mid
        {
            p -= pt1;
            tCoord d = (tCoord)floor(p / s);
            tCoord k = p - d*s;
            tCoord o = (d + s) % 2;
            tCoord e = 1 - o;
            x = o*s + (e - o)*k;
            y = e*s + (o - e)*k;
            if (m > n) // vertical matrix
            {
                x -= o;
                y += d + o;
            }
            else // horizontal matrix
            {
                x += d + e;
                y -= e;
            }
        }
        else // inside t2
        {
            p = n * m - 1 - p;
            tCoord d = (tCoord)floor((-1 + sqrt(1 + 8 * p)) / 2);
            tCoord k = p - (d*(d + 1)) / 2;
            tCoord o = (d + m + n) % 2;
            tCoord e = 1 - o;
            x = n - 1 - (o*d + (e - o)*k);
            y = m - 1 - (e*d + (o - e)*k);
        }

        return{ x, y };
    }

    void check(void)
    {
        tPoint move[4] = { { 1, 0 }, { -1, 1 }, { 1, -1 }, { 0, 1 } };
        tPoint pos;
        tCoord dir = 0;
        for (tCoord p = 0; p != n * m ; p++)
        {
            tPoint dc = diagonal_cell(p);
            if (pos != dc) panic("zot!");

            pos = pos + move[dir];
            if (dir == 0)
            {
                if (pos.y == m - 1) dir = 2;
                else dir = 1;
            }
            else if (dir == 3)
            {
                if (pos.x == n - 1) dir = 1;
                else dir = 2;
            }
            else if (dir == 1)
            {
                if (pos.y == m - 1) dir = 0;
                else if (pos.x == 0) dir = 3;
            }
            else
            {
                if (pos.x == n - 1) dir = 3;
                else if (pos.y == 0) dir = 0;
            }
        }

    }
};

void main(void)
{
    const tPoint dim[] = { { 10, 10 }, { 11, 11 }, { 10, 30 }, { 30, 10 }, { 10, 31 }, { 31, 10 }, { 11, 31 }, { 31, 11 } };
    for (tPoint d : dim)
    {
        printf("Checking a %lldx%lld matrix...", d.x, d.y);
        tMatrix(d.x, d.y).check();
        printf("done\n");
    }
    tCoord p = 10000000000;
    tMatrix matrix(1000000, 1000000);
    tPoint cell = matrix.diagonal_cell(p);
    printf("Coordinates of %lldth cell: (%lld,%lld)\n", p, cell.x, cell.y);
}

根据矩阵的“手动”扫描检查结果 这种“手动”扫描是一个丑陋的黑客攻击,不适用于单行或单列矩阵,但diagonal_cell() 可以在任何矩阵上工作(“对角线”扫描成为在这种情况下是线性的。)

1.000.000x1.000.000矩阵的10.000.000.000th单元格的坐标似乎是一致的,因为单元格所在的对角线 d 大约是sqrt(2 * 1e10),大约。 141421,并且单元坐标的总和约等于 d (121090 + 20330 = 141420)。此外,这也是另外两张海报的报道。

我想说这个混淆代码块实际上很可能会为你的问题产生O(1)解决方案。