十大排序算法总结更新

十大排序算法总结

一、冒泡排序

  1. 身世曰:冒泡排序可以誉为程序员跨入算法门槛的第一步,相信大家一定被冒泡排序一直萦绕在耳边。【且听冒泡吟】,冒泡乃排序家族之太上长老,掌门人不为过之。
  2. 闻之也:冒泡排序(Bubble Sort),乃计算机科学领域排序算法的简简易者。
  3. 知其身:它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。【百度百科】
  4. 名由来:这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。【百度百科】

附实录

/*
 * 对数组a中的元素排序,改良时间复杂度,空间换时间,冗余flag变量
 */
public static void bubbleSort(Integer[] a) {
    for (int i = 0; i < a.length - 1; i++) {           // 外层循环,负责控制比较趟数
        // 标志位,记录本趟循环是否进行了交换(即是否整体已经有序)
        boolean flag = false;                
        for (int j = 0; j < a.length - 1 - i; j++) {   // 内层循环,比较为冒泡的数
            if (greater(a[j], a[j + 1])) {
                exch(a, j, j + 1);
                flag = true;                           // 记录已经交换过 
            }
        }
        if (!flag) break;                              // 本趟未交换,已经有序退出外层循环
    }
}

知论之

  • 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  • 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  • 针对所有的元素重复以上的步骤,除了最后一个。
  • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

赞曰

  • 时间复杂度
    • 最优:O(n) ,若待排序序列和期望序列顺序一致,则只许挨着扫描
    • 最坏:O(n^2)

二、选择排序

  1. 身世曰:冒泡排序可以誉为程序员跨入算法门槛的第一步,相信大家一定被冒泡排序一直萦绕在耳边。【且听冒泡吟】,冒泡乃排序家族之太上长老,掌门人不为过之。
  2. 闻之也:冒泡排序(Bubble Sort),乃计算机科学领域排序算法的简简易者。
  3. 知其身:它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。【百度百科】
  4. 名由来:这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。【百度百科】

附实录

归并排序

归并排序乃二分法、递归值精华结合之。

概念:归并排序是一种分治的思想,将大问题拆解成为小问题,将一个大的数组先拆分成几个小的数组,然后再一点点的合并。

摘要:其排序思路中和快速排序算法一样使用到了递归的思想,同时在归并排序中还用到了一个算法,就是有序数组合并算法。配合递归与有序数组合并算法,归并排序能够高效且稳定的完成排序,归并排序的优点在于其时间复杂度低,稳定性高,但是缺点也是有的,那就是空间复杂度很高

我们可以总结出归并排序的算法思路,那就是在将整个数组进行不断划分,知道划分的每个字数组的长度为0或者为1,这是每个字数组统统都是有序数组,这是再按照有序数组的拼接算法,对每个子数组进行拼接,这样就能保证每次的拼接结果都还是有序的最终拼接成一个之后,整个数组便都是有序的,而数组的排序也宣布完成,关于这个字数组的划分,实际上是通过递归实现的逻辑上的划分,接下来我们使用动图来看看这个排序过程:

归并排序

归并排序的实现

public static void mergeSort(int[] arr,int left,int right){
		if(left>=right){
			return;
		}
		int mid = (left + right) / 2;
		mergeSort(arr, left, mid);
	mergeSort(arr, mid+1, right);
	//上边为拆分过程
	
}

对于拆分过程实际上就是递归,我们不断进行左右的递归,并不断减小子数组的规模,最终便会减小到每个数组的规模为1或者为0,这里需要注意的是递归出口的判断条件为:left>=right,我们为什么不写成leftright呢?这是因为在右递归中,我们的左边界为mid+1,我们的mid是通过直接整除2得到的,当数组规模为1的时候,mid的运算结果为0,此时进入下一层递归时,左递归的左右边界都是0,下层左递归正常退出,但是右递归这是便出现了问题,这是右递归中的right = mid + 1,为1,如果使用leftright,就无法判断并终止这个情况了,所有此时我们还要加入一个新情况,那就是left>right,所以我们合写为left>=right。

