七大排序及其时间测试

孤街浪徒 提交于 2020-03-11 14:42:49

一、冒泡排序

public class MyBubbleSort {
        /*
             冒泡排序:
             时间复杂度:O(n) ~ O(n^2)  最好:顺序 + 标记  最坏: 逆序
             稳定性: 稳定
             空间复杂度:  常数空间  ---> O(1)
        */
        public static void bubleSort (int[] arr) {
            int len = arr.length;
            //len是未排序的元素个数
            while (len > 0) {
                //flag:true  已经有序
                boolean falg = true;
                //一轮冒泡排序
                for (int i = 0; i < len - 1; i++) {
                    if (arr[i] > arr[i + 1]) {
                        MySort.swap(arr, i, i + 1);
                        falg = false;
                    }
                }
                if (falg) {
                    //为真就是有序,有序就跳出
                    break;
                }
                len--;
            }
        }
}

二、堆排序

public class MyHeapSort {
    public static void shifDownBig (int[] arr, int parent, int sz) {
        int child = parent * 2 + 1;
        while (child < sz) {
            if (child + 1 < sz && arr[child + 1] > arr[child]) {
                child++;
            }
            //比较父节点和孩子节点
            if (arr[child] > arr[parent]) {
                MySort.swap(arr, child, parent);
                parent = child;
                child = parent * 2 + 1;
            } else {
                break;
            }
        }
    }

    /*
         堆排序:
         时间复杂度: 建堆: O(n)   log(n) +log(n - 1) + log(n - 2) + ... + 1   ---> n * log(n)
         稳定性: 不稳定  ---> 向下调整可能会破坏相对位置
         空间复杂度:  常数空间  ---> O(1)
    */
    public static void heapSort (int[] arr) {
        int len = arr.length;
        //建堆,最后一个非叶子节点向下调整
        for (int i = (len - 2) / 2; i >= 0; i--) {
            shifDownBig(arr, i, len);
        }
        //交换
        int end = len - 1;
        while (end > 0) {
            MySort.swap(arr, 0, end);
            shifDownBig(arr, 0, end);
            end--;
        }
    }
}

从最后一个非叶子节点向下调整,一直调整到根部,每次调整完,交换根部与尾部元素,因为是大堆,所以数组中最大的数就到了尾部

三、插入排序

public class MyInsertSort {
    //插入排序
    /*
       插入排序:
       时间复杂度: 最坏情况: O(n^2) ---> 逆序序列    最好情况:O(n)  ---> 顺序序列
       稳定性:稳定
       空间复杂度:  常数空间  ---> O(1)
    */
    public static void insertSort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            //i表示已排好序的最后一个位置
            int key = arr[i + 1];
            int end = i;
            while (end >= 0 && arr[end] > key) {
                //把大的元素向后移动
                arr[end + 1] = arr[end];
                end--;
            }
            arr[end + 1] = key;
        }
    }

}

遍历数组,然后一个一个往前插。

四、希尔排序

public class MyShellSort {
    /*
          希尔排序:改进的插入排序,数据越有序,优化的空间就越小
          时间复杂度: O(n) ~ O(n^2)  -----> O(n^1.3)
          稳定性:不稳定
          空间复杂度:  常数空间  ---> O(1)
    */
    public static void shellSort (int[] arr) {
        //对数据进行分组
        //每组数据进行插入排序
        //同一组数据,间隔递减
        int gap = arr.length;
        while (gap > 1) {
            gap = gap / 3 + 1;
            //i表示已排序的数据的最后一个位置
            for (int i = 0; i < arr.length - gap; i++) {
                int end = i;
                int key = arr[end + gap];

                while (end >= 0 && arr[end] > key) {
                    arr[end + gap] = arr[end];//依次往后挪一个
                    end -= gap;
                }
                arr[end + gap] = key;
            }
        }
    }
}

希尔排序是优化的插入排序,普通的插入排序是每间隔一个就排一次序,而希尔排序则把间隔改成了 gap = gap / 3 + 1;

五、归并排序

public class MyMergeSort {
    //归并排序
    private static void mergeInternal(int[] arr, int left, int mid, int right, int[] tmp) {
        int begin1 = left, end1 = mid;
        int begin2 = mid + 1, end2 = right;
        int idx = left;
        while (begin1 <= end1 && begin2 <= end2) {
            //合并
            if (arr[begin1] <= arr[begin2]) {
                tmp[idx++] = arr[begin1++];
            } else {
                tmp[idx++] = arr[begin2++];
            }
        }
        //判断是否还有剩余元素
        while (begin1 <= end1) {
            tmp[idx++] = arr[begin1++];
        }
        while (begin2 <= end2) {
            tmp[idx++] = arr[begin2++];
        }
        //拷贝
        for (int i = left; i <= right; i++) {
            arr[i] = tmp[i];
        }
    }

