剑指offer习题3——二维数组的排序和查找

来源:互联网 发布:网络北京时间 编辑:程序博客网 时间:2024/06/06 14:08

题目:在一个二维数组中,每一行都按照从左到右递增顺序排序,每一列都赞找从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

                                         eg:
                                             
           1  2  8   9
                                              2  4  9   12
                                              4  7  10 13
                                              6  8  11 15


       首先我们需要对二维数组进行排序,其实二维数组的排序和一维数组的排序没有什么本质上的区别,一维数组怎么排,二维数组还是怎么排,这里我们主要研究的不是排序,所以我就用最简单的冒泡排序,二维数组无非就是多排几趟,我们分为行排序和列排序,因为它们在下标处理上有点细微的差别,但是原理不变,有多少行就调用多少次行排序,列的排序也是如此。

       二维数组排序这部分代码如下:

const int row=4;   
const int line=4;

//一行排序
void bubblesort_line(int (*arr)[row], int eachline, int len)
{
 for(int i=0;i<len-1;++i)
    for(int j=0;j<len-1-i;j++)
    {
     if(arr[eachline][j]>arr[eachline][j+1])
     {
      int tmp=arr[eachline][j];
      arr[eachline][j]=arr[eachline][j+1];
      arr[eachline][j+1]=tmp;
     }
    }
}

//一列排序
void bubblesort_row(int (*arr)[row], int eachrow,int len)
{
 for(int i=0;i<len;++i)
  for(int j=0;j<len-1-i;j++)
    {
     if(arr[j][eachrow]>arr[j+1][eachrow])
     {
      int tmp=arr[j][eachrow];
      arr[j][eachrow]=arr[j+1][eachrow];
      arr[j+1][eachrow]=tmp;
     }
    }
}

//二维数组排序
void sort(int (*arr)[row],int line,int row)
{
 for(int i=0;i<line;++i)
 {
        bubblesort_line(arr,i,row);
 }
 for(int i=0;i<row;++i)
 {
  bubblesort_row(arr,i,line);
 }
}

          我再把一个排序后的二维数组展示一下,方便大家对照这个观察规律:

                                     列数  0 1   2   3
                                                        1  2  8   9
                                              2  4  9   12
                                              4  7  10 13
                                              6  8  11 15

查找功能解决分析:

        首先主题思路是需要限制查找的范围,我们仔细观察会发现,右上角的数是一列中最小的,但是确实一行中最大的,那如果我们查找的值,比这一列中的最小的数都小,那它肯定就比这一列的其他数要小,所以可以直接将列的边界减小,缩小一列的范围。同样的我们观察行,我们会发现我们每一行的列边界是一行中的最大值,例如,我们要查找7,它小于9,8,所以我们列边界在1列了。我们第一行的最大值是不是a[0][1]; 所以我们如果我们要查找值比一行中数的最大值还大,那行边界可以增加了,因为这一行前面的数字肯定比这个小。范围缩小后我们再做一一比较。

         如果还是不太理解的话,我就直接上图吧,有图有真相!

                                                                       我们假设我们要找7

                                                                          linebound=0

                                                                          rowbound=3

                                                                          a[lowbound][rowbound]=9 >7

                                                                          --row

                                                                   

                                                                          linebound=0

                                                                          rowbound=2

                                                                          a[lowbound][rowbound]=8 >7

                                                                           --row

                                                                                    

 linebound=0

  rowbound=1

                             a[lowbound][rowbound]=2 >7

  ++linebound

 linebound=1

  rowbound=1

                              a[lowbound][rowbound]=4 >7

 ++linebound

        我们就找到7了。


        理清楚思路了,我们可以把解题思路转化为代码思想。我们每次比较是不是都分为3种情况:

        1、a[linebound][rowbound]==val

        2、a[lineboudn][rowbound]<val

        3、a[linebound][rowbound]>val

        我们上代码,上完我们再仔细分析一下代码的实现细节和易错易混淆的点:

                      

