zoukankan      html  css  js  c++  java
  • 排序算法java实现以及简易性能测试

    离上一篇博客已经一个星期了,一直想把学到的东西写出来,但总觉得领悟的不够透彻,想写点数据库事务吧,对每种隔离级别所对应的加锁情况以及mvcc只是一知半解;想学着别人写hashmap源码分析吧,看到红黑树,emmmmmmm等我把红黑树弄明白了再好好写一篇hashmap……

    那今天,先把八大排序算法实现一遍好了。在之前学数据结构时,八种排序算法大致流程都能弄明白(其实有几个还是有些误解的,捂脸)。

    上图的内部排序罗列了八种排序,图是网上找的,那么现在,开始一个个的实现他们吧。(以下内容都是从小到大排序)

    1.排序介绍以及实现代码

    • 直接插入排序(稳定)

    直接插入排序,就是每一轮将第n个元素,插入到前n个已经排序元素中的适当位置,而由于插入的位置是从后往前查找的,因此在排序数组有序时时间复杂度为o(n)。

     1 public static void insertionSort(int[] array) {
     2     int temp,j;
     3     for (int i = 0; i < array.length; i++) {
     4         temp = array[i];
     5         for (j = i; j > 0 && temp<array[j-1]; j--) {
     6             array[j] = array[j - 1];
     7         }
     8         array[j] = temp;
     9     }
    10 }
    • 希尔排序(不稳定)

    希尔排序是插入排序的一种,由于直接插入排序在元素有序时排序效率较高,因此使用增量d对数组进行插入排序处理,使数组尽可能的有序,不断的减小增量至1,当d为1时也就是直接插入排序,不过此时数组已经基本有序,因此排序效率较高。看代码会更直观的感受到希尔排序与直接插入排序的关系。

     1 public static void shellSort(int[] array) {
     2     int temp,j;
     3     for (int d = array.length / 2; d >= 1; d /= 2) {//增量不断减小,当d=1时,和直接插入排序一致
     4         for (int i = 0; i < array.length; i++) {
     5             temp = array[i];
     6             for (j = i; j >= d && temp<array[j-d]; j -= d) {
     7                 array[j] = array[j - d];
     8             }
     9             array[j] = temp;
    10         }
    11     }
    12 }

    个人觉得希尔排序耗时与增量d的选取都是一种玄学。

    • 简单选择排序(稳定)

    简单选择排序就是每次选取一个最小的元素,将其与哨兵位交换。

    public static void selectionSort(int[] array) {
        int temp, swap;
        for (int i = 0; i < array.length; i++) {
            temp = i;
            for (int j = i; j < array.length; j++) {
                if (array[i] < array[temp]) {
                    temp = i;
                }
            }
            //交换哨兵位以及最小元素的位置,之后的代码都将用swap(array,i,j)表示
            swap = array[i];
            array[i] = array[temp];
            array[temp] = swap;
        }
    }
    • 堆排序(不稳定)

    堆排序,对于基本概念我都理解了,所以,有需要的可以看一下别人的博客,主要流程大概是

    1. 初始化建堆,从最后一个有叶子节点的节点开始进行下沉操作,沉完换“前”一个节点下沉,直到根节点下沉完毕。
    2. 交换根节点和堆尾节点
    3. 对根节点进行下沉操作,此时的下沉的范围比上一次小1

      然后不断的循环2.3操作,直到下沉范围为1。

    /**
     * 堆排序每次都建堆(on^2)
     * 
     * @param array
     */
    public static void heapSortWrong(int[] array) {
        int swap;
        for(int i = 0;i<array.length;i++){
            heaplify(array, array.length - i-1);
            
            swap = array[0];
            array[0] = array[array.length - i-1];
            array[array.length - i-1] = swap;
        }
    }
    
    /**
     * 建堆过程
     * @param array
     * @param size
     */
    public static void heaplify(int[] array,int size){
        for(int i = (size-1)/2;i>=0;i--){//从最后一个子节点的父节点开始下沉
            siftDown(array, size, i);
        }
    }
    
    /**
     * 对第parent个元素进行下沉调整
     * @param array
     * @param size
     * @param parent
     */
    public static void siftDown(int[] array,int size,int parent) {
        int swap,big;
        while(size >= (parent*2)+1){//如果存在左子节点
            big = (parent*2)+1;
            if((parent*2)+2<=size && array[big] < array[big+1]){//存在右子节点,且右节点比左节点大
                big++;
            }
            if(array[big] >array[parent]){
                swap = array[big];
                array[big] = array[parent];
                array[parent] = swap;
                
                parent = big;
            }else {
                break;
            }
        }
    }

    一开始对于第3步操作也进行了同1的建堆操作,于是跑出来的“堆排序”和冒泡差不多快。当然也可以用PriorityQueue模拟堆排序

    /**
     * 用PriorityQueue模拟堆排序
     * @param array
     */
    public static void heapSortPriority(int[] array) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        for(int i = 0;i<array.length;i++){
            queue.add(array[i]);
        }
        for(int i = 0;i<array.length;i++){
            array[i] = queue.poll();
        }
    }

    对于topK问题,维护一个大小为K的小顶堆可以解决,不论数据是否可以一次性放入内存,都可以用小顶堆解决。当然你要是故意为难我说k大到k个元素无法一次性放入内存,那我……我,就有点为难了,不过内外存结合的归并排序应该可以解决。

    • 冒泡排序(稳定)

    不多说,上代码

    public static void bubbleSort(int[] array) {
        int swap;
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array.length - i - 1; j++) {
                if (array[j] > array[j + 1]) {
                    swap = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = swap;
                }
            }
        }
    }
    • 快速排序(不稳定)

    emmm,不知道说什么,快排写了好几次了,今天又死循环了,于是我给了自己一个嘴巴子,默默的加上了两个‘=’。(再忘记我就,我就再也不手写快排了)。

    public static void quickSort(int[] array) {
        qSort(array, 0, array.length - 1);
    }
    
    public static void qSort(int[] array, int l, int r) {
        if (l < r) {
            int ll = l;
            int rr = r;
            int x = array[ll];
            while (ll < rr) {
                while (array[rr] >= x && ll < rr)//嘴巴子
                    rr--;
                array[ll] = array[rr];
                while (array[ll] <= x && ll < rr)
                    ll++;
                array[rr] = array[ll];
            }
            array[ll] = x;
            qSort(array, l, ll-1);
            qSort(array, ll+1, rr);
        }
    }

    有序时间复杂度o(n^2),无序接近o(nlogn)。

    • 归并排序(稳定)

    之前没写过归并,不过就是“归”与“合并”两个过程。

    public static void mergeSort(int[] array) {
        copy = new int[array.length];
        mSort(array, 0, array.length-1);
    }
    
    public static void mSort(int[] array,int l,int r){
        if(l<r){
            int mid = (l+r)/2;
            mSort(array, l, mid);
            mSort(array, mid+1, r);
            merge(array, l, mid, r);
        }
    }
    static int[] copy;
    public static void merge(int[] array,int l,int mid,int r){//合并
        int i = l,j = mid+1;
        for(int k = l;k<=r;k++){
            copy[k] = array[k];
        }
        
        for(int k = l; k<=r;k++){
            if(i>mid){
                array[k] = copy[j++];
            }else if (j>r) {
                array[k] = copy[i++];
            }else if (copy[i] <= copy[j]) {
                array[k] = copy[i++];
            }else {
                array[k] = copy[j++];
            }
        }
    }

    稳定的排序,时间复杂度也是稳定的o(nlogn)。

    • 基数排序

    基数排序其实就是多次桶排序,而桶排序对数据范围有一定的要求,不然桶空间就会过大,而基数排序虽然进行多次桶排序,对数据的范围要求有一定的降低,但是当数据分布过大时,如何设置桶的范围对排序性能有较大的影响,说了这么多,其实就是想说我没写基数排序,嘻嘻,因为基数排序比较适合特定的场景,比如说对日期-时间进行排序。

    2.运行结果

    对于有序数据,插入排序的性能确实较高,但是对于大量无序数据,快排的性能较为优异,归并以及(玄学)希尔排序的性能也不差。

    下图是500个正序数据,500个随机数据,以及5000个随即数据所使用的时间。基本符合理论时间复杂度。

    500正序:

    500随即:

    5000随即:

    使用自己写的堆排序以及PriorityQueue的耗时变化主要在于元素少时,jdk实现的PriorityQueue内部使用的移位比我的乘除耗时少一些,而元素多时,把元素从数组复制到PriorityQueue再移到数组中耗时较大。

    测试以及排序源码:点我

    那么,收工,睡觉!

  • 相关阅读:
    readLine读取socket流的时候产生了阻塞
    Netty开发UDP协议
    Netty关闭客户端
    GIT 回退出错 Unlink of file 'xx' failed. Should I try again? (y/n) 解决办法
    linux 安全狗安装问题
    linux连接mysql命令
    CentOS7 64位下MySQL5.7安装与配置(YUM)
    nginx已经启动 无法访问页面
    Linux系统下我的/etc/sysconfig/路径下无iptables文件
    CentOS 7 下安装 Nginx
  • 原文地址:https://www.cnblogs.com/zzzdp/p/9070104.html
Copyright © 2011-2022 走看看