    public static void mergeSort (int[] arr, int left, int right, int tmp[]) {
        if (left >= right) {
            return;
        }
        //分组
        int mid = (left + right) / 2;
        ///首先给[left, mid] 和 [mid + 1, right]小组进行排序
        mergeSort(arr, left, mid, tmp);
        mergeSort(arr, mid + 1, right, tmp);
        //归并: 前提条件 数组已经有序
        mergeInternal(arr, left, mid, right, tmp);
    }


    //递归
    public static void merge (int[] arr) {
        int[] tmp = new int[arr.length];
        mergeSort(arr, 0, arr.length - 1, tmp);
    }

    //非递归
    public static void mergeNoR(int[] arr){
        int[] tmp = new int[arr.length];
        //i: 每次归并的元素个数
        for(int i = 1; i < arr.length; i *= 2){
            // j: 下一次归并的起始位置
            for(int j = 0; j < arr.length; j += 2 * i) {
                //mergeInternal(arr, j, j + i - 1, j + 2 * i - 1, tmp);  区间可能越界
                int left = j;
                int mid = j + i - 1;
                //右半部分没有数据,不用进行归并
                if(mid >= arr.length - 1) {
                    continue;
                }
                int right = j + 2 * i - 1;
                //判断右边的区间是否越界
                if(right >= arr.length){
                    right = arr.length - 1;
                }
                mergeInternal(arr, left, mid, right, tmp);
            }
        }
    }
}

先分组,然后再合并,合并的时候排序。分为递归与非递归两种。

六、快速排序**

import java.util.Stack;

public class MyQuickSort {

    private static int getMid(int[] arr, int left, int right) {
        int mid = (left + right) / 2;
        if (arr[mid] > arr[left]) {
            if (arr[mid] > arr[left]) {
                return mid;
            } else {
                if (arr[left] > arr[right]) {
                    return left;
                } else {
                    return right;
                }
            }
        } else {
            if (arr[mid] > arr[right]) {
                return mid;
            } else {
                if (arr[left] < arr[right]) {
                    return left;
                } else {
                    return right;
                }
            }
        }
    }

    //1.第一种分割方法:Hoare法
    public static int partion1(int[] arr, int left, int right) {
        //三数取中
        int mid = getMid(arr, left, right);
        //把三个数中 大小中间的数放到mid位置上
        MySort.swap(arr, left, mid);

        int key = arr[left];//key是基准值
        int start = left;
        //从左到右一直找,相遇为止
        while (left < right) {
            while (left < right && arr[right] >= key) {
                //right要是有小于基准值的就停下来进行交换
                right--;
            }
            while (left < right && arr[left] <= key) {
                //left要是有大于基准值的就停下来进行交换
                left++;
            }
            MySort.swap(arr, left, right);
        }
        //跳出循环时,left与right相遇,再把基准值与相遇位置进行交换
        MySort.swap(arr, left, start);
        //返回中间的位置,完成一次分割
        return left;
    }

    //2.第二种分割法:挖坑法
    public static int partion2 (int[] arr, int left, int right) {
        //三数取中
        int mid = getMid(arr, left, right);
        //把三个数中 大小中间的数放到mid位置上
        MySort.swap(arr, left, mid);

        //基准值
        int key = arr[left];
        //挖坑填坑
        while (left < right) {
            while (left < right && arr[right] >= key) {
                right--;
            }
            arr[left] = arr[right];
            while (left < right && arr[left] <= key) {
                left++;
            }
            arr[right] = arr[left];
        }
        //跳出循环后,把基准值放到相遇的位置
        arr[left] = key;
        return left;
    }

    //3.第三种分割法:前后遍历法
    public static int partion3 (int[] arr, int left, int right) {
        //三数取中
        int mid = getMid(arr, left, right);
        //把三个数中 大小中间的数放到mid位置上
        MySort.swap(arr, left, mid);

        int key = arr[left];
        int prev = left;//最后一个小于key的位置
        int cur = left + 1;//下一个小于key的位置
        while (cur <= right) {
            if (arr[cur] < key && ++prev != cur) {
                //如果cur找到下一个小于key的位置
                //并且prev和cur之间有大于key的值
                //则交换prev 和cur的值
                MySort.swap(arr, prev, cur);
            }
            cur++;
        }
        MySort.swap(arr, left, prev);
        return prev;
    }

    /*
        快排递归:
        时间复杂度:O(nlogn)    最坏时间复杂度: 没有优化之前 O(n^2)
        稳定性:不稳定  基准值的位置进行交换的时候有可能改变相同元素的相对位置
        空间复杂度:logn
     */
    public static void quickSort (int[] arr, int left, int right) {
        if (left < right) {
            int mid = partion3(arr, left, right);
            quickSort(arr, left, mid - 1);
            quickSort(arr, mid + 1, right);
        }
    }

