美文网首页
排序算法的一些优化和改进

排序算法的一些优化和改进

作者: 滨岩 | 来源:发表于2020-02-23 15:42 被阅读0次

1、冒泡排序 O(n^2)

冒泡排序,记下最后一次交换的位置,如果后边没有交换则交换位置为0,必然是有序的,然后下一次排序从第一个比较到上次记录的位置结束即可,优化后代码如下:

    public static void bubbleSort(Comparable[] arr) {

        int end = arr.length ;
        int last = 0;
        for (int i = 0; i < arr.length; i++) {
            last = 0;
            for (int j = 1; j < end; j++) {
                if (arr[j - 1].compareTo(arr[j]) < 0) {
                    continue;
                }
                SortTestHelper.swap(arr, j - 1, j);
                last = j;// 记录最后一次的交换位置,在此之后的元素在下一轮扫描中均不考虑

            }

            if (last > 0) {
                end = last;
            }
        }
    }

2、选择排序 O(n^2)

    public static void selectSort(Comparable[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            // 寻找[i, n)区间里的最小值的索引
            int minIndex = i;
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[j].compareTo(arr[minIndex]) < 0) {
                    minIndex = j;
                }
            }
            if (i == minIndex)
                continue;
            SortTestHelper.swap(arr, i, minIndex);
        }
    }

可以对选择排序进行优化,在每一轮循环中, 可以同时找到当前未处理元素的最大值和最小值

public static void selectSort(Comparable[] arr){

        int left = 0, right = arr.length - 1;
        while(left < right){
            int minIndex = left;
            int maxIndex = right;

            // 在每一轮查找时, 要保证arr[minIndex] <= arr[maxIndex]
            if(arr[minIndex].compareTo(arr[maxIndex]) > 0)
                swap(arr, minIndex, maxIndex);

            for(int i = left + 1 ; i < right; i ++)
                if(arr[i].compareTo(arr[minIndex]) < 0)
                    minIndex = i;
                else if(arr[i].compareTo(arr[maxIndex]) > 0)
                    maxIndex = i;

            swap(arr, left, minIndex);
            swap(arr, right, maxIndex);

            left ++;
            right --;
        }
    }

3、插入排序 O(n^2)

插入排序在有序的数据的情况下,可以进化成O(n) 级别的算法,相比O(n^2)性能高很多,它与选择排序的区别是,选择排序每一次是从前往后找最小小值,而插入排序是每次从后与它前一个元素进行比较,然后插入,插入排序适合数据量小,且有序的数据

    public static void insertSort(Comparable[] arr) {
        for (int i = 1; i < arr.length; i++) {
            for (int j = i; j > 0; j--) {
                if (arr[j].compareTo(arr[j - 1]) > 0) {
                    break;
                }

                SortTestHelper.swap(arr, j, j - 1);
            }
        }
    }

4、希尔排序 O(n^2)

希尔排序是插入排序的进化版本,插入排序是每一次轮询是跟前一个元素比较,而希尔排序是每一次轮询跟前第 h 个元素比较

    public static void sort(Comparable[] arr) {

        int step = 3;
        int n = arr.length;
        int h = 0;

        while (step * h < n) {
            h = step * h + 1;
        }

        while (h > 0) {
            for (int i = h; i < n; i++) {
                //对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序
                for (int j = i; j >= h; j -= h) {
                    if (arr[j - h].compareTo(arr[j]) > 0) {
                        SortTestHelper.swap(arr, j - h, j);
                    }
                }
            }

            h /= step;
        }

    }

5、归并排序 O(nlogn)

Merge Sort是一个O(nlogn)复杂度的算法,可以在1秒之内轻松处理100万数量级别的数据,
不要轻易尝试使用SelectionSort,InsertionSort或者BubbleSort处理100万级的数据,
否则你就见识了O(n^2)的算法和O(nlogn)算法的本质差异
 public static void sort(Comparable[] arr) {
        mergeSort(arr, 0, arr.length - 1);
    }


    // // 将arr[l...mid]和arr[mid+1...r]两部分进行归并
    private static void mergeSort(Comparable[] arr, int left, int right) {
        if (left >= right) {
            return;
        }

        int mid = (left + right) / 2;

        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);

        // 对于arr[mid] <= arr[mid+1]的情况,不进行merge
        // 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失
        if (arr[mid].compareTo(arr[mid + 1]) < 0) {
            return;
        }
        merge(arr, left, right, mid);
    }

    private static void merge(Comparable[] arr, int left, int right, int mid) {
        if (left >= right) {
            return;
        }

        int tempLength = right - left + 1;
        Comparable[] tempArr = new Comparable[tempLength];

        for (int i = left; i <= right; i++) {
            tempArr[i - left] = arr[i];
        }

        int leftP = left;
        int rightP = mid + 1;
        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        for (int k = left; k <= right; k++) {
            // 如果左半部分元素已经全部处理完毕
            if (leftP > mid) {
                arr[k] = tempArr[rightP - left];
                rightP++;
                continue;
            }
            // 如果右半部分元素已经全部处理完毕
            if (rightP > right) {
                arr[k] = tempArr[leftP - left];
                leftP++;
                continue;
            }

            // 左半部分所指元素 < 右半部分所指元素
            if (tempArr[leftP - left].compareTo(tempArr[rightP - left]) < 0) {
                arr[k] = tempArr[leftP - left];
                leftP++;
                continue;
            }
            // 左半部分所指元素 >= 右半部分所指元素
            arr[k] = tempArr[rightP - left];
            rightP++;
            continue;

        }
    }

归并排序可以优化的几个点
1、对于近乎有序的数组,也就是arr[mid]<=arr[mid+1]的情况,不进行merge操作
2、由于归并排序以后,切割的数组越小,越趋近于有序,此时使用插入排序效率更高,可以达到O(n)级别,对于小数据规模的近乎有序的数组,可以优先使用插入排序效率更高
优化后代码如下:

    // 递归使用归并排序,对arr[l...r]的范围进行排序
    private static void sort(Comparable[] arr, int l, int r) {

        // 对于小规模数组, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int mid = (l+r)/2;
        sort(arr, l, mid);
        sort(arr, mid + 1, r);
        // 对于arr[mid] <= arr[mid+1]的情况,不进行merge
        // 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失
        if( arr[mid].compareTo(arr[mid+1]) > 0 )
            merge(arr, l, mid, r);
    }

归并排序还有一种写法是“自底向顶” 归并

    //自顶向上归并排序
    private static void mergeButtomToUp(Comparable[] arr) {

        int n = arr.length;

        //step 从 1,2,4,8....
        for (int step = 1; step < n; step = 2 * step) {
            for (int i = 0; i + step < n; i += 2 * step) {
                int right = Math.min(n - 1, 2 * step + i - 1);
                int mid = i + step - 1;

                if (arr[mid + 1].compareTo(arr[mid]) >= 0)
                    continue;
                merge(arr, i, right, mid);
            }
        }
    }

与快速排序一样,归并排序也是分而治之的应用。不同的是,它先让左右两部分进行排序,然后把它们合并起来。
在排序左右两部分时,同样使用归并排序。归并排序需要额外的存储空间,
这部分空间和被排序的数组所占空间一样大,每次递归调用中分配空间,这样会使性能下降。但是归并排序平方的平衡度比
快速排序要好很多。每次都能平分n/2的平衡度,而快速排序最坏的情况下分成1/n.

相关文章

网友评论

      本文标题:排序算法的一些优化和改进

      本文链接:https://www.haomeiwen.com/subject/ocgdqhtx.html