排序

1、排序算法介绍

  • 排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程
  • 排序的分类
    • 内部排序:指将需要处理的所有数据都加载到内部存储器中进行排序
    • 外部排序法:数据量过大,无法全部加载到内存中,需要借助外部存储进行排序

image.png

2、冒泡排序

  • 冒泡排序(Bubble Sorting)的基本思想:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部。
package sort;

//冒泡排序,时间复杂度(n^2)
public class BubbleSort {
    public static void main(String[] args) {
        int arr[] = {3,9,-1,10,-2};

        int temp = 0; //临时变量

        for (int i=0;i<arr.length-1;i++){
            for (int j=0;j<arr.length-1-i;j++){
                if (arr[j]>arr[j+1]){
                    temp = arr[j+1];
                    arr[j+1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }
}
  • 冒泡排序优化,如果在某趟排序中,没有发生一次交换,可以提前结束冒泡排序
package sort;

//冒泡排序,时间复杂度(n^2)
public class BubbleSort {
    public static void main(String[] args) {
        int arr[] = {3,9,-1,10,-2};

        int temp = 0; //临时变量
        boolean flag = false;  //表示变量,表示是否进行过交换
        for (int i=0;i<arr.length-1;i++){
            for (int j=0;j<arr.length-1-i;j++){
                if (arr[j]>arr[j+1]){
                    flag = true;
                    temp = arr[j+1];
                    arr[j+1] = arr[j];
                    arr[j] = temp;
                }
            }
            System.out.println("趟数");

            if (flag == false){   //在一趟排序中,一次交换都没有发生过
                break;
            }else {
                flag = false;  //重置flag,进行下次判断
            }
        }
    }
}

3、选择排序

  • 选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一个元素,再依规定交换位置达到排序的目的
  • 选择排序思路
    • 第一次从arr[0]-arr[n-1]中选取最小值,与arr[0]交换,第二次从arr[1]-arr[n-1]中选取最小值,与arr[1]交换,第三次从arr[2]-arr[n-1]中选取最小值,与arr[2]交换,…,第i次从arr[i-1]-arr[n-1]中选取最小值,与arr[i-1]交换,…, 第n-1次从arr[n-2]-arr[n-1]中选取最小值,与arr[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
package sort;


//选择排序,时间复杂度O(n^2)
public class SelectSort {
    public static void main(String[] args) {
        int arr[] = {101,34,119,1,-1,88,24};

        selectSot(arr);

        for (int i=0;i<arr.length;i++){
            System.out.print(arr[i]+" ");
        }
    }

    //选择排序
    public static void selectSot(int[] arr){
        for (int i=0;i<arr.length-1;i++){
            int minIndex = i;
            int min = arr[i];
            for (int j=i+1;j<arr.length;j++){
                if (min > arr[j]){
                    min = arr[j];
                    minIndex = j;
                }
            }

            if (minIndex != i ){
                arr[minIndex] = arr[i];
                arr[i] = min;
            }
        }
    }
}

4、插入排序

  • 插入式排序属于内部排序法,是对欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的
  • 排序思想
    • 把 n 个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含 n-1 个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表
package sort;

//插入排序 O(n^2)
public class InsertSort {
    public static void main(String[] args) {
        int[] arr = {101,34,119,1,-1,88,24};
        insertSort(arr);

        for (int i=0;i<arr.length;i++){
            System.out.print(arr[i]+" ");
        }
    }

    public static void insertSort(int[] 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;
        }
        
    }
}

5、希尔排序

  • 希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一种更搞笑的版本,也称为缩小增量排序
  • 希尔排序基本思想
    • 希尔排序是把记录按照下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含得关键词越来越多,当增量减至1时,整个文件恰好分成一组,算法便终止

image.png

  • 交换法
package sort;

//希尔排序
public class ShellSort {
    public static void main(String[] args) {
        int arr[] = {5,4,3,2,1};
        shellSort(arr);

        for (int i=0;i<arr.length;i++){
            System.out.print(arr[i]+" ");
        }
    }

    //希尔排序
    public static void shellSort(int[] arr){
        int temp = 0;

        for (int gap = arr.length / 2;gap > 0;gap /= 2){  //gap 步长
            for (int i =gap;i<arr.length;i++){
                for (int j = i-gap;j>=0;j-=gap){
                    if (arr[j]>arr[j+gap]){
                        temp = arr[j];
                        arr[j] = arr[j+gap];
                        arr [j+gap] = temp;
                    }
                }
            }
        }
        /* 
           for (int i =1;i<arr.length;i++){
                for (int j = i-1;j>=0;j--){
                    if (arr[j]>arr[j+1]){
                        temp = arr[j];
                        arr[j] = arr[j+1];
                        arr [j+1] = temp;
                    }
                }
            }
        */
    }
}

  • 移位法
    //对交换式的希尔排序进行优化 -> 移位法
    public static  void shellSort2(int[] arr){
        for (int gap = arr.length / 2; gap > 0;gap /= 2 ){
            //从第gap个元素开始,逐个对其所在的组进行直接插入排序
            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;
                    }

                    //当退出while后,就给temp找到插入的位置
                    arr[j] = temp;
                }
            }
        }

    }

6、快速排序