    /*
        非递归: 模拟递归过程
        1. 选基准值
        2. 根据基准值分组
        3. 给划分的小组数据进行重复1,2的过程
    */
    public static void quickSortNoR (int[] arr) {
        int left = 0;
        int right = arr.length - 1;
        //用栈来记录空间
        Stack<Integer> st = new Stack<>();
        if (left < right) {
            st.push(left);
            st.push(right);
        }
        while (!st.isEmpty()) {
            //取出栈顶的一组区间
            int right1 = st.pop();
            int left1 = st.pop();
            //分组
            int mid = partion3(arr, left1, right1);
            //新的分组进行压栈
            if (mid - 1 > left1) {
                st.push(left1);
                st.push(mid - 1);
            }
            if (mid + 1 < right1) {
                st.push(mid + 1);
                st.push(right1);
            }
        }
    }
}

快速排序有三种方法,不过三种方法换汤不换药。
快排主要是分组然后排序,与归并不同的是归并是先分组在排序,快排则是分组完后就会进行排序。
优化:三数去中,首先在首位和中的三个数字中取一个中间数,作为首,效率会提高。

七、选择排序

public class MySelectSort {
    /*
          选择排序:
          时间复杂度: O(n^2)
          稳定性:可以让它稳定/不稳定
          空间复杂度:  常数空间  ---> O(1)
    */
    public static void selectSort (int[] arr) {
        //最小值放前边,最大值放后边
        int begin = 0;
        int end = arr.length - 1;
        while (begin < end) {

            int minIdx = begin;//默认开头为最值
            int maxIdx = begin;
            for (int i = begin; i <= end; i++) {
                //最小值放到前面,
                // begin往前都是排好序的
                // end往后的是排好序的
                if (arr[i] < arr[minIdx]) {
                    minIdx = i;
                }
                //最大值放到后边
                if (arr[i] > arr[maxIdx]) {
                    maxIdx = i;
                }
            }
            //交换最小值的位置
            MySort.swap(arr, begin, minIdx);
            //不能再直接交换end和max,如果最大值是第一位的话最大值就到min的位置了
            if (maxIdx == begin) {
                maxIdx = minIdx;
            }
            MySort.swap(arr, end, maxIdx);
            begin++;
            end--;
        }
    }
}

选择排序就是设置两个指针,一个是头部一个是尾部开始,遍历一遍最小的放在头部,最大的放在尾部,再遍历剩下的数组,以此类推,每次遍历筛选完后 begin++, end–,如此一来范围便缩小了。

运行时间测试

七个排序及其非递归写完后进行时间测试

测试代码:

import java.util.Random;

public class Test2 {
    public static void main(String[] args) {
        Random rdm = new Random(20200308);
        int number = 88888;
        int[] arr = new int[number];
        for (int i = 0; i < number; i++) {
            arr[i] = rdm.nextInt();
        }
        int[] arr2 = arr.clone();
        int[] arr3 = arr.clone();
        int[] arr4 = arr.clone();
        int[] arr5 = arr.clone();
        int[] arr6 = arr.clone();
        int[] arr7 = arr.clone();
        int[] arr8 = arr.clone();
        int[] arr9 = arr.clone();

        //快速排序
        long begin = System.nanoTime();
        MyQuickSort.quickSort(arr, 0, arr.length - 1);
        long end = System.nanoTime();
        System.out.printf("quickSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);

        //快速排序非递归
        begin = System.nanoTime();
        MyQuickSort.quickSortNoR(arr2);
        end = System.nanoTime();
        System.out.printf("quickSortNoR time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);

        //归并排序
        begin = System.nanoTime();
        MyMergeSort.merge(arr3);
        end = System.nanoTime();
        System.out.printf("mergeSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);

        //归并排序非递归
        begin = System.nanoTime();
        MyMergeSort.mergeNoR(arr4);
        end = System.nanoTime();
        System.out.printf("mergeSortNoR time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);

        //插入排序
        begin = System.nanoTime();
        MyInsertSort.insertSort(arr5);
        end = System.nanoTime();
        System.out.printf("inserSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);

        //希尔排序
        begin = System.nanoTime();
        MyShellSort.shellSort(arr6);
        end = System.nanoTime();
        System.out.printf("shellSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);

        //插入排序
        begin = System.nanoTime();
        MySelectSort.selectSort(arr7);
        end = System.nanoTime();
        System.out.printf("selectSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);

        //堆排序
        begin = System.nanoTime();
        MyHeapSort.heapSort(arr8);
        end = System.nanoTime();
        System.out.printf("heapSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);

        //冒泡排序
        begin = System.nanoTime();
        MyBubbleSort.bubleSort(arr9);
        end = System.nanoTime();
        System.out.printf("bubleSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);

    }
}

创造出一个88888个数据的 随机数组,进行排序,比较七大排序的时间。
结果如下
在这里插入图片描述

在这里插入图片描述

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!