位图排序算法实践

一.算法介绍

排序算法是计算机专业必学的内容,教科书里介绍的像冒泡排序,插入排序,Shell排序,堆排序,快速排序等几种经典的排序算法,其中快速排序最为推荐和广为人知,其采用二分分组递归的方式,对数据巧妙而快速的排序,平均性能复杂度为N*LogN,商用的代码库一般会使用快速排序(由于其最差性能复杂度为N*N,像STL库里面的排序算法会结合插入排序和堆排序,使其最差性能复杂度也能达到NLogN)。但是除了这些教科书里面的算法,有没有其它的排序算法,排序算法的时间复杂度能不能突破理论上的极限N*LogN呢?

经典的《编程珠玑》一书里对于长度在一定范围的不重复数字序列,介绍一款非常巧妙的算法-位图排序,其时间复杂度达到惊人的N,很多了解通用排序算法的读者,听到的第一反应可能是大呼“怎么可能!”

且看位图排序算法的实践逻辑如下:

1)第一步:申请一个长度超过最大数字的数组,

2)第二步:遍历数字序列,以数字作为索引,将数组对应的值赋值为1.

3)第三步:遍历数组,打印值为1的数组索引值

这个算法直接打破常规思维,唤山不来,向山走去,利用空间换取时间,使算法复杂度达到了不可思议的N,简单至极而又巧妙至极!

二.编程实践

“纸上得来终觉浅,绝知此事要躬行”,位图排序的实际效果到底如何,笔者开发了一个demo来验证一下,代码中使用Java的里面的位操作来实现位图存储,以节约了存储空间,另外和快排排序算法进行了性能比较,代码如下:

public static void main(String[] args) {
    final int MAX = 1000000000; //数字最大值
    final int COUNT = 300000000;//数字个数
    
    System.out.println("排序个数:" + COUNT);
    int[] ints = getRandomData(COUNT, MAX);
    long start = System.currentTimeMillis();
    start = System.currentTimeMillis();
    ints = getRandomData(COUNT, MAX);
    start = System.currentTimeMillis();
    quickSort(ints);
    System.out.println("快速排序时间:" + (System.currentTimeMillis() - start));
    
    ints = getRandomData(COUNT, MAX);
    start = System.currentTimeMillis();
    byte[] bytes = bitmapSort(ints, MAX);
    System.out.println("位图排序1时间:" + (System.currentTimeMillis() - start));
}

// 产生最大为值max,数量为count的数字
private static int[] getRandomData(int count, int max) {
    int[] ints = new int[count];
    for (int i = 0; i < count; i++) {
        int ran = (int) (Math.random() * max);
        //System.out.print(ran + " ");
        ints[i] = ran;
    }
    return ints;
}

// 位图排序法1,无法处理重复数据
private static byte[] bitmapSort(int[] array, int max) {
    final int len = (int) Math.ceil(max / 8.0);
    byte[] bytes = new byte[len];
    for (int i = 0; i < array.length; i++) {
        int j = array[i] >> 3;
        int k = array[i] % 8;
        bytes[j] = setByte(bytes[j], k);
    }
    return bytes;
}

// 将字节b第i位置1
public static Byte setByte(byte b, int i) {
    b |= 0x01 << i;
    return b;
}

/**
 * 快速排序
 *
 * @param array
 */
public static void quickSort(int[] array) {
    int len;
    if (array == null
            || (len = array.length) == 0
            || len == 1) {
        return;
    }
    sort(array, 0, len - 1);
}

/**
 * 快排核心算法,递归实现
 *
 * @param array
 * @param left
 * @param right
 */
public static void sort(int[] array, int left, int right) {
    if (left > right) {
        return;
    }
    // base中存放基准数
    int base = array[left];
    int i = left, j = right;
    while (i != j) {
        // 顺序很重要,先从右边开始往左找,直到找到比base值小的数
        while (array[j] >= base && i < j) {
            j--;
        }

        // 再从左往右边找,直到找到比base值大的数
        while (array[i] <= base && i < j) {
            i++;
        }

        // 上面的循环结束表示找到了位置或者(i>=j)了,交换两个数在数组中的位置
        if (i < j) {
            int tmp = array[i];
            array[i] = array[j];
            array[j] = tmp;
        }
    }

    // 将基准数放到中间的位置(基准数归位)
    array[left] = array[i];
    array[i] = base;

    // 递归,继续向基准的左右两边执行和上面同样的操作
    // i的索引处为上面已确定好的基准值的位置,无需再处理
    sort(array, left, i - 1);
    sort(array, i + 1, right);
}

对于最大10亿的3亿个数字排序:运行的时间结果是:

快速排序:37183毫秒

位图排序:4019毫秒

比起经典的快速排序,位图排序的性能要高出9倍以上!

但这个算法存在一个问题,就是排序的数字序列不能有重复的数字,如何改进这个算法呢?其实很简单,只要将存储最小单元由BIT改成BYTE,相同的数字,位图数组的值加1即可,这样可以处理最多255个重复的数,应该可以满足大部分场景,改进的代码如下:

// 位图排序法2,可处理重复最多255个数
private static byte[] bitmapSort2(int[] array, int max) {
    byte[] result = new byte[max];
    for (int i = 0; i < array.length; i++) {
        result[array[i]]++;
    }
    return result;
}

运行测试,第二种排序时间为4857毫秒,效率依然杠杠的!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值