十大经典排序算法(java)——部分代码参考算法第四版

在这里插入图片描述

1. 冒泡排序

主要思想:外层循环从1到n-1,内循环从当前外层的元素的下一个位置开始,依次和外层的元素比较,出现逆序就交换。
特点:stable sort(稳定性排序)、In-place sort(不占用额外的空间,只是交换元素)
最优复杂度:当输入数组就是排好序的时候,复杂度为O(n),而快速排序在这种情况下会产生O(n^2)的复杂度。
最差复杂度:当输入数组为倒序时,复杂度为O(n^2)
插入排序比较适合用于“少量元素的数组”。

其实插入排序的复杂度和逆序对的个数一样,当数组倒序时,逆序对的个数为n(n-1)/2,因此插入排序复杂度为O(n^2)。

1.1 算法步骤

  • 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

  • 针对所有的元素重复以上的步骤,除了最后一个。

  • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

1.2 代码

public class BubbleSort{
	public int[] sort(int[] sourceArray){
		int[] a = Arrays.copyOf(sourceArray,sourceArray.length);
		for(int i=1;i<a.length;i++){
			for(int j=0;j<a.length-i;j++){
				if(a[j]>a[j+1]){
					int temp = a[j+1];
					a[j+1] = a[j];
					a[j] = temp;
				}
			}
		}
		return a;
	}
}

2. 选择排序

特性:In-place sort,unstable sort。
思想:每次找一个最小值。
最好情况时间:O(n^2)。
最坏情况时间:O(n^2)。

2.1 算法步骤

  • 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置

  • 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

  • 重复第二步,直到所有元素均排序完毕。

2.2 代码

public class selectionSort{
	public static void sort(int[] array){
		//一共需要n-1次
        for(int i = 0;i<array.length-1;i++){
            int temp = i;
            // 每轮需要比较的次数 N-i
            for(int j = i+1;j<array.length;j++){
                if(array[j]<array[temp]){
                // 记录目前能找到的最小值元素的下标
                    temp = j;
                }
            }
            // 将找到的最小值和i位置所在的值进行交换
            if(temp !=i){
                int a = array[temp];
                array[temp] = array[i];
                array[i] = a;
            }
        }
    }
}

3. 插入排序

主要思想:
特点:stable sort(稳定性排序)、In-place sort(不占用额外空间)
最优复杂度:当输入数组就是排好序的时候,复杂度为O(n),而快速排序在这种情况下会产生O(n^2)的复杂度。
最差复杂度:当输入数组为倒序时,复杂度为O(n^2)
插入排序比较适合用于“少量元素的数组”。

其实插入排序的复杂度和逆序对的个数一样,当数组倒序时,逆序对的个数为n(n-1)/2,因此插入排序复杂度为O(n^2)。

3.1 算法步骤

  • 将待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

  • 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

3.2 代码

