数据结构--排序

排序


一、排序种类

1.交换排序
(1)冒泡排序
(2)快速排序
2.插入排序
(1)直接插入排序
(2)希尔排序
3.选择排序
(1)简单选择排序
(2)堆排序(与二叉树有关,暂时先不实现)
4.归并排序
5.基数排序

二、排序实现

1.冒泡排序

思想:(1)对于每两个相邻的元素进行交换,如果是从小到大排序,则每次将最大的交换到最后一位,需要执行(array.length-1)次,每次需要交换(array.length-i-1)次,其中i属于[0,array.length-1);如果是从大到小排序,则是将最小的交换到后面。
(2)若是某一次不需要交换了,则说明不需要执行后面的交换次,可以直接跳出,实现对冒泡排序的优化。

代码如下(示例):

// 冒泡排序
    public void BubbleSort(int[] arr){
        boolean flag = true;
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - i - 1; j++) {
                if(arr[j] > arr[j+1]){ //从小到大排序
                    flag = false;
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
            System.out.println("第"+(i+1)+"趟排序后的数组:"+ Arrays.toString(arr));
            // 优化 -- 如果已经有序则直接跳出
            if (flag){
                break;
            }
        }
    }

2.选择排序

思路:如果是从小到大排序,则将array[i]与min(arr[j])(j = [i+1,array.length-1])进行交换,即每一次找出(i+1)到(array.length)的最小值,与array[i]交换。
代码如下(示例):

// 选择排序
    public void selectSort(int[] arr){
        for (int i = 0; i < arr.length - 1; i++) {
            int min = arr[i];
            int minIndex = i;
            boolean flag = false;
            for (int j = i+1; j < arr.length; j++) {
                if(min > arr[j]){
                    flag = true;
                    min = arr[j];
                    minIndex = j;
                }
            }
            if(flag){
                int temp = arr[i];
                arr[i] = min;
                arr[minIndex] = temp;
            }
            System.out.println("第"+(i+1)+"趟排序后的数组:"+ Arrays.toString(arr));
        }
    }

3.插入排序

思想:(1)首先将数组看成一个有序表和一个无序表,有序表中暂存第一个数,其他的都存在无序表中;
(2)依次取出无序表中的每一个数与有序表中的数进行比较,将其插入到一个适合的位置。

代码如下(示例):

 // 插入排序
    public void insertSort(int[] arr){
//        for (int i = 0; i < arr.length - 1; i++) {
//            for (int j = i+1; j < arr.length; j++) {
//                if(arr[i] > arr[j]){ //从小到大排序
//                    int temp = arr[i];
//                    arr[i] = arr[j];
//                    arr[j] = temp;
//                }
//            }
//            System.out.println("第"+(i+1)+"趟排序后的数组:"+ Arrays.toString(arr));
//        }

        for (int i = 1; i < arr.length; i++) {
            int insertVal = arr[i];
            int insertIndex = i - 1;
            while (insertIndex >= 0 && insertVal < arr[insertIndex]){
                arr[insertIndex +1] = arr[insertIndex];
                insertIndex--;
            }
            arr[insertIndex + 1] = insertVal;
            System.out.println("第"+i+"趟排序后的数组:"+ Arrays.toString(arr));
        }
    }

4.希尔排序

思想:gap = arrar.length/2
(1)每一次将数组分成gap = gap/2组;
(2)每一组的i=gap与j=i-gap比较,如果array[i]比较大,则交换,就这样一直进行到i=array.length。
(3)直到gap不能再分的时候退出。

代码如下(示例):

    //希尔排序(交换法)
    public void donaldShell1(int[] arr){
        int count = 0;
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            count++;
            for (int i = gap; i < arr.length ; i++) {
                for (int j = i - gap; j >= 0; j -= gap) {
                    if(arr[j] > arr[j + gap]){
                        int temp = arr[j];
                        arr[j] = arr[j + gap];
                        arr[j + gap] = temp;
                    }
                }
            }
            System.out.println("第"+count+"趟排序后的数组:"+ Arrays.toString(arr));
        }
    }

    //希尔排序(移位法)
    public void donaldShell2(int[] arr){
        int count = 0;
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            count++;
            for (int i = gap; i < arr.length ; i++) {
                int j = i;
                int temp = arr[j];
                if(arr[j] < arr[j - gap]){
                    while (j - gap >= 0 && temp < arr[j - gap]){
                        arr[j] = arr[j - gap];
                        j -= gap;
                    }
                    arr[j] = temp;
                }
            }
            System.out.println("第"+count+"趟排序后的数组:"+ Arrays.toString(arr));
        }
    }

5.快速排序

思想:(1)确定一个数组元素,将小于它的放到左边,大于他的放到右边;
(2)再根据递归的思想再对左边和右边进行快速排序。

