十大排序的java实现(配有动图)

十大经典算法(java的代码实现)

算法概述

排序就是将一组对象按照某种逻辑顺序重新排列的过程。十种排序的算法一般分为两大类:

算法分类

  • 比较类排序: 通过比较来决定元素间的相对的次序
  • 非比较类排序: 不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下限,以线性时间运行,因此也称为线性时间非比较类的排序。

算法复杂度

相关术语

  • 时间复杂度:只算法需要消耗的时间资源,一般来说,计算机的算法是问题规模n的函数f(n),算法的时间复杂度记作 T(n) = O(f(n)),常见的时间复杂度有:常数阶O(1),对数阶O(logn),线性阶O(n),线性对数阶O(nlogn),平方阶O(n2).....K次方阶O(nk),指数阶O(2^n).随着问题n的不断增大,上述时间复杂度不断的增大,算法的执行效率越低。

  • 空间复杂度: 指算法需要消耗的空间资源,就是运行时占的算机的内存。

  • 稳定:如果在排序前,r[i]=r[j] 且r[i]在r[j]之前,在排序后,r[i]仍在r[j]之前。

  • 不稳定: 如果在排序前,r[i]=r[j] 且r[i]在r[j]之前,在排序后,r[i]在r[j]之后。

1. 冒泡排序

冒泡排序(英语:Bubble Sort)又称为泡式排序,是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

1.1 算法运作过程

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

1.2 动图展示

1.3 伪代码的实现

  function bubble_sort (array, length) {
    var i, j;
        for(i from 0 to length-1){
    for(j from 0 to length-1-i){
        if (array[j] > array[j+1])
            swap(array[j], array[j+1])
    }
  }
}

1.4 java代码实现

 public static int[] bubbleSort(int[] array){
    int temp;
    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]){
                temp = array[j];
                array[j] = array[j+1];
                array[j+1] = temp;
            }
        }
    }
    return array;
}

2. 选择排序

选择排序是一种简单直观的排序算法。主要优点是与数据的移动有关,如果某个元素在正确的位置上就不会被移动。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。

2.1 算法运作过程

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
  3. 重复步骤1-2直到排序结束

2.2 动图展示

2.3 java代码实现

  public static void selectSort(Comparable[] array){
    for (int i = 0; i < array.length-1; i++) {
        int min = i;
         // 1.寻找最小元素
        for (int j = i+1; j < array.length ; j++) {
              if(less(array[j],array[min])) min = j;
        }
        
        // 2.将最小元素放到已排序的队尾
        exch(array,i,min);
    }
}


 //=====================辅助方法============================
 // 元素的交换
 private static void exch(Comparable[] array, int i, int min) {
    Object temp = array[i];
    array[i] = array[min];
    array[min] = (Comparable) temp;
}
 // 元素的比较
 private static boolean less( Comparable i, Comparable j) {
    return i.compareTo(j) < 0;
}

3. 插入排序

是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

3.1 算法运作过程

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后往前搜描。
  3. 如果该元素大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2-5.

3.2 动图展示

3.3 java代码实现

public static void insertSort(Comparable[] array){
    for (int i = 0; i < array.length ; i++) {
        for (int j = i; j>0 && less(array[j],array[j-1]); j++) {
            exch(array,j,j-1);
        }
    }
}

//=================辅助方法============================
 // 元素的交换
 private static void exch(Comparable[] array, int i, int min) {
    Object temp = array[i];
    array[i] = array[min];
    array[min] = (Comparable) temp;
}
 // 元素的比较
 private static boolean less( Comparable i, Comparable j) {
    return i.compareTo(j) < 0;
}

4. 希尔排序

也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。

4.1 基于插入排序的两种改进方法

  1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列个数k,对序列进行k 趟排序;
  3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

4.2 动图展示

4.3 java代码实现

   public static void shellSort(Comparable[] array){

    int n = array.length;
    int h = 1;
     
    while(h < n/4) h = 4*h+1;
     
    while(h >=1){
        for (int i = h; i < n ; i++) {
            for (int j = i; j >= h && less(array[j],array[j-h]) ; j-=h) {
                exch(array,j,j-h);
            }
        }
        // 步长为4 
        h /= 4;
    }
}

5. 归并排序

归并排序是创建在归并操作上的一种有效的排序算法,效率为 O(nlog n)(大O符号)该算法是采用分治法的一个非常典型的应用,且各层分治递归可以同时进行。

5.1 算法运作过程

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  4. 重复步骤3直到某一指针到达序列尾
  5. 将另一序列剩下的所有元素直接复制到合并序列尾

5.2 动图展示

5.3 java代码实现

 // 归并排序的实现,使用的递归  
 public static void mergeSort(Comparable[] array){
    Comparable[] aux = new Comparable[array.length];
     mergeSort(array,aux,0,array.length-1);
}

