排序(令我烧脑的东西,跪着也要理解)

Posted 可乐好哇!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序(令我烧脑的东西,跪着也要理解)相关的知识,希望对你有一定的参考价值。

概念

   排序,就是使一串记录按照其中某个或某些关键字的大小,递增或递减的排列起来的操作。通常指的是排升序。

  • 稳定性:两个相等的数据,如果经过排序后,排序算法能保证它相对位置不发生变化,这样称该算法具备稳定性的排序算法

基于排序的总览(七种)

图示如下:
在这里插入图片描述

插入排序

  • 直接插入排序:每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入

  • 插入排序的优化:在有序区间选择数据应该插入的位置时,因为区间的有序性,可以利用折半查找的思想

    // 1.插入排序
    public static void insertSort(int[] array) {
    
        for (int i = 1; i < array.length; i++) {
    		
            int tmp = array[i]; //无序区间的第一个数
    
            int j = i - 1;
            for (; j >= 0; j--) {
                if (array[j] > tmp) {
                    array[j + 1] = array[j];
                } else {
                    break;
                }
            }
            array[j + 1] = tmp;
        }
    }
    // 折半建议了解
    
  • 时间复杂度O(n²)

  • 空间复杂度O(1)

  • 稳定性:稳定

  • 代码实现如下:

希尔排序

  • 希尔排序法:称缩小增量法,思想是先选定一个整数,把待排序文件中所有记录分成各组,所有距离的记录分在同一组内,并对每一组内的记录进行排序,然后取。重复分组和排序的工作。当到达1时,所有记录就已经排好序。
  • 希尔排序是对直接插入排序的优化。
  • 当gap > 1时都是预排序,目的是让数组更接近于有序。
  • 代码实现如下:
    // 2.希尔排序
   public static void shell(int[] array, int gap) {
       for (int i = gap; i < array.length; i++) {

           int tmp = array[i];
           int j = i - gap;

           for (; j >= 0; j = j - gap) {
               if (array[j] > tmp) {
                   array[j + gap] = array[j];
               } else {
                   break;
               }
           }
           array[j + gap] = tmp;
       }
   }

   public static void shellSort(int[] array) {
       int[] drr = {5, 3, 1};
       for (int i = 0; i < drr.length; i++) {
           shell(array, drr[i]);
       }
   }
  • 时间复杂度O(n^1.3 ~ n^1.5)
  • 空间复杂度O(1)
  • 稳定性:不稳定

选择排序

  • 每一次从无序区间选出最大的一个元素,存放在无序区间的最后,知道全部待排序的数据元素拍完。
  • 代码实现如下:
	// 3.选择排序
   public static void selectSort(int[] array) {

       for (int i = 1; i < array.length; i++) {

           for (int j = i + 1; j < array.length; j++) {
               if (array[i] > array[j]) {
                   int tmp = array[i];
                   array[i] = array[j];
                   array[j] = tmp;
               }
           }
       }
   }
  • 时间复杂度O(n²)
  • 空间复杂度O(1)
  • 稳定性:不稳定

堆排序

  • 基本原理是选择排序,通过堆来选择无序区间的最大的数
    注意:排升序要建大堆;排降序要建小堆。
  • 代码实现如下:
    // 4.堆排序
   // 创建大堆
   public static void createHeap(int[] array) {
       for (int i = (array.length - 1) / 2; i >= 0; i--) {
           adjustDown(array, array.length, i);
       }
   }

   // 向上调整
   public static void adjustDown(int[] array, int len, int parent) {

       int leftChild = 2 * parent + 1;

       while (leftChild < len) {

           int maxChild = leftChild;
           int rightChild = 2 * parent + 2;

           if (rightChild < len) {
               if (array[rightChild] > array[leftChild]) {
                   maxChild = rightChild;
               }
           }

           if (array[parent] >= array[maxChild]) {
               break;
           }

           int tmp = array[parent];
           array[parent] = array[maxChild];
           array[maxChild] = tmp;

           parent = maxChild;
           leftChild = 2 * parent + 1;
       }
   }

   // 交换数值
   public static void swap(int[] array, int i, int j) {
       int tmp = array[i];
       array[i] = array[j];
       array[j] = tmp;
   }

   public static void heapSort(int[] array) {

       createHeap(array);

       for (int i = 0; i < array.length - 1; i++) {

           swap(array, 0, array.length - i - 1);

           adjustDown(array, array.length - i - 1, 0);

       }

   }
  • 时间复杂度O(nlog(2^n))
  • 空间复杂度O(1)
  • 稳定性:不稳定

冒泡排序

  • 通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序
  • 代码实现如下:
    // 5.冒泡排序
   public static void bubbleSort1(int[] array) {
       for (int i = 0; i < array.length - 1; i++) {
           for (int j = 0; j < array.length - 1 - i; j++) {
               if (array[j] > array[j + 1]) {
                   int tmp = array[j];
                   array[j] = array[j + 1];
                   array[j + 1] = tmp;
               }
           }
       }
   }

   // 优化后的冒泡排序
    public static void bubbleSort2(int[] array) {
       for (int i = 0; i < array.length - 1; i++) {
           boolean flag = false;
           for (int j = 0; j < array.length - 1 - i; j++) {
               if (array[j] > array[j + 1]) {
                   int tmp = array[j];
                   array[j] = array[j + 1];
                   array[j + 1] = tmp;
                   flag = true;
               }
           }
           if (flag = false) {
               break;
           }
       }
   }
  • 时间复杂度O(n²)
  • 空间复杂度O(1)
  • 稳定性:稳定