之后我们来探讨合并的过程,在合并的时候我们需要使用到一个额外空间,在合并时,我们需要先将合并结果存放在那个额外的新空间上,然后再将新空间上的结果复制回我们的当前数组位置上,我们下面用一个例子来介绍,如图所示:

合并

整体代码如下所示:

    static int ans = 0;
    public int InversePairs(int [] array) {
        mergeSort(array, 0, array.length - 1);
        return ans;
    }

    // 进行拆分
    static void mergeSort(int [] array, int lt, int rt) {
        // 终止条件
        if (lt >= rt) return;
        int mid = lt + (rt - lt >> 1);
        mergeSort(array, lt, mid);
        mergeSort(array, mid + 1, rt);
        mergeSort(array, lt, mid, rt);
    }

    // 进行合并
    static void mergeSort(int [] array, int lt, int mid, int rt) {
        int[] tmp = new int[rt - lt + 1]; // 临时数组,长度为需要合并的数组长度
        int lt1 = lt, rt1 = mid;
        int lt2 = mid + 1, rt2 = rt;
        int idx = 0;
        while(lt1 <= rt1 && lt2 <= rt2) {
            tmp[idx++] = array[lt1] < array[lt2] ? array[lt1++] : array[lt2++];
        }
        // 将有剩余的一边归入tmp
        while(lt1 <= rt1) tmp[idx++] = array[lt1++];
        while(lt2 <= rt2) tmp[idx++] = array[lt2++];
        // 将tmp数组赋值到array中
        for (int i = 0; i < tmp.length; i++){
            array[lt + i] = tmp[i];
        }
    }

练习题:

题目:BM20数组中的逆序对剑指 Offer 51. 数组中的逆序对

优化归并:

    public int[] MySort (int[] arr) {
        // write code here
        int left = 0, right = arr.length - 1;
        merge(left, right, arr);
        return arr;
    }

    // 分
    void merge(int left, int right, int[] arr) {
        if (left >= right) {
            return;
        }

        int mid = left + (right - left >> 1);
        merge(left, mid, arr);
        merge(mid + 1, right, arr);
        // 优化,左右两边都有序且相对有序,则直接复制
        if (arr[mid] <= arr[mid + 1]) {
            System.arraycopy(arr, left, arr, left, right - left + 1);
            return;
        }

        cure(left, mid, right, arr);
    }

    // 治
    void cure(int left, int mid, int right, int[] arr) {
        // 临时数组长度为当前区间长度
        int[] tmp = new int[right - left + 1];
        int idx = 0;
        int l1 = left, r1 = mid;
        int l2 = mid + 1, r2 = right;
       
        while (l1 <= r1 && l2 <= r2) {
            tmp[idx++] = arr[l1] < arr[l2] ? arr[l1++] : arr[l2++];
        }

        // 判断两边的剩余数,先左后右
        while (l1 <= r1) {
            tmp[idx++] = arr[l1++];
        }

        while (l2 <= r2) {
            tmp[idx++] = arr[l2++];
        }

        // 赋值给arr数组
        for (int i : tmp) {
            arr[left++] = i;
            System.out.println(left);
        }
    }

附录

具者类也:排序算法公用(交换和比较)

    
    public static class CommonSortUtils {

        /*
         数组元素 i 和 j 交换位置方法
         * */
        public static void exch(Comparable[] a, int i, int j) {
            Comparable temp;
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }

        /*
         * 比较是否a > b的方法
         * */
        public static boolean greater(Comparable a, Comparable b) {
            return a.compareTo(b) > 0;
        }

        /*
         * 比较是否a < b的方法
         * */
        public static boolean less(Comparable a, Comparable b) {
            return b.compareTo(a) > 0;
        }

    }

posted @ 2022-10-06 21:59  lam要努力  阅读(33)  评论(0编辑  收藏  举报