  • 快速排序(Quicksort)是对冒泡排序的一种改进。
  • 基本思想
    • 通过一趟排序将要排序的数据分割成独立的两个部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

image.png

package sort;


import java.util.Arrays;

//快速排序
public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {-9,78,0,23,-267,70};

        quickSort(arr,0,arr.length-1);

        System.out.println(Arrays.toString(arr));
    }

    //快速排序
    public static void quickSort(int[] arr,int left,int right){
        int l = left;  //左下标
        int r = right; //右下标

        int pivot = arr[(l+r) /2];  //中轴值

        int temp = 0;  //临时变量,用于交换时使用
        //while循环的目的让比pivot值小的放在左边,比pivot值大的放在右边
        while( l < r){
            //在pivot左边一直找,找到大于等于pivot的值,才退出
            while(arr[l] < pivot){
                l += 1;
            }

            //在pivot右边一直找,找到小于等于pivot的值,才退出
            while (arr[r] > pivot){
                r -= 1;
            }

            //如果 l >= r 说明pivot 的左右两边的值,已经按照左边全部是小于等于pivot
            //的值,右边全部是大于等于pivot的值
            if (l >= r){
                break;
            }

            //交换
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;

            //如果交换完后发现arr[l] == pivot r--,前移
            if (arr[l] == pivot){
                r -= 1;
            }

            //如果交换后,发现这个arr[r] == pivot值 r++,后移
            if (arr[r]  == pivot){
                l += 1;
            }
        }

        //如果 l == r,必须l++ r-- ,否则会出现栈溢出
        if (l == r){
            l += 1;
            r -=1;
        }
        //向左递归
        if (left < r){
            quickSort(arr,left,r);
        }

        //向右递归
        if (right > l){
            quickSort(arr,l,right);
        }
    }
}

7、归并排序

  • 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段将分的阶段的到的答案“修补”在一起,即分而治之)

image.png

  • 治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图的最后一次合并,要将 【4,5,7,8】和【1,2,3,6】两个已经有序的子序列,合并为最终序列【1,2,3,4,5,6,7,8】,实现步骤如下图

image.png

package sort;

import java.util.Arrays;

//归并排序
public class MergerSort {
    public static void main(String[] args) {
        int arr[] = {8,4,5,7,1,3,6,2};
        int temp[] = new int[arr.length];  //归并排序需要一个额外的空间
        mergeSort(arr,0,arr.length-1,temp);
        System.out.println(Arrays.toString(arr));
    }

    //分+和

    public static 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);
        }
    }

    //合并
    /**
     * @param arr 排序的原始数组
     * @param left 左边有序序列的初始索引
     * @param mid 中间索引
     * @param right 右边索引
     * @param temp 临时数组
     */

    public static void merge(int arr[],int left,int mid,int right,int[] temp){
        int i = left; //初始化i ,左边有序序列的初始索引
        int j = mid + 1;  //初始化j,右边有序序列的初始索引
        int t = 0;   //初始化t,指向temp数组的当前索引

        //先把左右两边有序的数据,按照规则填充到temp数组,直到左右两边有序序列有一边处理完毕
        while (i <= mid && j <= right){

            //如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
            //即将左边的当前元素,填充到temp数组中,temp下标t++, 左边数组下标 i++
            if (arr[i] <= arr[j]){
                temp[t] = arr[i];
                i++;
                t++;
            }else{
                //如果右边的有序序列的当前元素,小于左边有序序列的当前元素
                //即将右边的当前元素,填充到temp数组中,temp 下标 t++,右边数组下标 j++
                temp[t] = arr[j];
                j++;
                t++;
            }
        }
        //把有剩余数据的一边的数据依次填充到temp
        while (i <= mid){   //左边的有序序列还有剩余的元素,就全部填充到temp
            temp[t] = arr[i];
            t++;
            i++;
        }

        while(j <= right){   //右边的有序序列还有剩余的元素,就全部填充到temp
            temp[t] = arr[j];
            t++;
            j++;
        }


        //将temp数组的元素拷贝到arr
        //并不是每次都拷贝所有
        t = 0;
        int tempLeft = left;
        while (tempLeft <= right){
            arr[tempLeft] = temp[t];
            t++;
            tempLeft++;
        }
    }
}

8、基数排序

  • 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或 bin sort,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用
  • 基数排序法是属于稳定性的排序,基数排序法是效率高的稳定排序法
  • 基数排序(Radix Sort)是桶排序的扩展
  • 基数排序是1887年赫尔曼.何乐礼发明的。实现方式:将整位数切成不同的数字,然后按每个位数分别比较
  • 基数排序的基本思想
  • 将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样最低位排序一直到最高位排序完成以后,数列就变成一个有序序列
package sort;

import java.util.Arrays;