// 归并排序的实现
public static void mergeSort(Comparable[] array,Comparable[] tempArr,int lo,int hi){
    if(lo >= hi) return;
    int mid = (lo+hi)/2;
    mergeSort(array,tempArr,lo,mid);
    mergeSort(array,tempArr,mid+1,hi);
    merge(array,tempArr,lo,hi);
}

//====================================辅助方法================================

// 原地归并的抽象方法
 private static void merge(Comparable[] array, Comparable[] tempArr, int lo, int hi) {
     // 将 array[lo....mid] 和array[mid+1....hi]归并
    int mid = (lo+hi)/2;

    // 将array[lo..hi] 复制到tempArr[lo..hi]中
    for (int i = 0; i < array.length ; i++) {
        tempArr[i] = array[i];
    }

    // 归并回到原数组array[lo....hi]
    int i = lo,j = mid+1;
    for (int k = lo; k <= hi ; k++) {
        if(i > mid) array[k] = tempArr[j++];
        else  if(j > hi) array[k] = tempArr[i++];
        else if(less(array[j],array[i])) array[k] = tempArr[j++];
        else array[k] = tempArr[i++];
    }
}
  
     // 元素的交换
 private static void exch(Comparable[] array, int i, int min) {
    Object temp = array[i];
    array[i] = array[min];
    array[min] = (Comparable) temp;
}
 // 元素的比较
 private static boolean less( Comparable i, Comparable j) {
    return i.compareTo(j) < 0;
}

6. 快速排序

又称划分交换排序,简称快排,一种排序算法。在平均状况下,排序n个项目要 O(nlog n)次比较。在最坏状况下则需要 O(n^2)次比较,但这种状况并不常见。

6.1 算法运作过程

  1. 挑选基准值:从数列中挑出一个元素,称为“基准”
  2. 分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成,
  3. 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。

6.2 动图展示

6.3 伪代码的实现

function quicksort(q)
{
 var list less, pivotList, greater
 if length(q) ≤ 1 
     return q
 else 
 {
     select a pivot value pivot from q
     for each x in q except the pivot element
     {
         if x < pivot then add x to less
         if x ≥ pivot then add x to greater
     }
     add pivot to pivotList
     return concatenate(quicksort(less), pivotList, quicksort(greater))
 }
}

6.4 java代码实现

 public static void sort(Comparable[] array, int lo, int length) {
	
	if(length <= lo) return;
	
            // 找出基准值的索引
	int j = partition(array,lo,length);
	
	 // 使用递归前部部分排序
	 sort(array,lo,j-1);
	 
	 // 后半部分排序
	 sort(array,j+1,length);
}
 
 // 找出基准值的方法
 private static int partition(Comparable[] array, int lo, int length) {
	 int i = lo,j = length+1;
	 Comparable temp = array[lo];
	 
	 // 实现左右的扫描找出这个数
	 while(true) {
		 while(less(array[++i],temp)) if(i == length) break;
		 while(less(temp,array[--j])) if(j == lo) break;
		 if(i >= j) break;
		 // 将左边大于中间值的与右边小于中间值进行交换
		 exch(array,i,j);
	 }
	 
	 // 将j放入到相应的位置
	 exch(array,lo,j);
	 return j;
}

 // 元素的交换
 private static void exch(Comparable[] array, int i, int min) {
    Object temp = array[i];
    array[i] = array[min];
    array[min] = (Comparable) temp;
}
  private static boolean less(Comparable[] array, int i, int j) {
    return array[i-1].compareTo(array[j-1]) < 0 ;
}

7. 堆排序

堆排序是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

7.1 算法运作过程

  1. 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  2. 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  3. 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。
  4. 不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

7.2 动图展示

7.3 java代码实现

    public static void sort(Comparable[] array){
    int n = array.length;

    // 实现堆化
    for (int k =n/2; k > 0 ; k--) {
        sink(array,k,n);
    }

    int k = n;
    // 实现排序
    while (k > 1){
        exch(array,1,k--);
        sink(array,1,k);
    }
 }

  // 元素的下沉
private static void sink(Comparable[] array, int k, int n) {
    while(2*k <= n){
        int j = 2*k;
        if(j<n && less(array,j,j+1)) j++;
        if(!less(array,k,j)) break;
        exch(array,k,j);
        k = j;
    }
}

private static void exch(Comparable[] array, int k, int j) {
    Object temp = array[k-1];
    array[k-1] = array[j-1];
    array[j-1] = (Comparable) temp;
}

private static boolean less(Comparable[] array, int i, int j) {
    return array[i-1].compareTo(array[j-1]) < 0 ;
}

8. 计数排序

计数排序是一种稳定的线性时间排序算法。计数排序使用一个额外的数组 C ,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组 C 来将A中的元素排到正确的位置。