bool search(int (*arr)[row],int* l, int *r,int val)
{
         sort(arr,*l,*r);
         int linebound=0;
         int rowbound=*r-1;
        while(linebound<*l&&rowbound>=0)
       {
              if(arr[linebound][rowbound]==val)
             {
                       //printf("linebound:%d rowbound:%d",linebound,rowbound);   这些是辅助我们分析得输出
                       //printf("a[linebound][rowbound]%d ==%d\n",arr[linebound][rowbound],val);
                      
                       *r=rowbound;
                       *l=linebound;
                        return true;
              }
              else if(arr[linebound][rowbound]<val)
             {
                       //printf("linebound:%d rowbound:%d",linebound,rowbound);  带注释的printf信息都是辅助测试分输出
                       //printf("a[linebound][rowbound]%d <%d\n",arr[linebound][rowbound],val);
                        
                         ++linebound;
 
                      //printf("linebound:%d rowbound:%d\n",linebound,rowbound);
   
            }
            else
            {
                    printf("linebound:%d rowbound:%d",linebound,rowbound);
                    printf("a[linebound][rowbound]%d >%d\n",arr[linebound][rowbound],val);
                    --rowbound;
            }
      }
  
      if(rowbound<0||linebound>=*l)
     {
                 return false;
     }
     else
    {
                return true;
    }
}

            以上就是我们search 函数的具体实现,实现其实很简单,就是我们刚刚的分析,首先定义的的两个变量linebound 和 rowbound 表示的是如果这个元素存在,那这两个吧变量最后的值两两确定一个点就是定位到那个与
val值一样的下标的位置。然后主要实现代码就是while条件和while函数体里的几个条件分支走向。
            1、while括号里的条件主要是判断我们的linebound和rowbound最后缩小范围的过程中是否存在越界的问题,如果其中任何一个越界了都没找到,那肯定就说明我们的的二维数组中是不存在这个值的。所以这里的越界条件判断就可以作为我们是否找到元素的一个参考。
            2、while中我们用if else if 和else  构成了三选一的选择分支,这三个进入条件是互斥的,只能进入一个,这个我们来好好分析一下。我们一进入while就开始判断val是否是和我们linebound和rowbound定位的值相等,相等就相当于找到了,找到后会return true,表示找到了,并通过我们传过来的指针将它的下标带给主调函数。不相等的话,我们的条件分支就会向下判断。其实这个时候其实可以看成是两个大的情况,相等或者是不等,而我们的相等部分是不成立的,现在要来判断不等的情况了,我们的val!=a[linebound][rowbound]又分为了一些情况,一个是a[linebound][rowbound]<val,如果是这种情况,我们上面也分析过了,val大于了这行中最大的数,可以让linebound的边界+1了,因为这一行的其他数肯定都比他小,不可能相等。如果是我们a[linebound][rowbound]>val,那我们的列可以-1了,因为我们的val要小于我们一列中最小的数,那这一列其他的数,肯定都比他大,肯定也不会相等。但是我们这里为什么要实现成为三选一的呢?写成下面这样行不行?
                         if(val==a[linebound][rowbound])
                         {
                                 *l=linebound;
                                *r=rowbound;
                                 return ture;
                         }

                         if(val>a[linebound][rowbound])
                          {
                                 linebound++;
                          }
                        
                          else
                          {
                                 rowbound--;
                          }
             这样当然是可以的,为什么呢?
             我们上面刚刚分析过,其实我们的val和a[linebound][rowbound]的比较其实就分为了两个大的情况,一种是相等,一种是不相等。两个部分完全没有共同的区域,符合相等,就直接进入相等返回,根本走不到不相等执行的语句;如果是不相等,不管是用else if 链接还是 直接是if连接,后面的a[linebound][rowbound]<val都是要比较的,所以
在这里也不存在写成 else if 就效率高了,少执行了判断的语句的问题,逻辑情况覆盖区域完全互斥,所以两者互不影响,不存在任何问题。
              那如果我们将后面>  <的情况,也分开,都用if判断有问题吗?也就是实现成以下这样:
                                                    
                         if(val==a[linebound][rowbound])
                         {
                                 *l=linebound;
                                *r=rowbound;
                                 return ture;
                         }

                         if(val>a[linebound][rowbound])
                          {
                                 linebound++;
                          }
                        
                          if(a[linebound][rowbound]>val)
                          {
                                 rowbound--;
                          }
              这样当然有问题了,我们这两个情况它是不能两个都执行的,只能执行一个,如果写成这样,可能我们linebond做了一次+1操作可能就已经越界了,但是因为还能顺序执行下去,它还有用已经越界的linebound的作为下标去做访问元素,比较一个越界区域和我们val的大小,再决定是否--rowbound,这当然是不对的,我们每做一次linebound的变换都要判断一次越界问题,所以这里我们只能写成if else形式,只能执行一个,然后就去做一次越界判断,否则就有可能出错。
             

原创粉丝点击