美文网首页leetcode
数据结构-常见排序算法总结

数据结构-常见排序算法总结

作者: 一条小袍袍YoY | 来源:发表于2017-05-07 15:32 被阅读0次

    Sort Algorithm(ASC)

    [TOC] //怎么生成目录,纠结ing

    插入排序

    每一趟排序都将待排元素插入到已有序的序列中,但不能保证该元素的插入位置是全局有序的。

    Insertion Sort

    直接插入排序

    • 最好情况:$O(n)$
    • 最坏情况:$O(n^2)$
    • 平均情况:$O(n^2)$
    • 辅助空间:O(1)
    • 稳定性:稳定
    • 每一趟排序只能保证当前有序,并不能保证全局有序
    1. 核心思想
      数组中的每一个待排元素与已部分有序的元素依次比较,如果待排元素小于当前已有序的元素,则交换两者位置,并继续向前比较。

    2. 程序示例

      public static void insertionSort(int[] arr){
          for (int i=1;i<arr.length;i++){
              for (int j=i-1;j>=0;j--){
                  if (arr[j]>arr[j+1]){
                      swap(arr,j,j+1);
                  }
              }
          }
      }
      public static void  swap(int[] arr,int i,int j){
          int tmp=arr[i];
          arr[i]=arr[j];
          arr[j]=tmp;
      }
      
    3. 程序分析
      直接插入排序采用两层循环:

      • 外层循环用来跟踪每一个待排元素,从1到 n-1,刚开始arr[0]是默认有序的。
      • 内层循环负责当前待排元素与已部分有序的元素进行逐一比较,确定当前待排元素的最终插入位置。
    4. 优化分析*
      在内层循环中,每当逆序(已有序元素大于待排元素时)的情况出现时,就执行一次 swap 操作,交换两元素的位置,这样会导致交换次数较多。可以考虑通过比较确定最后的插入位置,然后将交换操作推迟到最后进行。这样只需要进行一次交换操作,但已有序元素的移动次数是不会改变的。

    Shell Sort

    希尔排序(缩小增量排序)

    • 步长+直接插入排序
    • 最好情况:$O(n)$
    • 最坏情况:$O(n^2)$
    • 平均情况:$O(n^{1.3})$
    • 辅助空间:O(1)
    • 稳定性:不稳定
    • 每一趟排序只能保证当前有序,并不能保证全局有序
    1. 核心思想
      希尔排序,又称缩小增量排序,使用步长来确定每一次的待排子序列,然后使用直接插入排序对当前待排子序列进行排序。在完成步长为 h 的排序后,通过证明可以保证,对于每一个 i 与 i+h 元素都是有序的。然后缩小步长,再次使用直接插入排序。直到最后步长为1,最后一次序列已经基本有序,最后采用一次直接插入排序后就可以得到最终的有序数组。

    2. 程序示例

      public static void shellSort(int[] arr){
          //第一层循环控制步长
          for (int gap=arr.length/2;gap>0;gap=gap/2){
              //第二层循环控制满足步长的所有子序列
              for (int i=gap;i<arr.length;i++){
                  //第三层循环控制当前满足步长的子序列的排序,采用直接插入排序的方式进行排序
                  for (int j=i;j<arr.length;j+=gap){
                      if (arr[j]<arr[j-gap]){
                          swap(arr,j,j-gap);
                      }
                  }
              }
          }
      }
      
      public static void  swap(int[] arr,int i,int j){
          int tmp=arr[i];
          arr[i]=arr[j];
          arr[j]=tmp;
      }
      
    3. 程序分析
      希尔排序采用三层循环:

      • 第一层循环负责确定每一趟的步长,并按一定的规则(这里采用/2折半的规则),循环减小步长到1。
      • 第二层循环针对当前步长,依次确定满足该步长的待排子序列。
      • 第三层循环针对当前确定的待排子序列,采用直接插入排序的方法进行排序。

    选择排序

    基本思想:比较+交换

    Selection Sort

    简单选择排序

    • 最好情况:$O(n^2)$
    • 最坏情况:$O(n^2)$
    • 平均情况:$O(n^2)$
    • 辅助空间:O(1)
    • 稳定性:不稳定
    • 每一趟排序保证全局有序
    1. 核心思想
      简单选择排序,是选择排序中的一种,遵循其基本思想:比较+交换。即通过依次比较确定当前趟最小元素,并根据情况与当前待排元素交换。具体做法是在每趟排序中,通过将当前待排元素与与剩余所有元素比较,找到最小的元素并与当前元素交换(如果当前待排元素就是最小,则不用交换)。

    2. 程序示例

      public static void selectionSort(int[] arr){
          for (int i=0;i<arr.length;i++){
              //这里采用 min 来标记最小元素的下标,方便 swap 的时候通过下标进行交换
              int min=i;
              //第二层循环负责将当前元素与剩余所有元素比较,找到这些元素中的最小值,如果该最小值不是当前元素,则在退出循环后将当前元素与该最小元素交换
              //因为每一次寻找当前趟最小元素都是与所有待排元素比较,因此,简单选择排序是全局有序的
              for (int j=i+1;j<arr.length;j++){
                  if (arr[j]<arr[min]){
                      min=j;
                  }
              }
              if (min!=i){
                  swap(arr,i,min);
              }
          }
      }
      
      public static void  swap(int[] arr,int i,int j){
          int tmp=arr[i];
          arr[i]=arr[j];
          arr[j]=tmp;
      }
      
    3. 程序分析
      简单选择排序,采用两层循环:

      • 外层循环用于控制当前待排元素及其待排位置。
      • 内存循环用于基于当前待排位置和待排元素,循环依次与序列中剩余其它元素比较,如果存在元素小于当前待排元素,则采用一个临时变量始终标记符合该条件的元素。在退出内层循环后,判断经过一趟完整比较后,最小元素是否为当前待排元素,如果是,说明当前待排元素是当前趟的最小值,当前位置即全局有序位置,不交换;如果不是,则采用 swap 交换。

    Heap Sort

    堆排序

    • 最好情况:$O(nlog_2n)$
    • 最坏情况:$O(nlog_2n)$
    • 平均情况:$O(nlog_2n)$
    • 辅助空间:O(1)
    • 稳定性:不稳定
    • 每一趟排序保证全局有序
    1. 核心思想
      堆排序,先根据父节点与子节点下标关系(i,2i+1,2i+2)将待排序列构造为大顶堆(实际上还是在数组中存储)。然后执行循环操作:每次将顶点元素与最后一个元素swap,交换后,当前最大元素已经位于数组的末尾,因为原来的末尾元素到了顶点位置,有可能破坏堆的性质,则通过堆调整来修正堆性质。循环执行这一组操作,直到堆中只有一个元素的时候,该待排序列已然有序。

    2. 程序示例

      public static void heapSort(int[] arr){
          buildHeap(arr);
          int length=arr.length;
          while (length>0){
              swap(arr,0,length-1);
              length--;
              adjustToHeap(arr,length,0);
          }
      
      }
      
      public static void buildHeap(int[] arr){
          for (int i=0;i<arr.length/2;i++){
              adjustToHeap(arr,arr.length,i);
          }
      }
      
      public static void  adjustToHeap(int[] arr,int length,int index){
          int indexOfMax=index;
          while (true){
              int leftIndex=getLeftChild(index);
              int rightIndex=getRightChild(index);
              if (leftIndex<length&&arr[leftIndex]>arr[indexOfMax]){
                  indexOfMax=leftIndex;
              }
              if (rightIndex<length&&arr[rightIndex]>arr[indexOfMax]){
                  indexOfMax=rightIndex;
              }
              if (indexOfMax!=index){
                  swap(arr,index,indexOfMax);
                  index=indexOfMax;
                  continue;
              }
              else {
                  break;
              }
          }
      }
      
      public static int getLeftChild(int i){
          return 2*i+1;
      }
      
      public static int getRightChild(int i){
          return 2*i+2;
      }
      
      public static void  swap(int[] arr,int i,int j){
          int tmp=arr[i];
          arr[i]=arr[j];
          arr[j]=tmp;
      }
      
    3. 程序分析
      在采用堆排序前,需要对待排序列构造大顶堆。具体构造方式,稍后详述

      • 在生成大顶堆后,满足大顶堆的性质,第一个元素 arr[0]是当前0~arr.length-1中的最大值。接下来对该大顶堆进行 N-1 次的交换和调整。
      • 具体做法是,在每次交换和调整中,将堆顶的最大值与堆尾元素互换,这样,该最大值就位于正确顺序位置上。因为互换后有可能破坏大顶堆顺序,因此必须在互换后调用 adjustToHeap 进行堆性质修正。

    交换排序

    Bubble Sort

    冒泡排序

    • 最好情况:$O(n^2)$
    • 最坏情况:$O(n^2)$
    • 平均情况:$O(n^2)$
    • 辅助空间:O(1)
    • 稳定性:稳定
    • 每一趟排序保证全局有序
    1. 核心思想
      冒泡排序,在每一趟排序中,都依次比较左右两个元素,如果不符合大小关系,则交换位置,并继续比较下一个坐标位置。在一趟比较结束后,当前趟最大的元素会沉底。

    2. 程序示例

      public static void bubbleSort(int[] arr){
          //对于 N 个元素的序列,需要 N-1 趟排序
          for (int i=1;i<arr.length;i++){
              //在每一趟排序后,下标为 arr.length-i的元素已经位于全局有序位置,因此下一趟比较的最后待排位置为 arr.length-i-1
              //每一趟比较中,当前下标的左右两个元素如果顺序不当,则互相交换,较大的向后调整,如果满足大小要求,则不交换。这样依次对剩余元素进行比较,直到 arr.length-i
              for (int j=0;j<arr.length-i;j++){
                  if (arr[j]>arr[j+1]){
                      swap(arr,j,j+1);
                  }
              }
          }
      }
      
      public static void  swap(int[] arr,int i,int j){
          int tmp=arr[i];
          arr[i]=arr[j];
          arr[j]=tmp;
      }
      
    3. 程序分析
      冒泡排序采用两层循环,对于 N 个元素的冒泡排序,需要进行 N-1 趟排序,第 i 趟排序需要比较 N-i 次。

      • 第一层循环控制冒泡排序的趟数,从1到 N-1趟冒泡排序。
      • 第二层循环控制每一趟的冒泡排序,因为经过第 i-1 趟的排序,第 N-i+1 个位置已经有序,所以在第 i 趟的冒泡排序中,只需要从第一个元素一直比较到第 N-i 个元素。
    4. 优化分析
      针对最好情况为$O(n)$的情形,可以在外层循环中设置一个变量用来检查上一趟是否存在交换操作,在内层循环中,发生 swap 操作则置该变量为 true,否则默认为 false。在退出内层循环后,外层逻辑会判断如果变量为 true 说明上一趟待排序列不满足顺序条件,继续本趟排序;如果为 false 说明在上一趟中待排序列所有元素已经有序。因此在本趟排序可以直接 break,已经提前完成了序列排序。

    Quick Sort

    快速排序

    • 最好情况:$O(nlog_2n)$
    • 最坏情况:$O(n^2)$
    • 平均情况:$O(nlog_2n)$
    • 辅助空间:$O(nlog_2n)$
    • 稳定性:不稳定
    • 每一趟排序保证轴 key 全局有序
    1. 核心思想
      循环比较交换+分而治之的思想。在每一趟的排序操作中,当 i不等于j 的时候,将大于 key 的元素都交换在 key 的右边,小于 key 的元素都交换在 key 的左边。当 i等于j 的时候,就是 key 的最终有序位置。但是经过一趟排序操作,仅保证了当前 key 位于正确有序的位置 i,但对于start~i-1和 i+1~end 是无序的,因此需要在这两段上递归调用刚刚的排序操作。

    2. 程序示例

      public static void quickSort(int[] arr,int start,int end){
          if (start<end){
              int i=start;
              int j=end;
              int key=arr[start];
              while (i!=j){
                  //从后向前循环找到第一个小于 key 的元素
                  while (i<j&&arr[j]>=key){
                      j--;
                  }
                  //将该元素替换到 key 的左边
                  if (i<j){
                      arr[i]=arr[j];
                      i++;
                  }
                  //从前向后循环找第一个大于 key 的元素
                  while (i<j&&arr[i]<key){
                      i++;
                  }
                  //将该元素替换到 key 的右边
                  if (i<j){
                      arr[j]=arr[i];
                      j--;
                  }
              }
              //当 i=j 的时候,说明在这一趟交换过后,所有大于 key 的元素都位于 key 的右边,所有小于 key 的元素都位于 key 的左边
              //而此时 i==j 即表示 key 应该插入的位置,即原先的 arr[start]元素的最终位置
              arr[i]=key;
              quickSort(arr,start,i-1);
              quickSort(arr,i+1,end);
          }
      }
      
    3. 程序分析
      快速排序在每一趟排序中,先选定一个轴,通过交换来确定轴的最终位置,在一趟排序后,大于或小于该轴的元素会位于该轴的两侧。

      • 外层循环用来控制这趟排序是否结束,当 i 等于 j 的时候,说明待排序列的比较已经结束,该位置即为轴 key 的最终有序位置。
      • 在第一个内层循环中,先从后向前循环查找第一个小于 key 的元素,如果存在则退出循环并将该元素交换至 key 的 i 位置,然后再进入第二个内层循环。如果没有存在一个小于 key 的元素,说明在从后向前的查找中,key 后面的元素都大于 key,因此,说明 key 就是在正确的有序位置。
      • 在第二个内层循环中,从前向后循环查找第一个大于 key 的元素,如果存在则退出循环并交换至当前 j 位置,然后退出该循环,判断 i 是否等于 j,不等于则说明该趟还没比较结束,继续从第一个内层循环开始,继续排序操作。直至 i 等于 j。
    4. 优化分析
      快速排序对于越无序的序列效果越好,但对于基本有序序列,在选定 key 后,有可能剩余的 N-1个元素均大于或小于 key,导致分而治之的效果不明显,没有明显的均分待排序列,使得效率接近于插入排序$O(n^2)$的效果。改善这样的情况,可以尝试采用随机 key 的方式,每一趟排序,均在当前 start~end 中随机选取 key 作为轴,而不是始终设定为 arr[start]为轴,尽可能通过随机化减少有序影响,实现或接近分治效果。

    相关文章

      网友评论

        本文标题: 数据结构-常见排序算法总结

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