zoukankan      html  css  js  c++  java
  • 位图法与N个数中找最大10个数

    前言

    今日,听得同学间讨论两个问题,觉得甚是有趣,一个是找到n个数找最大10个数,另一个是位映射的问题。


    一、N个数找最大10个数

    引入
    给定n个数据,比如10万,又或着100万,让你找到最大前10个数,怎么找呢?
    我心中不免一惊,真的是很巧,之前我在做数字手写识别时就考虑过这个问题,knn算法有涉及从n个邻居中找到k个最近的邻居,二者问题本质是一样的。
    听到有人不假思索,直说:“排序”。我们分析是不必的,我们只需要找到最大的10个,对次序并无要求,排序让整个数据有序是没有必要的。

    想法
    看过我那篇文章的同志应该知道我是用了一个“最值淘汰”的方法,想法很简单,我们只要不断把10个数中的最小值和剩余数比较,淘汰掉小的数,这个过程可以看作是一个不断提升最小值下限的过程。基本思路是这样,如何实现则又是一个优化过程。
    因为剩余所有数我们肯定都要遍历一遍,已经无法进一步优化,那么唯一能优化的只有一个问题了——我们如何找到10个数中的最小值?

    • 数组每次遍历10个数

    遍历m个数操作复杂度为O(m)。从N个数中找到最大M个数,则总的复杂度为O(n*m);

    • 10个数用堆存储

    建堆复杂度为O(m),我们先将前m个数建堆,因为是不断提高最小值下限,我们要建一个最小堆,每次都是和根节点比较,如果交换了值,就要对堆重新维护一次,因为维护的代价为O(logm),所以总的代价为O(nlogm);
    关于堆排序的实现,可以参考我的另一篇博客:基于比较的内排序——温故而知新

    最后讨论,还是堆结构比较方便,后来想,对于大数据,数据的重复量也是影响效率的因素,我们对数据的预处理去掉重复数据,也潜在可能提高效率。那么关于大量数据的重复性我们如何检验呢?

    顺水推舟,我们又来考虑大量数据怎么检查重复性呢?


    二、位映射

    也是假设1亿个int数据,在32位机内存上运行,让你过滤掉重复的数。

    讨论:
    同样,我们否定掉每找一个数就和后面的数进行比较的思路,int型数据可表示的数一共有232=4294967296种,这样最差情况下复杂度达到O(n2),是不理想的。

    我提出的是采用Hash表的方法,我们将数值作为key,出现次数作为value,只需要遍历一遍,value不为1就是重复的,后来一想,这样也是不理想的,因为32位机的内存只有4GB,如果重复较少,根本存不下。

    经老师提点,采用位图法
    我们知道int一共有232种,我们可以申请232bit 的数组空间,这样对于每个int数我们都可以有对应的位置映射,当出现了int最小值,我们就将数组0号位置置1,如果再次出现0,因为已经置1,所以判定为重复。
    这个方法,我们存储空间就缩小为 229bit=512MB。
    但是考虑到实现部分时,我们知道计算机最小可操作单位是1Byte,位图法则要求到bit精度,所以这又涉及到一个byte和bit之间的转换。

    算法实现思路:

    1. 既然最小操作单位是byte,我们分组即可,每8个bit就是一个byte,所以我们申请长度为229的byte数组即可。
    2. 如果没有申请byte数组,我们是从左往右直接数来确定某个bit位,有了byte数组,我们没有本质改变,只要先确定组号,再确定组内偏移一样可以定位。
    3. 怎么才能实现对单个bit位进行赋值修改呢? 又涉及到一次转换,因为操作单位是byte,我们只能通过对这个byte单位进行赋值修改某个bit,通过十进制和二进制的转换我们可以实现,比如对于某个byte,我们要让第三个0置1,我们只要对该byte赋值4即可。

      0000 0100 = 4

    4. 我们怎么判定重复呢?对于某个数必须要知道它的位置是否被置1,一种方法是通过移位单独取出该位,第二种方法则是 按位相与—— &。
      比如某个byte内部:

      0000 1100 = 12

      我们要判断4的重复性
      Method 1:逻辑移位

      对12左移2位后取出剩余首位是否为1
      0000 0011

      Method 2:按位相与

       0000 1100
      & 0000 0100
      = 0000 0100
      可见,只要重复,那么按位相与结果必定不为0;

    实现如下:
    环境:IDEA
    语言:Java

    public class Repetetive{
        public static void main(String[] args){
            byte[] res=new byte[1<<29];  //申请byte数组
            //考虑到int存在负数,我们要加上一个bias偏移值,使得最小的负数编号为0
            double bias=2147483648d; 
            //测试数据
            int arr[]={166,233,15555,2,4,23,2,23,5,2,6,-10000,-10000};
            int index;
            int relativePos;
            for(int i=0;i<arr.length;i++){
                //tmp是该数的位置编号
                double tmp=arr[i]+bias;
                //index是组号
                index=(int)(tmp/8);
                //relative是组内偏移
                relativePos=(int)(tmp%8);
                //按位相与判断结果
                if(((1<<relativePos) & (res[index]))!= 0) 
                    System.out.println(arr[i]+"重复");
                else res[index]+=1<<relativePos;
            }
        }
    }
        输出:
        2重复
        23重复
        2重复
        -10000重复
    
  • 相关阅读:
    面向对象-对象的内存图
    面向对象-类与对象的关系
    部署Ambari Server实战案例
    面向对象介绍
    基础语法-二维数组的基本使用
    基础语法-无序/有序数组中元素的查找
    常见数据结构与算法-冒泡排序
    常见数据结构与算法-选择排序
    基础语法-数组的常见问题及常见操作
    基础语法-数组的内存分配
  • 原文地址:https://www.cnblogs.com/gujiewei/p/9670577.html
Copyright © 2011-2022 走看看