排序矩阵的选择算法

时间:2011-02-15 07:18:47

标签: algorithm

这是一个谷歌面试问题:

给出N * N矩阵。 对所有行进行排序,并对所有列进行排序。 找到矩阵的第K个最大元素。

在n ^ 2中执行它很简单,我们可以使用堆或合并排序(n lg n)对其进行排序,然后得到它,但是有更好的方法,比(n lg n)更好吗?

array ::

的示例
 1   5   7  12
 3   6   8  14
 4   9  10  15
11  17  19  20
与其他行和列类似,

1 <5 <7&lt; 12&lt; 3&lt; 3&lt; 3&lt; 11&lt; 11&lt; 11&lt; 11&lt; 11&lt;现在说我们需要找到第10个最小的元素,在这里它是11..hope这增加了一些问题的细节......

9 个答案:

答案 0 :(得分:3)

是的,由于弗雷德里克森和约翰逊,有一种O(K)算法。

Greg N. Frederickson和Donald B. Johnson。 广义选择和排名:排序矩阵。 SIAM J. Comput。 13,pp.14-30。 http://epubs.siam.org/sicomp/resource/1/smjcat/v13/i1/p14_s1?isAuthorized=no

答案 1 :(得分:1)

使用示例中给出的矩阵: 如果你想搜索第7个元素,你知道第7个元素在元素M [4] [1..4],M [1..4] [4]中。你得到两个已经排序的数组,12,14,15,20和11,17,19可以合并。然后应用二进制搜索,即O(log N)。

