杨氏矩阵问题
问题描述
如果一个矩阵每一行每一列都严格单调递增,我们称该矩阵为杨氏矩阵(Young Tableau)。对于杨氏矩阵(a[m][ n]),通常会涉及两个问题:(1) 怎样在杨氏矩阵中查找某个元素X?(2) 怎样在杨氏矩阵找第k大的数?解决方案
杨氏矩阵是一种非常巧妙的数据结构,它既可以用来当堆,又可以用来当做平衡树。(1) 问题1求解
【方案一】
<二分查找>
对于杨氏矩阵,由于每行每列均是有序的,则可以于矩阵采用二分查找。具体方法是:
对于当前子矩阵a[i][j]~a[s][t],中间元素为a[(i+s)/2][(j+t)/2],如果a[(i+s)/2][(j+t) /2]==x,则找出该元素;如果a[(i+s)/2][(j+t)/2] > x,则在子矩阵a[i][j]~a[(i+s)/2][(j+t)/2]中查找;如果a[(i+s)/2][(j+t)/2] < x,则在三个子矩阵:a[i][(j+t)/2]~ a[(i+s)/2][t],a[(i+s)/2][(j+t)/2]~ a[s][t]和a[(i+s)/2][j]~ a[s][ (j+t)/2]中查找。该算法的递归式为f(mn)=3f(mn/4)+O(1),根据主定理知,时间复杂度为:O((mn)^(log4(3)))。
【方案二】
<类堆查找法>
杨氏矩阵具有明显的堆特征:从矩阵的右上角出发(从左下角出发思路类似),对于元素a[i][j],如果a[i][j]==x,则找到元素x,直接返回; 如果a[i][j] > x,则向下移动,即继续比较a[i+1][j]与x;如果a[i][j] < x,则向左移动,即继续比较a[i][j-1]与x。该算法的时间复杂度是O(m+n),代码如下:
bool find_in_YoungTableau(int** a, int m, int n, int x) { assert(a != NULL && m > 0 && n > 0); int row, col; row = 0; col = n-1; while(row <= m-1 && col >= 0) { if(a[row][col] == x) return true; else if(a[row][col] > x) col--; else row++; } return false; }
【方案一】
<小顶堆法>
首先将a[0][0]加入小顶堆,然后每次从小顶堆中取出最小元素,并加入比该元素大且与之相邻的两个元素(对于a[0][0],则需要加入a[0] [1]和a[1][0]),直到取出第k个元素,需要注意的是,需要采用额外空间记录某个元素是否已经加入到小顶堆以防止重复加入。
【方案二】
<二分枚举+类堆查找>
首先,二分枚举找到一个数x,它比杨氏矩阵中k个数大;然后,利用类堆查找法找到刚好小于x的元素。该算法的时间复杂度为O((m+n)lg(mn)),但不需要额外存储空间。代码如下:
int find_kth_in_YoungTableau(int** a, int m, int n, int k) { int low = a[0][0]; int high = a[m-1][n-1]; int order = 0; int mid = 0; do { mid = (low + high) >> 1; order = get_order(a, m, n, mid); if(order == k) break; else if(order > k) high = mid - 1; else low = mid + 1; } while(1); //二分枚举整,找出正好大于k的一个整数 mid int row = 0; int col = n - 1; int ret = mid; while(row <= m-1 && col >= 0) { //找出比mid小的最大数 if(a[row][col] < mid) { ret = (a[row][col] > ret) ? a[row][col] : ret; row++; } else { col--; } } return ret; } //整数k在矩阵中的排名 int get_order(int** a, int m, int n, int k) { int row, col, order; row = 0; col = n-1; order = 0; while(row <= m-1 && col >= 0) { if(a[row][col] < k) { order += col + 1; row++; } else { col--; } } return order; }