­

常见的五种排序算法

  • 2019 年 12 月 26 日
  • 筆記

冒泡排序

冒泡排序简介

冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。

如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。大的数往下沉,小的数往下冒。 稳定、原地排序,最好时间复杂度为O(n),最坏与平均时间复杂度为O(n2)。

原地排序简介

原地排序指的是空间复杂度为O(1)的排序算法。

稳定排序简介

稳定性指的是:如果待排序列中存在值相等的元素,经过排序之后,相等元素的先后顺序没有改变 比如我们有一组数据 2,9,3,4,8,3,按照大小排序之后就是 2,3,3,4,8,9。 这组数据里有两个 3。经过某种排序算法排序之后,如果两个 3 的前后顺序没有改变,那我们就把这种排序算法叫作稳定的排序算法;如果前后顺序发生变化,那对应的排序算法就叫作不稳定的排序算法。

冒泡代码代码实现

public class BubbleSort {      public static void main(String[] args) {          // TODO Auto-generated method stub          int[] data=new int[]{2,3,5,4,7,6,9,10,12,11,23,22,18,8};          bubbleSort(data);          for(int i=0;i<data.length;i++){              System.out.print(data[i]+" ");          }      }         public static void bubbleSort(int[] data){          if(data.length<=1) return;          for(int i=0;i<data.length;i++){              //提前退出冒泡循环的标志位              boolean flag = false;              for(int j=0;j<data.length-1-i;++j){                  if(data[j]>data[j+1]){                      int temp = data[j];                      data[j] = data[j+1];                      data[j+1] = temp;                      flag = true;//有数据交换                  }              }              if(!flag) break;//没有数据交换,退出          }      }  }

插入排序

插入排序简介

我们将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。 稳定、原地排序,最好时间复杂度为O(n),最坏与平均时间复杂度为O(n2)。

代码实现

public class InsertSort {      public static void main(String[] args) {          // TODO Auto-generated method stub          int[] data=new int[]{4,3,2,1,5};    insertSort(data);          for(int i=0;i<data.length;i++){              System.out.print(data[i]+" ");          }      }         public static void insertSort(int[] data){          if(data.length<=1) return;          for(int i=1;i<data.length;++i){              int value = data[i];              int j=i-1;              for(;j>=0;--j){                  if(data[j]>value){                      data[j+1] = data[j];                  }else {                      break;                  }              }              data[j+1]=value;          }      }  }

选择排序

选择排序简介

选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。 不稳定、原地排序,最好时间复杂度为O(n2),最坏与平均时间复杂度为O(n2)。 选择排序是一种不稳定的排序算法。选择排序每次都要找剩余未排序元素中的最小值,并和前面的元素交换位置,这样破坏了稳定性。

代码实现

public class SelectSort {      public static void main(String[] args) {          // TODO Auto-generated method stub          int[] data=new int[]{2,3,5,4,7,6,9,10,12,11,23,22,18,8};          selectSort(data);          for(int i=0;i<data.length;i++){              System.out.print(data[i]+" ");          }      }      public static void selectSort(int[] data){          if(data.length<=1) return;          for(int i=0;i<data.length-1;++i){              int minIndex=i;              for(int j=i+1;j<data.length;++j){                  if(data[j]<data[minIndex]){                      minIndex = j;                  }              }              int temp = data[i];              data[i] = data[minIndex];              data[minIndex] = temp;          }      }  }

归并排序

归并排序简介

如果要排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。 归并排序用的是一种分治思想,它不是原地排序,即空间复杂度不是O(1),空间复杂度为O(n),不管最好还是最坏或平均,它的时间复杂度都为O(nlogn),是一个稳定的算法。

代码实现

/**   * 归并排序   * 将需要排序的数组从中间分成两部分,将这两部分分别排序,再将排序好的两部分合并在一起   * 时间复杂度O(nlogn)   * 空间复杂度(n)   *   */  public class MergeSort {         public static void main(String[] args) {          int[] a= new int[]{1,2,4,3,45,34,23,6,7};          mergeSort(a, 9);          for(int i=0;i<a.length;i++){              System.out.print(a[i]+" ");          }      }      private static void mergeSort(int[] a,int n){          mergeIntaily(a, 0, n-1);      }         private static void mergeIntaily(int[] a,int p,int r){          if(p>=r) return;          int q = p+(r-p)/2;          mergeIntaily(a, p, q);          mergeIntaily(a, q+1, r);          merge(a, p, q, r);      }         private static void merge(int[] a,int p,int q,int r){          int i=p;          int j = q+1;          int k=0;          //申请一个数组,大小与a[p..r]相同          int[] tmp = new int[r-p+1];          //用两个游标,一个i指向a[p..q]中的第一个元素,一个j指向a[q+1..r]的第一个元素,          //如果a[i]<=a[j],就将a[i]的值放进tmp数组中,i后移,否则将a[j]元素放进tmp数组中,j后移          while(i<=q&&j<=r){              if(a[i]<=a[j]){                  tmp[k++] = a[i++];              }else {                  tmp[k++] = a[j++];              }          }             //判断那部分数组剩余          int start = i;          int end = q;          if(j<=r){              start = j;              end = r;          }             //将剩余数组放进tmp中          while(start<=end){              tmp[k++] = a[start++];          }             //在将tmp数组中的元素放回原数组中          for(i=0;i<=r-p;i++){              a[p+i] = tmp[i];          }      }  }

merge(A[p…r], A[p…q], A[q+1…r]) 这个函数的作用就是,将已经有序的 A[p…q]A[q+1…r] 合并成一个有序的数组,并且放入 A[p…r]。 申请一个临时数组 tmp,大小与 A[p…r] 相同。我们用两个游标 i 和 j,分别指向 A[p…q] 和 A[q+1…r] 的第一个元素。比较这两个元素 A[i] 和 A[j],如果 A[i]<=A[j],我们就把 A[i] 放入到临时数组 tmp,并且 i 后移一位,否则将 A[j] 放入到数组 tmp,j 后移一位。 继续上述比较过程,直到其中一个子数组中的所有数据都放入临时数组中,再把另一个数组中的数据依次加入到临时数组的末尾,这个时候,临时数组中存储的就是两个子数组合并之后的结果了。最后再把临时数组 tmp 中的数据拷贝到原数组 A[p…r] 中。

快速排序

快速排序简介

要排序的数组下标从p到r的一组数据,选取从p到r之间的任意一个数据作为pivot(分区点),也是分治思想。 快速排序是原地排序,不稳定,最坏时间复杂度为O(n2),最好和平均时间复杂度都为O(nlogn),空间复杂度为O(1)。

代码实现

public class QuickSort {      public static void main(String[] args) {          // TODO Auto-generated method stub          int[] a= new int[]{1,2,4,3,45,34,23,6,7};          quickSort(a, 9);          for(int i=0;i&lt;a.length;i++){              System.out.print(a[i]+&quot; &quot;);          }      }         private static void quickSort(int[] a,int n){          quickSortIntaily(a, 0, n-1);      }         private static void quickSortIntaily(int[] a,int p,int r){          if(p&gt;=r) return; //递归终止条件             int q= partition(a, p, r);             quickSortIntaily(a, p, q-1);          quickSortIntaily(a, q+1, r);      }         //分区函数,随机选择一个元素作为pivot,一般选取从p到r的最后一个元素,然后对a[p..r]分区,返回pivot下标      private static int partition(int[] a,int p,int r){          int pivot = a[r];          int i=p;          for(int j=p;j&lt;r;++j){              if(a[j]&lt;pivot){                  int tmp = a[i];                  a[i] = a[j];                  a[j] = tmp;                  ++i;              }          }          int tmp = a[i];          a[i] = a[r];          a[r] = tmp;          return i;      }  }

遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,将大于 pivot 的放到右边,将 pivot 放到中间。经过这一步骤之后,数组 p 到 r 之间的数据就被分成了三个部分,前面 p 到 q-1 之间都是小于 pivot 的,中间是 pivot,后面的 q+1 到 r 之间是大于 pivot 的。

归并与快排区别

  • 归并排序的处理过程是由下到上的,先处理子问题,然后再合并。而快排处理过程是由上到下的,先分区,然后再处理子问题。归并排序虽然是稳定的、时间复杂度为 O(nlogn) 的排序算法,但是它是非原地排序算法;
  • 归并排序和快速排序是两种稍微复杂的排序算法,都是使用分治的思想,代码都通过递归来实现,过程相似;