演算法-點陣圖排序

栗子醬油餅發表於2019-03-03

0. Thanks

1. 概述

有這樣的一道題目,給出一定範圍的1億個資料(N=<資料<=M),要求給他從小到大排序
顯然這個涉及到超大資料的排序。一般有兩個套路:一個用堆排序,一個是用點陣圖排序。這裡說
一下點陣圖排序。

2. 原理

點陣圖排序其實是用資料的下標作對映到對應的資料。假如現在有一個待排序的資料:
int[] a = {4,7,2,5,3};

我們需要先知道這些資料的取值範圍,我們看到資料是<8,那麼我們初始化8個bit位的陣列:

演算法-點陣圖排序

並把他們初始化為零。每一個bit位的取值是0,或者1。
然後把每一個的待排序的數字取出來,根據數字的大小把bit陣列的對應下標的bit置為1.

演算法-點陣圖排序

到最後會變成這樣:

演算法-點陣圖排序

然後,我們從第0未bit開始列印非0位的下標,也就是:23457,也就排好序了。

3. Java來實現一下

基本的資料型別是沒有bit,最小是byte,所以我們先實現一個bit陣列這樣的一個資料結構:

/**
 * 這裡,先實現一個位陣列的資料結構
 */
public static class BitArr {
    private int bitLength = 0;
    private byte[] bytes;
    public byte[] getBytes() {
        return bytes;
    }
    /**
     * 構建多少位的位陣列
     * @param bitLength 位長
     */
    public BitArr(int bitLength) {
        this.bitLength = bitLength;
        bytes = new byte[(int) Math.ceil((double) bitLength/7)];
    }
    /**
     * 標記某一個位
     * 設定為1
     * @param position 位
     */
    public void mark(int position) {
        if (position>bitLength)
            return;
        int arrIndex = position/7;
        int bitIndex = position%7;
        bytes[arrIndex] |= (1 << (6-bitIndex));
    }
    public void cleanMark(int position) {
        if (position>bitLength)
            return;
        int arrIndex = position/7;
        int bitIndex = position%7;
        bytes[arrIndex] &= ~(1 << (6-bitIndex));
    }
    public void printAllBit() {
        for (byte aByte : bytes) {
            System.out.print(BitArr.Byte2String(aByte));
        }
        System.out.println();
    }
    /**
     * 列印除符號位的bit
     * @param nByte
     * @return
     */
    private static String Byte2String(byte nByte){
        StringBuilder nStr=new StringBuilder();
        for(int i=6;i>=0;i--) {
            int j=(int)nByte & (int)(Math.pow(2, (double)i));
            if(j>0){
                nStr.append("1");
            }else {
                nStr.append("0");
            }
        }
        return nStr.toString();
    }
}
複製程式碼

再基於此實現演算法:

public static int[] bitmapSort(int[] arr, int theMax) {
    if (arr==null || arr.length==0)
        return null;
    BitArr bitArr = new BitArr(theMax+1);
    for (int anArr : arr) {
        bitArr.mark(anArr);
    }
    int[] result = new int[arr.length];
    byte[] bytes = bitArr.getBytes();
    int index = 0;
    for (int i = 0; i < bytes.length; i++) {
        for (int j = 0; j < 7; j++) {
            byte temp = (byte) (1<<6-j);
            byte b = (byte) (bytes[i] & temp);
            if ( b == temp) {
                result[index++] = i*7 + j;
            }
        }
    }
    return result;
}
複製程式碼

來個驗證:

public static void main(String[] args) {
    int[] a = {4,7,2,5,14,3,8,12};
    int[] end = bitmapSort(a, 14);
    for (int x : end) {
        System.out.print(x+",");
    }
}
//輸出
2,3,4,5,7,8,12,14,
複製程式碼

有幾個地方需要注意:

  • java裡面沒有無符號的型別,所以我們只能用byte的前7位

上面寫的Java實現,其實還有幾個問題:

  • 如果我們一開始並不知道,這堆待排序資料的取值範圍怎麼辦?也許可以採取動態擴充陣列
  • 如果待排序的資料有小於0的資料呢?

4.總結

點陣圖演算法,其需要一次遍歷整個資料,假如有N個資料,就只是需要遍歷N次,所以時間複雜度
O(N)。但是,其需要額外地開闢記憶體空間,有N個資料,就需要多開闢N bit位的資料,
額外需要:N/8/1024/1024 MB 的空間。假如是一億個資料,那麼大概要:11.92MB

程式碼在這裡

相關文章