Algorithm 排序矩阵的选择算法

Algorithm 排序矩阵的选择算法,algorithm,Algorithm,这是一个谷歌面试问题: 给定一个N*N矩阵。 所有行都已排序,所有列都已排序。 求矩阵的第k个最大元素 在n^2中进行排序很简单,我们可以使用堆或合并排序(nlgn)对其进行排序,然后得到它,但是有比(nlgn)更好的方法吗 数组的示例:: 1 5 7 12 3 6 8 14 4 9 10 15 11 17 19 20 1将矩阵顺时针旋转45度。您将得到一个菱形的数据集。高度为2N-1,从顶部开始的每一行中的元素数量为:1,2,3,4,5,4,3,2,1

这是一个谷歌面试问题:

给定一个N*N矩阵。 所有行都已排序,所有列都已排序。 求矩阵的第k个最大元素

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

数组的示例::

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

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

您将发现,一行中的每个数字总是大于上面的任何数字

对于第k行(从1开始计算),k=N时有2N-k个元素 k属于{1..2N-1}

通过计算从第1行到k-1以及从第1行到k行的元素累计数,您将找到目标所在的行(总和(1到k-1)) 现在,您已经找到了一行最坏情况为N的元素。您可以对它们进行排序,然后找到正确的元素。这个值为O(N ln N)


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

,示例中给出了矩阵: 如果要搜索第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]+…>=k,因此选择适当层的算法是Sum[2(N-i)-1]>=k,对于i=0,N-1,其中i是层的编号。找到i,层编号后,您将有2(N-i)-1该数组中必须合并然后搜索的元素。搜索该层的复杂性为O(log[2(N-i)-1]=O(log(N-i))

算术级数导致

0>=i^2-2*N*i+k

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

是的,由于Frederickson和Johnson,有一个O(k)算法


Greg N.Frederickson和Donald B.Johnson.《广义选择和排序:排序矩阵》《暹罗计算机杂志》13,第14-30页。

基于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大元素即可从(0,0)开始进行呼吸优先搜索。(0,0)的2个子元素(0,1)和(1,0)添加到第二个元素的潜在候选元素列表中。循环选择潜在候选元素列表中最小的元素作为下一个元素,将其子元素添加到潜在候选元素列表中。找到第k个元素时停止

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

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


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

如果您注意到:

  • 生成位于数组[i][j]和数组[k][l]之间的随机数,使得数组[i][j] 使用[1]作为子例程,您可以使用类似于随机选择的过程来生成整个数组中的第k个最小数。

    我下面的代码是一个O(k)算法。它不适用于特定的边缘情况(可能每个方向都有一个:x和y).我列出了edge案例以便有人可以修复它。我不会修复它,因为我该睡觉了

    算法概述:您只需跟踪两个可能最小的候选对象,一个在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];
    }
    

    下面是我的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;
    }
    
    #包括
    #包括
    #包括
    使用名称空间std;
    结构条目{
    int值;
    int x;
    int-y;
    布尔运算符<(常数输入和其他){
    返回此->值>其他.value;
    }
    };
    bool getKthNumber(int*矩阵、int行、int列、int k、int*结果){
    
    如果(矩阵==NULL | |行可能重复,请访问O(n lg n)?这是什么意思?元素的数量为n^2,您希望如何在n log n中对它们进行排序?您可以使用经典的选择算法,在线性时间内找到第k个最小值,而不考虑矩阵的顺序。@Nohsib您的意思是说O(n^2 log n)使用排序?在本例中,菱形的第三行的数字(4)比上面的数字(5)小。这是一个正确且最优的解决方案。我想将其扩展为一个单独的答案,以帮助陷入付费墙的人,但……算法相当混乱。它是渐进最优的,但我认为不是特别实用。它肯定够混乱的,不适合面试的限制。@missingno:不是那么最优。我有一个更有效的方法这个问题的NT平均运行时间解决方案在,它不是一个凌乱的算法。无法访问pdf???-1这个答案是完全无用的,这篇文章不再可用。答案er自从回答这个问题以来一直没有在线。不起作用。看看第三个di中的4
    #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;
    }