数组中找出前xxx个(或第xxx个)元素的实现

来源:互联网 发布:软件开发行业 编辑:程序博客网 时间:2024/04/29 20:43

简要说明:这个是我边看http://blog.csdn.net/lanphaday/archive/2008/12/18/3547776.aspx边思考的结果,由于csdn回复会把回车都弄没,而且有长度限制,所以我就发到这里了

 

 

额,来踩一脚。。。这个题的内容类似于从数组中找出前xxx个(或第xxx个),正好是我去年所思考过的一个问题。

 

当时我只想到了solution3的层次(我跳过了solution2),看来我还是差远了,啊哈哈。然后当时我的算法和这个solution3有个区别是我是在进行交换后立刻搜索结果数组,来查找最小元素。当然这个缺点和这里的solution3的bExchanged相比就是如果已经是最后一次了,那么我还要搜索一次。。呵呵。当然这个只是O(1)的可以忽略不计的差别。

 

然后既然看到这篇文章,我又重新简单想了下这个问题(此时我看完了上部分的solution3),因为以前还是没有过多考虑遍历结果数组的时间复杂度,而这里变成1w了,当然就要考虑了,呵呵。然后带着思考后的结果看完这篇文章,发现我已经能够想到solution6的那一层了,呵呵,然后就是我是没有明确有600/9400这个数字,具体这个数字应取多少我也没有实际测试过了。

 

不过我的solution6还是和这里的solution4,5,6有一些差别,写到这里分享一下:

 

我最初想到的是类似把排序后的ResultArr分解为2段,然后假设0号数组是最小的600个(为了方便说明,沿用600这个literal),后面就是1号数组咯。然后这里引入一个0a数组这个词,它相当于solution456中的交换区域。

 

因为这边写成日志了,懒得描述那么多了,就简单试着写下代码吧,顺便练练手:

 

//因为我当时考虑问题的思路是0号是最小的元素,9999时最大,所以和原文不同,这点需要注意。//如果代码有错欢迎指出 void solution6_another(T BigArr[], T ResArr[])
{
  //原封不动的先抄两句
  memcpy(ResArr, BigArr, sizeof(T)*RES_ARR_SIZE);
  std::sort(ResArr, ResArr + RES_ARR_SIZE, std::less_equal());
  //此时的条件:ResArr[10000],0号最小,9999号最大 RES_ARR_SIZE=10000
  //声明要用到的变量,并初始化。
  UINT uMinBound = 0;
  //这里0a就代表上文提到的0a数组
  BOOL bMinIsIn0a = FALSE;
  //下面开始遍历
  for (UINT i = RES_ARR_SIZE; i<BIG_ARR_SIZE; ++i)
  {
    //下面的变量代表0a数组中哪个元素可能为乱序,即需要排序
    //具体是按0开始还是按1开始看后面两处的代码吧,是为了比较符另一端没有+1或-1而定的
    UINT u0aIndex;
    //替换元素和排序
    if (bMinIsIn0a)
    {
      if (  BigArr[i] > ResArr[0]  )
      {
        ResArr[0] = BigArr[i];
        u0aIndex = 1;
        //类似冒泡排序,因为只有第一个元素乱序
        while (u0aIndex < uMinBound && ResArr[u0aIndex]<ResArr[u0aIndex-1])  //&&前面的是越界保护
        {
          ResArrSwap(u0aIndex,u0aIndex-1);  //这个函数自己写吧。。这里就这么简单带过了。
          ++u0aIndex;
        }
      } else continue;  //continue for循环,避免执行下面的判断及merge
    }
    else
    {
      if (  BigArr[i] > ResArr[uMinBound]  )
      {
        ResArr[uMinBound] = BigArr[i];  //把新的插到0a数组里
        ++uMinBound;  //ResArr里面那个我们不要了
        u0aIndex = uMinBound - 1;  //这句和上面那句可以交换顺序,就不用减1了,这里这样写是为了符合人理解的顺序
        //类似冒泡排序,因为只有最后一个元素乱序
        while (u0aIndex>1 && ResArr[u0aIndex]<ResArr[u0aIndex-1])  //&&前面的是越界保护

        while (u0aIndex>0 && ResArr[u0aIndex]<ResArr[u0aIndex-1])  //&&前面的是越界保护
        {
           ResArrSwap(u0aIndex,u0aIndex-1);  //这个函数自己写吧。。这里就这么简单带过了。
           --u0aIndex;
        }
       } else continue;  //continue for循环,避免执行下面的判断及merge
    }
 
 
    //merge
    if (uMinBound == 600)
    {
      std::inplace_merge(ResArr, ResArr + 600, ResArr + 10000, std::less_equal());  //merge到原处
      //查了apache发现有这个函数可以用,就不用原文章中用memcpy了,而且这样不会破坏BigArr[]
      uMinBound=0;  bMinIsIn0a = FALSE;//复原
    }
    else
    {
      //排序后的判断
      bMinIsIn0a = ResArr[0]<=ResArr[uMinBound];  //用小于等于,因为替换0a数组代价小并能减少以后merge的次数
    }
  }
 
  //现在遍历完了,不要忘记再merge一遍
  if (uMinBound > 0)
  {
    std::inplace_merge(ResArr, ResArr + uMinBound, ResArr + 10000, std::less_equal());
  }
}

 

简单来说,就是我是把对交换区的最后的整个sort变成了一步步的Bumble,乍看似乎效率变低了,但好处是每次有可能不需要遍历整个交换区了,因为排到顺序对就不接着往下排了。

 

至于具体孰优孰劣,未经测试,不敢乱言。

 

顺带一提,去年我是在上个学期的算法课上求解“查找数组中第n大的元素”中将其分治为“查找数组中前n大的元素”和“查找这n个元素中最小的元素”两步来求解的,当时是用java写的,大致就是上面所说的类似solution3的实现。
另外针对“查找数组中n大的元素”而不是“查找数组中n大的元素”这一特定情形,我是采用类似这样的方法:

  1. 判断n与数组长度的关系
    如果n>数组长度的一半,转而求解相反的问题,即n=length+1-n,大小比较符翻转
  2. 判断n的大小
    如果n大小不算太大,那么分治为“查找数组中前n大的元素”和“查找这n个元素中最小的元素”求解====>求解成功
    否则进行一次快速排序,得到的key的位置如果恰为n====>求解成功
    否则对新的问题求解,即key左边或key右边的数组+新的n跳到第一步。

这样用快速排序这种碰运气成分很高的算法可能能很快完成

 

编辑:上面有行代码错了,已经用strike标记了

原创粉丝点击