//基数排序
public class RadixSort {
    public static void main(String[] args) {
        int arr[] = {53,3,542,748,14,214};
        radixSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    //基数排序方法
    public static void radixSort(int[] arr){

        //得到数组中最大数的位数
        int max = arr[0];
        for (int i=1;i<arr.length;i++){
            if (arr[i] > max){
                max = arr[i];
            }
        }

        //得到最大数的位数
        int maxLength = (max+"").length();

        //定义一个二维数组,表示10个桶,每个桶就是一个一维数组
        /***
         * 二维数组包含10个一维数组
         * 为了防止在放入数的时候,数据溢出,则每一个数组(桶)大小定位arr.length
         * 基数排序是使用空间换时间的算法
         */
        int[][] bucket = new  int[10][arr.length];


        //为了记录每个桶中,实际存放了多少个数据,定义一个一维数组来记录各个桶每次放入数据的个数

        int[] bucketElementCounts = new  int[10];

        for (int i=0 , n=1;i<maxLength;i++,n *= 10){
            //针对每个元素的对应位进行排序处理,个位,十位,百位...
            for (int j =0;j<arr.length;j++){
                //取出每个元素的对应位的值
                int digitOfElement = arr[j] / n % 10;
                //放入对应的桶中
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                bucketElementCounts[digitOfElement]++;
            }
            //按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
            int index = 0;
            //遍历每一个桶,并将桶中的数据放入元数组
            for (int k = 0;k<10;k++){
                //如果桶中有数据
                if(bucketElementCounts[k] != 0){
                    for (int l =0;l<bucketElementCounts[k];l++){
                        //取出元素放入arr
                        arr[index++] = bucket[k][l];
                    }
                }
                //第i+1轮处理后,需要将每个bucketElementCount[k] = 0
                bucketElementCounts[k] = 0;
            }
        }
    }
}

9、堆排序

//堆排序
public class HeapSort {
    public static void main(String[] args) {
        //要求将数组进行升序排序
        int arr[] = {4,6,8,5,9};
        heapSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    //编写堆排序的方法
    public static void heapSort(int arr[]){
        //将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆
        for (int i = arr.length / 2 -1;i>=0;i--){
            adjustHeap(arr,i,arr.length);
        }
        //将堆顶元素与末尾元素交换,将最大元素沉到数组末端
        //重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前元素,反复执行调整+交换步骤 直到整个序列有序
        int temp;
        for (int j = arr.length-1;j>0;j--){
            //交换
            temp = arr[j];
            arr[j] = arr[0];
            arr[0] = temp;
            adjustHeap(arr,0,j);
        }

    }

    /**
     * 将一个数组(二叉树),调整成一个大顶堆
     * @param arr    待调整的数组
     * @param i      表示非叶子节点在数组中的索引
     * @param length 表示对多少个元素进行调整
     */
    public static void adjustHeap(int arr[],int i,int length){
        //先取出当前元素的值保存在临时变量
        int temp = arr[i];
        //开始调整, (i * 2 + 1)i的左子节点
        for (int k = i * 2 +1;k<length;k = k * 2 + 1){
            if (k+1 < length && arr[k] < arr[k+1]){  //左子节点的值小于右子节点的值
                k++;  // k 指向右子节点
            }
            if (arr[k] > temp){ //如果子节点大于父节点
                arr[i] = arr[k]; //把较大的值赋给当前节点
                i = k; // i 指向 k,继续循环比较
            }else {
                break;
            }
        }
        //当for循环结束后,已经将以i为父节点的数的最大值,放在了最顶上
        arr[i] = temp; //将temp值放到调整后的位置
    }
}

10、排序算法时间复杂度

  • 稳定 :如果 a 原本在 b 前面,而 a=b 排序之后 a 仍然在 b 的前面
  • 不稳定 :如果 a 原本在 b 前面,而 a=b 排序之后 a 可能会在 b 的后面
  • 内排序 所有排序操作都在内存中完成
  • 外排序 :由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传传输才能进行
  • 时间复杂度 :一个算法执行所耗费的时间
  • 空间复杂度 :运行完一个程序所需内存的大小
  • n :数据规模
  • k :“桶”的个数
  • In-place :不占用额外内存
  • Out-place :占用额外内存
排序算法 平均时间复杂度 最好情况 最坏情况 空间复杂度 排序方式 稳定性
冒泡排序 O(n^2) O(n) O(n^2) O(1) In-place 稳定
选择排序 O(n^2) O(n^2) O(n^2) O(1) In-place 不稳定
插入排序 O(n^2) O(n) O(n^2) O(1) In-place 稳定
希尔排序 O(n log n) O(n log^2 n) O(n log^2 n) O(1) In-place 不稳定
归并排序 O(n log n) O(n log n) O(n log n) O(n) Out-place 稳定
快速排序 O(n log n) O(n log n) O(n^2) O(log n) In-place 不稳定
堆排序 O(n log n) O(n log n) O(n log n) O(1) In-place 不稳定
计数排序 O(n + k) O(n + k) O(n + k) O(k) Out-place 稳定
桶排序 O(n + k) O(n + k) O(n^2) O(n + k) Out-place 稳定
基数排序 O(n x k) O(n x k) O(n x k) O(n + k) Out-place 稳定
posted @ 2022-11-06 22:37  youmo~  阅读(32)  评论(0编辑  收藏  举报