常用的排序算法

114 阅读3分钟

「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战 介绍几种常用的排序方法,主要包括选择排序,冒泡排序,插入排序,希尔排序,归并排序,快速排序和基数排序。

1.选择排序

每遍历一次数组就找出未排好序那部分数组的最小值(最大值)的索引,然后将该索引位置的数与对应的位置的数交换,例如数组{5,1,2,3,6,4},从小到大排列,第一次排序则是找到最小值1,索引是1,则将数字1跟5交换即索引为1(第一次遍历最小值的索引)的数字与对应位置的索引为0的数进行交换得到{1,5,2,3,6,4},此时1被放在了正确的位置,以此类推,代码如下:

public class BubbleSort {
    static int[] sort(int[] arr){
        for (int i = arr.length-1; i >0 ; i--) {
            for (int j = 0; j < i; j++) {
                if (arr[j]>arr[j+1]) {
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        return arr;
    }
    public static void main(String[] args) {
        int[] arr = {2,3,5,8,4,7,6,9,1};
        System.out.println(Arrays.toString(sort(arr)));
    }
}

2.冒泡排序

相邻的两个数进行比较,把较大的数字进行后移,通过交换顺序来实现交换

public class BubbleSort {
    static int[] sort(int[] arr){
        for (int i = arr.length-1; i >0 ; i--) {
            for (int j = 0; j < i; j++) {
                if (arr[j]>arr[j+1]) {
                    int temp = arr[j];

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

3.插入排序

从索引为1开始,把索引为1的数插入到前面排好数字应的位置

public class InsertionSort {
    static int[] sort(int[] arr){
        for (int i = 1; i < arr.length; i++) {
            for (int j = i; j > 0; j--) {
                if (arr[j]<arr[j-1]){
                    int temp = arr[j];
                    arr[j] = arr[j-1];
                    arr[j-1] = temp;
                }
            }
        }
        return arr;
    }

以上三种为简单排序,冒泡排序和选择排序基本不用换,插入排序在样本较小且基本有序的情况下效率较高,时间复杂度都是O(n2),空间复杂度都是O(1)

4.希尔排序

希尔排序是改进的插入排序,每次确定一个间隔即,将数组用间隔划分成新的数组,分别进行插入排序,每次间隔排序完之后缩小间隔,然后再划分,再进行插入排序,最后缩小到间隔为1,进行最后一次排序。间隔是由Knuth数列来决定的,首项h=1,公式为h=3*h+1,此时h必须小于等于数组的三分之一;时间复杂度为O(n1.3),空间复杂度为O(1);

public class ShellSort {
    public static void main(String[] args) {
        int[] arr = {9, 6, 11, 3, 5, 12, 8, 7, 10, 15, 14, 4, 1, 13, 2};
        System.out.println(Arrays.toString(sort(arr)));
    }
    static int[] sort(int[] arr){
        int h = 1;
        while(h<=arr.length){
            h = h * 3 + 1;
        }
        for (int gap = h; gap>0 ; gap = (gap-1)/3) {   //Knuth序列确定间隔
            for (int i = gap; i < arr.length; i++) {
                for (int j = i; j >gap - 1 ; j = j - gap) {
                    if (arr[j]<arr[j-gap]){
                        int temp = arr[j];
                        arr[j] = arr[j-1];
                        arr[j-1] = temp;
                    }
                }
            }
        }


        return arr;
    }
}

5.归并排序

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。就是将原数组分为若干个子数组,分别对这些子数组进行排序,将一个数组分为左右两个,新建一个数组,挨个比较左右两个数组元素的大小,然后复制到一个新的数组(temp)里,最后再将数组temp排好的元素更新到原数组里,如下图:

代码如下:

public class MergeSort {
    /**
     *
     * @param arr 待排序的数组
     * @param leftPtr 左指针
     * @param rightPtr 右指针
     * @param rightBound 右边界
     */
    static void merge(int[] arr,int leftPtr, int rightPtr, int rightBound){
        int mid = rightPtr - 1;
        int[] temp = new int[rightBound-leftPtr+1];
        int i = leftPtr;
        int j = rightPtr;
        int k = 0;


        while(i <= mid && j <= rightBound){
            temp[k++] = arr[i] <= arr[j]?arr[i++]:arr[j++];
        }
        while(i <= mid){
            temp[k++] = arr[i++];
        }
        while(j <= rightBound){
            temp[k++] = arr[j++];
        }
        for (int l = 0; l < temp.length; l++) {
            arr[leftPtr + l] = temp[l];
        }
    }

    /**
     *
     * @param arr 待排序的数组
     * @param left 左边界
     * @param right 右边界
     */
    static void sort(int[] arr,int left, int right){
        if(left == right) return;

        int mid = left + (right-left)/2;
        //左边排序
        sort(arr,left,mid);
        //右边排序
        sort(arr,mid+1,right);

        merge(arr,left,mid+1,right);
    }
    public static void main(String[] args) {
        int[] arr = {1,3,5,7,9,2,4,6,8};
        sort(arr, 0, arr.length-1);
        System.out.println(Arrays.toString(arr));
    }
}

6.快速排序

快速排序就是选取一个数作为轴,把比轴大的元素放到轴后面,把比轴小的元素放到轴前面,这时轴的位置固定,但轴前后的元素顺序不对,这时以轴为分界线把数组分为左右两个在进行如上操作,递归进行,代码如下:

public class QuickSort {
    public static void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }


    public static int partition(int[] arr, int leftBound, int rightBound){
        int left = leftBound;
        int right = rightBound-1;
        int provid = arr[rightBound];
        while(left <= right){
            while(left <= right && arr[left] <= provid) left++;
            while(left <= right && arr[right] > provid) right--;
            if (left < right)swap(arr, left, right);
        }

        swap(arr,left,rightBound);
        //System.out.println(Arrays.toString(arr));
        return left;
    }

    public static int[]  sort(int[] arr, int leftBound, int rightBound){
        if (leftBound >= rightBound) return arr;
        int mid = partition(arr,leftBound,rightBound);
        sort(arr,leftBound,mid-1);
        sort(arr,mid+1,rightBound);
        return arr;
    }
    public static void main(String[] args) {
        int[] arr = {3,5,8,4,2,6,10,1};
        sort(arr,0,arr.length-1);
    }
}

7.计数排序

非比较排序,对数据源有要求(数据量大,且数据分布在一定的区域内)

新建一个数组(统计数组,数组的大小与分布的区域的大小一致),以数组的索引(索引只能从0开始,而数据源最小的可能不是0,索引值可以用数据源的数字减去该数据源最小的数)表示数据源里出现的数字,索引对应位置存储数字出现的次数,再新建一个累加数组,该数组负责记录数据数字出现在结果数组里最后的位置,再有累加数组得到结果数组;

import java.util.Arrays;

public class CountSort {
    public static void main(String[] args) {
        int[] arr = {3,3,1,4,2,5};

        sort(arr,6);

    }

    public static void sort(int[] arr, int nums) {
        int[] count =new int[nums];
        int[] result = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            //如果原数据不是从0开始,则arr[i]需要用arr[i]减去最小值来代替
            count[arr[i]]++; //得到统计数组
        }
        System.out.println(Arrays.toString(count));

        for (int i = 1; i < count.length; i++) {
            count[i] = count[i]+count[i-1]; //得到累加数组
        }
        System.out.println(Arrays.toString(count));

        for (int i = arr.length-1; i >= 0; i--) {
            //得到结果数组,arr[i]既是源数据,也是count的下标,count数组里的元素减一又是结果数组的下标
            //如果原数据不是从0开始,则arr[i]需要用arr[i]减去最小值来代替
            result[--count[arr[i]]] = arr[i];
        }
        System.out.println(Arrays.toString(result));
    }
}