概括:对于此矩阵中第k个最大元素,你必须选择合适的层:[2N-1] + [2(N-1)-1] + ...>&gt; = k所以算法选择要查找的适当层是Sum [2(Ni)-1]> = k,对于i = 0,N-1,其中i是层的编号。找到层数后,您将在该数组中有2个(N-i)-1个元素,这些元素必须合并然后进行搜索。搜索该层的复杂性为O(log [2(N-i)-1] = O(log(N-i))...

算术级数导致

  

0&GT; = I ^ 2-2 * N * I + K

i1,2 = N + -sqrt(N ^ 2-k),其中k是我们搜索的元素......

答案 2 :(得分:0)

由于所有内容都已经排序,您可以进行对角线搜索。 (虽然,坦率地说,我不知道“所有行都被排序并且所有列都已排序”意味着什么。如果这是真实的,那么只需在矩阵的对角枚举中转到第k个元素。)< / p>

答案 3 :(得分:0)

顺时针旋转矩阵45度。您将获得菱形数据集。高度为2N-1,从顶部开始每行的元素数量如下:1,2,3,4,5,4,3,2,1为N = 5

您会发现连续的每个数字总是大于上面的任何数字。

对于第k行(从1开始计算),你将有k个元素用于k&lt;对于k,N和2N-k = N k属于{1..2N-1}

通过计算从第1行到第k-1和从第1到第k行的元素的累积数量,您将找到目标所在的行(sum(1到k-1))

现在您已找到一行元素,其中最坏情况为N总计。您可以对它们进行排序,然后找到正确的一个。这取了O(N ln N)

由于N = sqrt(n),该算法的总成本为O(sqrt(n)ln(sqrt(n)))

答案 4 :(得分:0)

基于N,您可以找到元素所在的对角线。例如,在矩阵中,

 1   5   7  12
 3   6   8  14
 4   9  10  15
11  17  19  20

您可以通过确定先前对角线中的元素总数来推断对角线

/diagonal#/elements/# of elements/cumulative # of elements/
/d1/ 1         / 1 / 1 /
/d2/ 3 5       / 2 / 1+2 = 3 /
/d3/ 4 6 7     / 3 / 1+2+3 = 6 /
/d4/ 11 9 8 12 / 4 / 1+2+3+4 = 10 /
/d5/ 17 10 14  / 3 /
/d6/ 19 15     / 2 /
/d7/ 20        / 1 /

我们需要找到对角线的原因是因为上面的对角线总是会有比任何当前对角元素更小的元素,并且下面的对角线总是会有比任何当前对角线元素更大的元素。

因此,您可以确定对角线d4具有所需的元素(因为它包含第7大到第10大)。由于直到前一个对角线有6个元素,你只需找到对角线d4中的第4个最大元素。

答案 5 :(得分:0)

从(0,0)开始先进行一次呼吸搜索。 (0,0)的2个孩子(0,1)&amp; (1,0)被添加到第二元素的潜在候选列表中。循环选择潜在候选列表中的最小元素作为下一个元素,将其子项添加到潜在候选列表中。找到第k个元素时停止。

使潜在候选人列表成为最小堆。堆永远不会大于n + m。

如果k大于n * m / 2,你也可以从最后一个元素(n,m)开始反向。

最坏情况:这将是n * m / 2 lg(n + m),而不是n * m lg(n * m)的排序。

答案 6 :(得分:0)

如果你注意到,你可以在预期的时间O(n log n)中找到k th 最小元素:

  1. 生成位于Array [i] [j]和Array [k] [l]之间的随机数,使得Array [i] [j]&lt;数组[k] [l]需要O(n)时间(预期)和
  2. 使用[1]作为子程序,您可以使用类似于 RANDOMIZED-SELECT 的过程在整个数组中生成k th 最小数字。

答案 7 :(得分:0)

下面的代码是O(k)算法。它不适用于某个边缘情况(每个方向可能有一个:x和y)。我列出了边缘案例,所以有人可以解决它。我不打算解决它,因为这是我的睡觉时间。

算法摘要:您只需要跟踪两个可能最小的候选#,一个在x方向上进行,一个在y方向上进行。想一想,它可能对你有意义。

enum Direction {
  X,
  Y
};

struct Index
{
  Index(int unsigned x, int unsigned y)
    : x(x),
      y(y)
  {}

  void operator = (Index const & rhs)
  {
    x = rhs.x;
    y = rhs.y;
  }

  int unsigned x;
  int unsigned y;
};

int unsigned solve(int unsigned i_k, int unsigned ** i_data, int unsigned i_n)
{
  if (1 == i_k) {
    return i_data[0][0];
  }

  Direction dir = X;
  Index smaller(0,0);
  Index larger(0,0);

  if (i_data[1][0] < i_data[0][1]) {
    dir = X;
    smaller = Index(1,0);
    larger = Index(0,1); }
  else {
    dir = Y;
    smaller = Index(0,1);
    larger = Index(1,0);
  }

  for (int unsigned i = 0; i < (i_k - 2); ++i) {
    int unsigned const x = smaller.x;
    int unsigned const y = smaller.y;
    if (X == dir) {
      if ((x + 1) == i_n) {
        // End of row
        smaller = larger;
        larger.x += 1;
        dir = Y; }
      else if (i_data[x + 1][y] < i_data[larger.x][larger.y]) {
        smaller.x += 1; }
      else {
        smaller = larger;
        larger = Index(x + 1, y);
        dir = Y;
      } }
    else {
      if ((y + 1) == i_n) {
        // End of col
        smaller = larger;
        larger.y += 1;
        dir = X; }
      else if (i_data[x][y + 1] < i_data[larger.x][larger.y]) {
        smaller.y += 1; }
      else {
        smaller = larger;
        larger = Index(x, y + 1);
        dir = X;
      }
    }
  }
  return i_data[smaller.x][smaller.y];
}

不适用于以下边缘情况(我们点击行的末尾)。我要睡觉了,随便解决这个问题:

  size = 4;
  data = createMatrix(size);
  data[0][0] = 1; data[1][0] = 6; data[2][0] = 10; data[3][0] = 11;
  data[0][1] = 3; data[1][1] = 7; data[2][1] = 12; data[3][1] = 14;
  data[0][2] = 4; data[1][2] = 8; data[2][2] = 13; data[3][2] = 15;
  data[0][3] = 5; data[1][3] = 9; data[2][3] = 19; data[3][3] = 20;
  answer = solve(14, data, size);
  assertAnswer(answer, 15, ++testNum);
  deleteMatrix(data, size);

答案 8 :(得分:0)

以下是我的C ++解决方案,它基于最小堆。当矩阵中的单元格位于最小堆的顶部时,右侧和/或下侧的数字将插入堆中。

#include <vector>
#include <algorithm>
#include <functional>

using namespace std;

struct Entry {
    int value;
    int x;
    int y;

    bool operator < (const Entry& other) {
        return this->value > other.value;
    }
};

bool getKthNumber(int* matrix, int row, int col, int k, int* result){
    if(matrix == NULL || row <= 0 || col <= 0 || result == NULL)
        return false;
    if(k <= 0 || k > row * col)
        return false;

    vector<Entry> minHeap;
    Entry first = {matrix[0], 0, 0};
    minHeap.push_back(first);
    make_heap(minHeap.begin(), minHeap.end());

    for(int i = 0; i < k; ++i){
        first = minHeap[0];
        int x = first.x;
        int y = first.y;
        if(first.y == 0 && first.x < row - 1){
            Entry next = {matrix[(x + 1) * col], x + 1, y};
            minHeap.push_back(next);
            push_heap(minHeap.begin(), minHeap.end());
        }
        if(first.y < col - 1){
            Entry next = {matrix[x * col + y + 1], x, y + 1};
            minHeap.push_back(next);
            push_heap(minHeap.begin(), minHeap.end());
        }

        pop_heap(minHeap.begin(), minHeap.end());
        minHeap.pop_back();
    }

    *result = first.value;
    return true;
}