杨氏矩阵 Young Tableau

【转自】http://hi.baidu.com/yangchenhao/item/3d0c631200f0b1e75e53b1a7

首先介绍一下这个数据结构的定义,Young Tableau有一个m*n的矩阵,让后有一数组 a[k],其中 k<=m*n,然后把a[k]中的数填入 m*n的矩阵中,填充规则为(如图1-1):

1. 每一行每一列都严格单调递增(有其他的版本是递减,其原理相同)。

2. 如果将a[k]中的数填完后,矩阵中仍有空间,则填入 ∞。

图1-1

则现在讨论,这个数据结构的几种操作,而在这些操作中,我们会发现堆排序的影子,并且这些操作具有很好的时间复杂度。

 

条件:一个已经建好的杨氏矩阵(Young Tableau)

操作:

【1】 在杨氏矩阵中插入元素x ---- Insert(x)

【2】 在杨氏矩阵中删除位于 (x, y)位置的元素---- Delete(x, y)

【3】 返回x是否在矩阵中----Find(x)

其中1-2操作类似于堆的操作,而4操作类似于BST的操作。下面我就用我

自己的理解把这几个操作加以解释。

【1】 插入操作

本操作的时间复杂度为O( n + m ),其操作类似于堆排序的SIFT-UP算法(具体算法见《算法设计技巧与分析》 M.H,Alsuwaiyel著)。它的堆的结构在这里表现为某个元素Y[x, y]下面和右面的两个元素 Y[x, y+1] ,Y[x+1, y]均比Y[x, y]要大。于是其插入算法可以描述为,首先把待插入元素以行为主序置于所有有效数字(除了无穷以外的数)最后面,如将x=2,放入 图1-1的d5的位置,然后执行类似于SIFT-UP的操作,将x与它左边(记为x-l)及上面(记为x-u)的数进行比较,根据比较结果有三种可能的操作:

1:x-u > x且x-u >= x-l 则将x与 x-u进行交换

2:x-l > x且x-l > x-u 则将x与 x-l进行交换

3: x >= x-l且 x > x-u 则x不动,此时已经插入成功

(可以有其他合理的约定)

对例子插入2进行操作如图1-2箭头的操作方向。

 

图1-2

  由上述的操作知其时间复杂度最大为行列之和,即O(m+n)。

【2】 删除操作

操作类似于插入操作,其时间复杂度也为O(m+n)。其操作类似于堆排序的SIFT-DOWN算法。删除算法可以描述为,首先将删除的位置(x, y)的数字删除,然后调整,把杨氏矩阵中的最大值k(可以行为主序进行搜索到最后)填入到被删除位置,然后让此数与其下面的数(k-d)和右面的数进行(k-r)比较,此时比较结果为两种,因为此时元素一定会下移:

 1:k-d > k-r将k-r和 k进行交换

 2:k-d < k-r将k-d和 k进行交换

举例 删除图1-1的a3位置的元素5如图1-3

 

 

图1-3

 由操作知,删除算法时间复杂同样最多为行列之和,即O(m + n)。

【3】查找算法

   查找算法类似于BST二叉排序树,不过需要在其思想上稍微进行修改,才能满足杨氏矩阵的查找操作,同样利用杨氏矩阵的性质,不过此时应该换一个角度思考问题,因为与BST二叉排序树类比,所以应该从左下角开始看起(如图1-1d1位置),该元素的上面的元素比本身小和右面的元素比本身大,这样就与BST二叉排序树具有相同的性质。所以在查找时从左下角不断的比较,从而最终确定所查找元素位置。

   如查找19,比较顺序如图1-4

 

图1-4

 此算法的时间复杂度同前两个算法,复杂度也是O(m + n)。

总结:

   经 过这次对杨氏矩阵的研究,感觉书本上学到的数据结构只是一些最基本的东西,其实在生活中有很多复杂,并且有趣的数据结构模型,但是这些数据结构模型最终将 可以用已有的简单数据结构来经过变换,以及组合来构成,同时相应的算法也就有所扩展。所以我认为在学习的过程中应该注重对一些问题的深入研究。

附c++源码

注:本源代码仅实现了插入功能,并且在vc++6.0编译通过,程序仅对简单的数据测试通过,可能存在bug,在此写出简单的源码重在说明杨氏矩阵的算法思想。

View Code
 1 #include <iostream>
 2 using namespace std;
 3  
 4 #define MAX1 5
 5 #define MAX2 6
 6 #define INF 99999
 7  
 8 void Insert( int x, int *young );
 9 void Delete( int x, int y, int *young );