8.1 算法运作过程

  1. 找出待排序的数组中最大和最小的元素
  2. 统计数组中每个值为i的元素出现的次数,存入数组 C 的第i项
  3. 对所有的计数累加( C 中的第一个元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第C[i]项,每放一个元素就将C[i]减去1

8.2 动图展示

8.3 java代码实现

 public static int[] countingSort(int[] A) {
    int[] B = new int[A.length];
    // 假设A中的数据a'有,0<=a' && a' < k并且k=100
    int k = 100;
    countingSort(A, B, k);
    return B;
}

private static void countingSort(int[] A, int[] B, int k) {
    int[] C = new int[k];

    // 计数
    for (int j = 0; j < A.length; j++) {
        int a = A[j];
        C[a] += 1;
    }

    // 求计数和
    for (int i = 1; i < k; i++) {
        C[i] = C[i] + C[i - 1];
    }
    // 整理
    for (int j = A.length - 1; j >= 0; j--) {
        int a = A[j];
        B[C[a] - 1] = a;
        C[a] -= 1;
    }
}

9. 桶排序

桶排序(Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间O(n)。但桶排序并不是比较排序,他不受到 O(nlog n)下限的影响。

9.1 算法运作过程

  1. 设置一个定量的数组当作空桶子。
  2. 寻访序列,并且把项目一个一个放到对应的桶子去。
  3. 对每个不是空的桶子进行排序。
  4. 从不是空的桶子里把项目再放回原来的序列中。

9.2 动图展示

9.3 java代码实现

  public void bucketSort(int[] arr) {

    int max = arr[0], min = arr[0];
    for (int a : arr) {
        if (max < a)
            max = a;
        if (min > a)
            min = a;
    }
    // 该值根据实际情况选择
    int bucketNum = max / 10 - min / 10 + 1;
    List buckList = new ArrayList<List<Integer>>();
    // 创建桶
    for (int i = 1; i <= bucketNum; i++) {
        buckList.add(new ArrayList<Integer>());
    }
    // 向桶内装入数据
    for (int i = 0; i < arr.length; i++) {
        int index = indexFor(arr[i], min, 10);
        ((ArrayList<Integer>) buckList.get(index)).add(arr[i]);
    }
    ArrayList<Integer> bucket = null;
    int index = 0;
    for (int i = 0; i < bucketNum; i++) {
        bucket = (ArrayList<Integer>) buckList.get(i);
        insertSort(bucket);
        for (int k : bucket) {
            arr[index++] = k;
        }
    }

}

// 把桶內元素插入排序
private void insertSort(List<Integer> bucket) {
    for (int i = 1; i < bucket.size(); i++) {
        int temp = bucket.get(i);
        int j = i - 1;
        for (; j >= 0 && bucket.get(j) > temp; j--) {
            bucket.set(j + 1, bucket.get(j));
        }
        bucket.set(j + 1, temp);
    }
}

private int indexFor(int a, int min, int step) {
    return (a - min) / step;
}

10. 基数排序

是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

10.1 算法运作过程

  1. 取得数组中的最大数,并取得位数;
  2. arr为原始数组,从最低位开始取每个位组成radix数组;
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点)

10.2 动图展示

10.3 java代码实现

  public void radixSort(int[] array,int d ) {
    int n=1; //代表位数对应的数:1,10,100...
    int k=0;//保存每一位排序后的结果用于下一位的排序输入
    int length=array.length;
    int[][] bucket=new int[10][length];//排序桶用于保存每次排序后的结果,这一位上排序结果相同的数字放在同一个桶里
    int[] order=new int[length];//用于保存每个桶里有多少个数字
    while(n<d)
    {
        for(int num:array) //将数组array里的每个数字放在相应的桶里
        {
            int digit=(num/n)%10;
            bucket[digit][order[digit]]=num;
            order[digit]++;
        }
        for(int i=0;i<length;i++)//将前一个循环生成的桶里的数据覆盖到原数组中用于保存这一位的排序结果
        {
            if(order[i]!=0)//这个桶里有数据,从上到下遍历这个桶并将数据保存到原数组中
            {
                for(int j=0;j<order[i];j++)
                {
                    array[k]=bucket[i][j];
                    k++;
                }
            }
            order[i]=0;//将桶里计数器置0,用于下一次位排序
        }
        n*=10;
        k=0;//将k置0,用于下一轮保存位排序结果
    }
}

参考资料和博客

参考博客

注: 以上的部分动图和内容摘抄自下面两个博客,如若侵权,可联系删除。

  1. https://www.cnblogs.com/onepixel/p/7674659.html
  2. https://www.cnblogs.com/developerY/p/3172379.html
    如果想了解javascript的排序实现,建议访问 https://www.cnblogs.com/onepixel/p/7674659.html

参考资料

  1. 维基百科
  2. 《算法》 Robert Sedgewick著
原文地址:https://www.cnblogs.com/chentang/p/12593313.html