public class InsertSort {
    public static void main(String []args){
        int[] arr = {9,8,7,4,3,5,2,3,1,6,2};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void sort(int[] arr){
        for(int i=1;i<arr.length;i++){
            for(int j=i;j>0 && arr[j]<arr[j-1];j--){
                swap(arr,j-1,j);
            }
        }
    }
    /**
     * 交换数组中两个元素
     * @param arr
     * @param i
     * @param j
     */
    public static void swap(int[] arr,int i ,int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

插入排序的改进:内循环发现逆序不交换,采用整体右移,直到没有逆序的时候把元素放在该位置

public class InsertSort2 {
    public static void main(String[] args) {
        int[] array = new int[]{2, 3, 5, 8, 9, 0, 7, 5, 1, 6, 8, 7};
        sort(array);
        System.out.println(Arrays.toString(array));
    }

    private static void sort(int[] array) {
        int n = array.length;
        for (int i = 1; i < n; i++) {
            int key = array[i];
            int j = i -1;
            while (j >= 0 && array[j]>key) {
                array[j + 1] = array[j];
                j--;
            }
            array[j+1] = key;
        }
    }
}

4. 希尔排序

思想:基于插入排序,交换不相邻的元素已对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。思想是使数组中任意间隔为h的元素都是有序的,这样的数组称为h有序数组.
特性:In-place sort,unstable sort。
思想:每次找一个最小值。
最好情况时间:O(n)。
最坏情况时间:O(n^2)。

4.1 算法步骤

  • 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;

  • 按增量序列个数 k,对序列进行 k 趟排序;

  • 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。
在这里插入图片描述

4.2代码

public class Shell {
    public static void main(String[] args){
        int[] arr = {9,8,7,4,3,5,2,3,1,6,2};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void sort(int[] arr){
        int gap = 1;
        while(gap<arr.length/3) gap=3*gap+1;//一个递增序列gap=1,4,13,40,121,364......
        while(gap >= 1){
            for(int i = gap;i<arr.length;i++){
                //这里就是插入排序;将a[i]插入到a[i-gap]、a[i-2*gap]、a[i-3*gap]......中去
                for(int j=i; j>=gap && arr[j]<arr[j-gap]; j-=gap){
                    swap(arr,j,j-gap);
                }
            }
            gap = gap/3;
        }
    }
    /**
     * 交换数组中两个元素
     * @param arr
     * @param i
     * @param j
     */
    public static void swap(int[] arr,int i ,int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

5. 归并排序

特点:stable sort、Out-place sort
思想:运用分治法思想解决排序问题。
最坏情况运行时间:O(nlgn)
最佳运行时间:O(nlgn)
虽然与快排渐近复杂度一样,但是归并排序的系数比快排大。

程序中merge的精髓(也就是排序):左半边用尽,则取右半边元素;右半边用尽,则取左半边元素;右半边的当前元素小于左半边的当前元素,则取右半边元素;右半边的当前元素大于左半边的当前元素,则取左半边的元素。实际上大部分发生的都是后面两句话,前面两句只是特殊情况而已。

5.1 算法步骤

  • 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;

  • 设定两个指针,最初位置分别为两个已经排序序列的起始位置;

  • 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;

  • 重复步骤 3 直到某一指针达到序列尾;

  • 将另一序列剩下的所有元素直接复制到合并序列尾。
    在这里插入图片描述

5.2 代码

在这里插入图片描述

自顶向下
public class MergeSort {
    public static void main(String []args){
        int[] arr = {9,8,7,4,3,5,2,3,1,6,2};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static int[] aux;//归并所需的辅助数组

    public static void sort(int[] arr){
        aux = new int[arr.length];
        sort(arr,0,arr.length-1);
    }

    public static void sort(int[] arr,int lo,int hi){
        if(hi<=lo) return;
        int mid =lo + ( hi - lo )/2;
        sort(arr,lo,mid);//经过该步,左半边有序
        sort(arr,mid+1,hi);//经过该步,右半边有序
        merge(arr,lo,mid,hi);//归并左右两个子数组
    }

    public static void merge(int[] arr,int lo,int mid,int hi){
        int i = lo,j = mid+1;
        //先将arr[]中的元素复制到aux[]中
        for(int k = lo;k<=hi; k++){
            aux[k] = arr[k];
        }
        //然后归并到arr[]中
        for(int k=lo;k<=hi;k++){
            if(i>mid) arr[k] = aux[j++];//如果左边用尽,取右边元素
            if(j>hi)  arr[k] = aux[i++];//如果右边用尽,取左边元素
            else if(aux[i]<aux[j]){
                arr[k] = aux[i++];//如果左边元素小,取左边元素
            }else {
                arr[k] = aux[j++];//如果右边元素小,取右边元素
            }
        }
    }
}

在这里插入图片描述
首先两两归并,然后四四归并,然后八八归并,一直下去。

自底向上
public class MergeSort2 {

    public static void main(String[] args) {
        int[] array = new int[]{2, 3, 5, 8, 9, 0, 7, 5, 1, 6, 8, 7};
        sort(array);
        System.out.println(Arrays.toString(array));
    }

    public static void sort(int[] array) {
        int N = a.length;
        int[] aux = new int[N];
        for (int n = 1; n < N; n = n+n) {
            for (int i = 0; i < N-n; i += n+n) {
                int lo = i;
                int m  = i+n-1;
                int hi = Math.min(i+n+n-1, N-1);
                merge(array, aux, lo, m, hi);
            }
        }
    }

    private static void merge(int[] array, int[] aux, int lo, int mid, int hi) {
        for (int k = lo; k <= hi; k++) {
            aux[k] = array[k];
        }
        // merge back to a[]
        int i = lo, j = mid+1;
        for (int k = lo; k <= hi; k++) {
            if(i > mid) array[k] = aux[j++];  // this copying is unneccessary
            else if (j > hi) array[k] = aux[i++];
            else if (aux[j]<aux[i]) array[k] = aux[j++];
            else  array[k] = aux[i++];
        }
    }
}

6.快速排序

特性:unstable sort、In-place sort。
最坏运行时间:当输入数组已排序时,时间为O(n^2),当然可以通过随机化来改进(shuffle array 或者 randomized select pivot),使得期望运行时间为O(nlgn)。
最佳运行时间:O(nlgn)
快速排序的思想也是分治法。
当输入数组的所有元素都一样时,不管是快速排序还是随机化快速排序的复杂度都为O(n^2),

6.1 算法步骤

  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。 此过程可以使用两个哨兵i与j。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

6.2 代码

public class QuickSort{
    
    public static void quickSort(int[] a,int left,int right){
    	//终止条件
        while(left>right) return;
		//先确定一个基准,以左边第一个元素为基准
        int temp = a[left];
        //指定两个哨兵,分别从左右两端出发
        int i = left,j = right;

        while(i!=j){
        	 //右指针先行,找到一个比基准小的值
             while(a[j]>=temp && i<j) j--;
             //右指针找到后停下,左指针开始向右移动找到一个比基准大的值
       		 while(a[i]<=temp && i<j) i++;
            //右指针找到后停下,如果i,j,不相遇,交换i,j位置上的数字
             if(i<j){
                int t = a[i];
                a[i] = a[j];
                a[j] = t;
             }
        }
		//当i,j相遇后,跳出循环,基准归位,该基准就到了自己的最终位置了
        a[left] = a[i];
        a[i] = temp;
	//分别对子序列进行快排
        quickSort(a,left,i-1);
        quickSort(a,j+1,right);
    }
    public static void main(String[] args) {
        int [] array = {5,2,3,1,6,4,7,8,0,9};
        QuickSort.quickSort(array,0,array.length - 1);
        for (int i = 0; i < array.length;i++)
            System.out.print(array[i] + " ");
    }
}

7.堆排序

特性:unstable sort、In-place sort。
最优时间:O(nlgn)
最差时间:O(nlgn)

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
在这里插入图片描述
对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子
在这里插入图片描述
该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:

  • 大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]

  • 小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

堆排序是一种选择排序,整体主要由构建初始堆+交换堆顶元素和末尾元素并重建堆两部分组成。其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)…1]逐步递减,近似为nlogn。所以堆排序时间复杂度一般认为就是O(nlogn)级。

7.1 算法步骤

  • 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

  • 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

  • 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

7.2 代码

public class HeapSort2 {
    public static void main(String[] args){
        int[] arr = {1,3,2,6,4,8,5,9,3,5,0};
        sort(arr);
        System.out.print(Arrays.toString(arr));
    }

    public static void sort(int[] arr){
        int N = arr.length;
        //构建最大堆
        //为了复用sink()函数,可以从数组最后由右向左下沉元素,构建子堆,当然也可以由左向右上浮元素;
        for(int j=N/2;j>0;j--){
            sink(arr,j,N);
        }
        //交换堆顶,堆尾元素
        while(N>1){
            exch(arr,1,N--);
            //将堆顶的元素下沉
            sink(arr,1,N);
        }
    }

    //元素下沉(堆元素序号从 1 开始,简化操作
    public static void sink(int[] arr,int k,int N){
        while(2*k<=N){
            int j=2*k;
            if(j<N && less(arr,j,j+1)) j++;
            if(!less(arr,k,j)) break;
            exch(arr,k,j);
            k=j;
        }
    }
    //元素上浮
//    public static void swim(int[] arr,int k,int N){
//        while(k>1 && less(arr,k/2,k)){
//            exch(arr,k,k/2);
//            k = k/2;
//        }
//    }
    //比较方法
    private static Boolean less(int[] arr,int i,int j){
        if(arr[i-1]<arr[j-1]) return true;
        else return false;
    }
    //交换方法
    private static void exch(int[] arr,int i,int j){
        int temp = arr[i-1];
        arr[i-1] = arr[j-1];
        arr[j-1] = temp;
    }
}

8.计数排序

特性:stable sort、out-place sort。
最坏情况运行时间:O(n+k)
最好情况运行时间:O(n+k)

计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

8.1 算法步骤

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

8.2 代码

在这里插入代码片

9.桶排序

9.1 算法步骤

9.2代码

10.基数排序

10.1 算法步骤

10.2 代码

猜你喜欢

转载自blog.csdn.net/qq_30281559/article/details/89110190