10 void Find( int x, int *young );
11  
12 void main()
13 {
14    int young[MAX1][MAX2]={1,3,5,7,8,11,4,6,9,14,15,19,10,21,23,33,56,57,34,37,45,55,INF,INF,INF,INF,INF,INF,INF,INF};
15    int choice=0;
16    int insert;
17    int k,s;//用于计数
18    cout<<"young tableau的插入功能"<<endl;
19    cout<<"young tableau:"<<endl;
20    for(int i=0; i<MAX1; i++)
21       for(int j=0; j<MAX2; j++)
22       {
23           cout<<young[i][j]<<' ';
24           if( j==MAX2-1 )
25              cout<<endl;
26       }
27    cout<<endl<<"请选择:1-3"<<endl;
28    cout<<"1.插入"<<endl;
29    cout<<"2.删除"<<endl;
30    cout<<"3.查找"<<endl;
31    while( choice<1 || choice>3 )
32       cin>>choice;
33    switch(choice)
34    {
35       case 1:
36           cout<<"请输入要插入的数字:";
37           cin>>insert;
38           Insert(insert, *young); 
39           cout<<"young tableau:"<<endl;
40           for(k=0; k<MAX1; k++)
41              for(s=0; s<MAX2; s++)
42                  {
43                     cout<<young[k][s]<<' ';
44                     if( s==MAX2-1 )
45                     cout<<endl;
46                  }
47           break;
48       case 2:
49           break;
50       case 3:
51           break;
52    }
53 }
54 void Insert(int insert, int *young)
55 {
56    int x=4;
57    int y=5;//为young表中第一个无穷的位置,并且用来定位当前待比较交换的位置
58    *(young+6*(x-1)+(y-1))=insert;
59    for(int i=x+y; ; i--)
60    {
61       int insert_l= *(young+6*(x-1)+(y-2));
62       int insert_u= *(young+6*(x-2)+(y-1));
63       if(x==1)
64           insert_u=0;
65       if(y==1)
66           insert_l=0;
67       if(insert<insert_l || insert<insert_u )
68       {
69           if( insert_l > insert_u )
70           {
71              int temp=insert_l;
72              *(young+6*(x-1)+(y-2)) = *(young+6*(x-1)+(y-1));
73              *(young+6*(x-1)+(y-1)) =temp;
74              y--;
75           }
76           else
77           {
78              int temp=insert_u;
79              *(young+6*(x-2)+(y-1)) = *(young+6*(x-1)+(y-1));
80              *(young+6*(x-1)+(y-1)) =temp;
81              x--;
82           }
83       }
84       else break;
85    }
86 }

==============================================================================================
==============================================================================================

【转自】http://blog.csdn.net/sunmenggmail/article/details/7694771

如果一个矩阵每一行每一列都严格单调递增,我们称该矩阵为杨氏矩阵(Young Tableau)。对于杨氏矩阵(a[m][ n]),通常会涉及两个问题:(1) 怎样在杨氏矩阵中查找某个元素X?(2) 怎样在杨氏矩阵找第k大的数?

 

2. 解决方案

 

杨氏矩阵是一种非常巧妙的数据结构,它既可以用来当堆,又可以用来当做平衡树。

(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),代码如下:

View Code
 1 bool find_in_YoungTableau(int** a, int m, int n, int x) {
 2   assert(a != NULL && m > 0 && n > 0);
 3   int row, col;
 4   row = 0;
 5   col = n-1;
 6   while(row <= m-1 && col >= 0) {
 7     if(a[row][col] == x)
 8       return true;
 9     else if(a[row][col] > x)
10       col--;
11     else
12       row++;
13   }
14   return false;
15 } 

(2) 问题2求解

【方案一】

<小顶堆法>

首先将a[0][0]加入小顶堆,然后每次从小顶堆中取出最小元素,并加入比该元素大且与之相邻的两个元素(对于a[0][0],则需要加入a[0][1]和a[1][0]),直到取出第k个元素,需要注意的是,需要采用额外空间记录某个元素是否已经加入到小顶堆以防止重复加入。

【方案二】

<二分枚举+类堆查找>

首先,二分枚举找到一个数x,它比杨氏矩阵中k个数大;然后,利用类堆查找法找到刚好小于x的元素。该算法的时间复杂度为O((m+n)lg(mn)),但不需要额外存储空间。代码如下:

View Code
 1 int find_kth_in_YoungTableau(int** a, int m, int n, int k) {
 2   int low = a[0][0];
 3   int high = a[m-1][n-1];
 4   int order = 0;
 5   int mid = 0;
 6   do {
 7     mid = (low + high) >> 1;
 8     order = get_order(a, m, n, mid);
 9     if(order == k)
10       break;
11     else if(order > k)
12       high = mid - 1;
13     else
14       low = mid + 1;
15   } while(1); //二分枚举整,找出正好大于k的一个整数 mid 
16  
17   int row = 0;
18   int col = n - 1;
19   int ret = mid;
20   while(row <= m-1 && col >= 0) { //找出比mid小的最大数
21     if(a[row][col] < mid) {
22       ret = (a[row][col] > ret) ? a[row][col] : ret;
23       row++;
24     } else {
25       col--;
26     }
27   }
28   return ret;
29 }
30  
31 //整数k在矩阵中的排名
32 int get_order(int** a, int m, int n, int k) {
33   int row, col, order;
34   row = 0;
35   col = n-1;
36   order = 0;
37   while(row <= m-1 && col >= 0) {
38     if(a[row][col] < k) {
39       order += col + 1;
40       row++;
41     } else {
42       col--;
43     }
44   }
45   return order;
46 } 

 

posted @ 2013-05-13 11:07  nextone  阅读(563)  评论(0编辑  收藏  举报