代码如下(示例):

public void QuickSort(int[] arr, int left, int right){
        int l = left;
        int r = right;
        int middle = arr[(left + right) / 2]; //中间值
        while (l < r){

            // 寻找数组左边大于中间数的元素
            while (arr[l] < middle){
                l += 1;
            }
            // 寻找数组右边大于中间数的元素
            while (arr[r] > middle){
                r -= 1;
            }
            // 如果左边的数都小于中间结点,右边的数都大于中间结点,则退出
            if( l >= r){
                break;
            }
            // 交换
            int temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;
        }

        // 没有的话会出现栈溢出
        if(l == r){
            l += 1;
            r -= 1;
        }

        // 对左边进行快速排序
        if(left < r){
            QuickSort(arr,left,l);
        }

        // 对右边进行快速排序
        if(right > l){
            QuickSort(arr,l,right);
        }
    }

6.归并排序

思想:(1)合并过程:将数组的两个有序子序列合并到一个临时数组中;然后将其中一个子序列剩余的元素合并到临时数组中;将临时数组的值赋给原数组。
(2)分解过程:将其左递归分解;再进行右递归分解;然后进行合并。

代码如下(示例):

 //归并排序
    public void mergeSort(int[] arr, int left, int right, int[] temp) {
        if (left < right) {
            int mid = (left + right) / 2;
            // 左递归进行分解
            mergeSort(arr, left, mid, temp);
            // 右递归进行分解
            mergeSort(arr, mid + 1, right, temp);
            // 合并
            merge(arr, left, mid, right, temp);
        }
    }
    public void merge(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left;
        int j = mid + 1;
        int t = 0;

        // 1、将两个子序列合并到一个临时数组中
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[t] = arr[i];
                i += 1;
                t += 1;
            } else {
                temp[t] = arr[j];
                j += 1;
                t += 1;
            }
        }

        // 2、将两个子序列中剩余的元素直接复制到temp中
        while (i <= mid) {
            temp[t] = arr[i];
            i += 1;
            t += 1;
        }
        while (j <= right) {
            temp[t] = arr[j];
            j += 1;
            t += 1;
        }

        // 3、将temp的数复制给arr
        t = 0;
        int tempLeft = left;
        System.out.println("tempLeft=" + tempLeft + ",right=" + right);
        while (tempLeft <= right) {
            arr[tempLeft] = temp[t];
            t += 1;
            tempLeft += 1;
        }
    }

7.基数排序

思想:(1)创建一个(0-9)的二维数组作为桶,进行存放每一轮比较的数据;
(2)每一轮对数组中的每个数的(个/十/百…(取决于数组中的最大数位数的个数))进行排序,按照大小放入二维数组中,其中可以利用一个一维数组记录一下个数;
(3)每一轮都将其从桶中按桶的顺序取出得到新的序列,其中数组的最大数有几位数总共就需要执行几次,最后一次就可以得到最终结果。

代码如下(示例):

 //基数排序
    public void radixSort(int[] arr) {
        int[][] bucket = new int[10][arr.length];
        int[] bucketElementCounts = new int[10]; // 记录每个桶中元素的个数

        // 找出数组最大数的位数
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if(max < arr[i]){
                max = arr[i];
            }
        }
        int maxLength = (max + "").length();
        for (int l = 0 , n = 1; l < maxLength; l++, n *= 10) {
            // 每一轮,将每个元素的个位数取出来,放入桶中
            for (int i = 0; i < arr.length; i++) {
                int index = arr[i] / n % 10;
                bucket[index][bucketElementCounts[index]] = arr[i];
                bucketElementCounts[index]++;
            }
            // 按照桶的顺序将数据取出放入原来的数组中
            int i = 0;
            while (i < arr.length) {
                for (int j = 0; j < 10; j++) {
                    if(bucketElementCounts[j] != 0){
                        for (int k = 0; k < bucketElementCounts[j]; k++) {
                            arr[i++] = bucket[j][k];
                        }
                    }
                    // 第一轮处理后,需要将每个bucketElementCounts[k] = 0;
                    bucketElementCounts[j] = 0;
                }
            }
            System.out.println("第" + (l+1) + "趟排序后的数组:" + Arrays.toString(arr));
        }
    }

总结

冒泡排序(稳定)、选择排序(不稳定)、插入排序(稳定)的平均时间复杂度是:O(n^2);

希尔排序(不稳定)、归并排序(稳定)、快速排序(不稳定)的平均时间复杂度是:O(n*(log n));

基数排序(稳定)的平均时间复杂度是:O(n*k)。

上一篇:经典排序算法(三) —— Shell Sort 希尔排序


下一篇:希尔排序(Java) 2022.01.01