快速排序(重点掌握)

  • 从待排序区间选择一个数作为基准值
  • Partition:遍历整个待排序区间,将比基准值小的当道基准值的左边,反之放在右边
  • 采用分治思想对左右两个小区间按照同样的方式处理,直到小区间的长度等于1,这时代表已经有序了,或者小区间的长度等于0,代表没有数据了
  • 代码实现如下:
    // 6.快排
   public static int partion(int[] array, int left, int right) {

       int tmp = array[left];

       while (left < right) {
           while (left < right && array[right] >= tmp) {
               right--;
           }

           if (left >= right) {
               array[left] = tmp;
               break;
           } else {
               array[left] = array[right];
           }

           while (left < right && array[left] <= tmp) {
               left++;
           }

           if (left >= right) {
               array[right] = tmp;
               break;
           } else {
               array[right] = array[left];
           }
       }
       return left;
   }

   public static void quick(int[] array, int low, int hight) {

       if (low >= hight) {
           return;
       }

       int par = partion(array, low, hight);
       quick(array, low, par - 1);
       quick(array, par + 1, hight);
   }

   public static void quickSort(int[] array) {
       quick(array, 0, array.length - 1);
   }


   // 快排优化1
   public static void quick2(int[] array, int low, int hight) {

       if (low >= hight) {
           return;
       }

       // 快排优化2
       if (hight - low + 1 < 100) {
           insertSort2(array, low, hight);
           return;
       }

       // 快排优化1
       int mid = (low + hight) >>> 1;

       medianOfThree(array, low, mid, hight);

       int par = partion(array, low, hight);
       quick(array, low, par - 1);
       quick(array, par + 1, hight);
   }

   public static void insertSort2(int[] array, int start, int end) {

       for (int i = start; i <= end; i++) {

           int tmp = array[i];

           int j = i - 1;
           for (; j >= start; j--) {
               if (array[j] > tmp) {
                   array[j + 1] = array[j];
               } else {
                   break;
               }
           }

           array[j + 1] = tmp;
       }
   }
  • 时间复杂度O(nlog(2^n))
  • 空间复杂度O(log(2^n))
  • 稳定性:稳定

优化总结

  • 选择基准值很重要,通常用几个数取中法
    1. 选择左边或者右边
    2. 随机选取
    3. 几个数取中法

  • partition过程中把和基准值相等的数也选择出来

  • 待排序区间小于一个阈值时,使用直接插入排序

归并排序(重点)

  • 建立在归并操作上的一种有效的排序算法,该算法也采用分治法。将已有的子序列合并,得到完全有序的序列,及先使每个子序列有序,再使子序列段有序,将两个有序表合并成一个有序表,称为二路归并。
  • 代码实现如下:
	// 7.归并排序
   public static void merge(int[] array, int left, int mid, int right) {

       int s1 = left;
       int e1 = mid;
       int s2 = mid + 1;
       int e2 = right;

       int[] tmpArray = new int[right - left + 1];
       int k = 0;

       while (s1 <= e1 && s2 <= e2) {
           if (array[s1] <= array[s2]) {
               tmpArray[k++] = array[s1++];
           } else {
               tmpArray[k++] = array[s2++];
           }
       }

       while (s1 <= e1) {
           tmpArray[k++] = array[s1++];
       }

       while (s2 <= e2) {
           tmpArray[k++] = array[s2++];
       }

       for (int i = 0; i < tmpArray.length; i++) {
           array[i + left] = tmpArray[i];
       }


   }

   public static void mergeSortRec(int[] array, int left, int right) {
       if (left >= right) {
           return;
       }

       int mid = (left + right) / 2;

       mergeSortRec(array, left, mid);
       mergeSortRec(array, mid + 1, right);

       merge(array, left, mid, right);

   }

   public static void mergeSort(int[] array) {
       mergeSortRec(array, 0, array.length - 1);
   }
  • 时间复杂度O(nlog(2^n))
  • 空间复杂度O(n)
  • 稳定性:稳定

海量数据的排序问题

  • 外部排序:排序过程需要在磁盘等外部存储进行的排序,因为内存中无法把所有数据全部放下,归并排序是最常用的外部排序。
    1. 先把文件切分为200份,每个512M
    2. 分别对512M排序
    3. 进行200路归并,同时对200份有序文件做归并过程,最终结果就有序了

总结

  1. 明确七大基于比较的排序算法原理,尤其是快速排序归并排序
  2. 自主完成排序算法的实现
  3. 了解海量数据排序问题的基本思路

以上是关于排序(令我烧脑的东西,跪着也要理解)的主要内容,如果未能解决你的问题,请参考以下文章

二零二零,自己选的路,跪着也要走完

ybt1195 判断整除(自己立的flag,跪着也要打完)

重开吐槽有感及OI书籍题库推荐 -自己选的路 跪着也要走完

前端异步代码烧脑的原因

蛋疼的iframe,烧脑的跨页面

tableau一道烧脑的表计算面试题