常见排序算法

目录

1.直接插入排序

[1.1 代码思路总结](#1.1 代码思路总结)

[1.2 特性总结](#1.2 特性总结)

2.希尔排序

[2.1 代码思路总结](#2.1 代码思路总结)

[2.2 特性总结](#2.2 特性总结)

3.选择排序

[3.1 直接选择排序](#3.1 直接选择排序)

[3.1.1 代码思路总结](#3.1.1 代码思路总结)

[3.1.2 特性总结](#3.1.2 特性总结)

[3.2 堆排序](#3.2 堆排序)

[3.2.1 代码思路总结](#3.2.1 代码思路总结)

[3.2.2 特性总结](#3.2.2 特性总结)

4.交换排序

[4.1 冒泡排序](#4.1 冒泡排序)

4.2.快速排序

[4.2.1 Hoare法](#4.2.1 Hoare法)

[4.2.2 挖坑法](#4.2.2 挖坑法)

[4.2.3 快排优化](#4.2.3 快排优化)

[4.2.4 快排总结](#4.2.4 快排总结)

6.归并排序


1.直接插入排序

基本思想:将一个未排序的元素插入到一个已排序的序列中合适的位置,插入后仍保持有序

**实现思路:**我们通过观察上图思考,我们将6插入到已排好的有序序列中应该怎么做? 是不是从后往前或者从前往后进行依次比较来确定插入的位置,那我们对一个乱序的数组进行排序,一定是从前往后来依次进行排序,以该数组为例:

我们默认第一个元素是有序的(当只有一个元素时不需要排序),所以我们从第二个元素开始比较,将3与前面所有的数字进行比较,将3和7进行比较,此时我们就需要定义两个变量i和j来记录这两个数所在的下标值,j下标永远是i的前一个,因为要进行j--将i前面所有的元素进行依次比较,arr[j] > arr[i],此时我们就需要将7移动到3的位置,即:

然后我们进行j--,此时j<0说明当前i下标的值与前面所有的值进行了比较,arr[i]此时已经有序,但此时我们发现3呢?3被覆盖了,此时我们就需要定义一个变量tmp来记录j的值,当比较完后将tmp赋值给arr[j+1]即可,如图:

需要进行多次比较的情况:

arr[j] > arr[i] 所以将12赋值到i下标的位置,此时我们发现5已经被覆盖了,但是我们还没有将5依次与前面的数字一一比较大小来进行排序,所以tmp变量此时又要发挥作用来记录下最初i下标的值

另一种情况:j<0时

1.1 代码思路总结

  1. 从arr[i]开始排序,arr[j]默认是arr[i]的前一个元素
  2. 因为存在值覆盖问题,所以需要定义tmp变量来记录最初i的值
  3. j的取值必须满足j >= 0,j--依次与前面的元素进行比较,当j<0时说明arr[i]前面所有的元素已经比较完毕
  4. 因为i的值经过比较并赋值会发生改变,所以也是需要定义外部变量来记录最初i下标的值来与前面的值进行依次比较,此时分两种情况:
    ①arr[j] > tmp时,进行赋值,即:arr[j+1] = arr[j]
    ②arr[j] <= tmp时,说明当前已经有序的,不需要再进行排序,此时的有序也分两种 第一种直接break跳出循环即可,第二种arr[j+1] = tmp即可,此时我们统一写成第 二种即可,因为可以同时满足两种情况
  5. 考虑j<0的情况,arr[j+1] = tmp即可
java 复制代码
public class StraightInsertionSort {
    public static void main(String[] args) {
        int[] nums = {5, 3, 8, 4, 2};
        int n = nums.length;
        //往回进行比较
        //i表示待排序的数组元素
        for (int i = 1; i < n; i++) {
            //j表示已排序的数组元素
            int j = i-1;
            int key = nums[i];
            // 将arr[i]插入到已排序的子序列arr[0..i - 1]中
            while (j >= 0 && nums[j] > key) {
                nums[j + 1] = nums[j];
                j--;
            }
            nums[j + 1] = key;
        }
        System.out.println(Arrays.toString(nums));
    }
}
java 复制代码
public static void insertSort1(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            int j = i-1;
            int tmp = arr[i];
            for (; j >= 0; j--) {
                if(arr[j] > tmp) {
                    arr[j+1] = arr[j];
                }else {
                    //arr[j+1] = tmp;
                    break;
                }
            }
            arr[j+1] = tmp;
        }
    }

1.2 特性总结

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

2.希尔排序

希尔排序法又称缩小增量法。

基本思想 :将整个待排序的记录分割成若干子序列分别进行直接插入排序,待整个数组"基本有序"时,再进行一次直接插入排序

2.1 代码思路总结

实现思路 :给定一组乱序的数组后,我们对该数组中所有元素进行分组,对每一个组中的元素进行排序,我们需要定义一个增量gap,这里我们选择增量为length/2,缩小增量以gap=gap/2的方式来对乱序的数组进行预排序, 直到gap为1时数组趋近于有序。

再次使用插入排序即可,不断地预排序直到增量为1,再次使用直接插入排序时速度就会很快这就是希尔排序的缩小增量体现

举例:

所以我们直接在插入排序的基础上进行改造即可

  1. 初始增量gap为数组长度,通过gap=gap/2来进行缩小增量,直到gap为1时停止,此时写一个while循环即可
  2. 对直接插入排序进行改造即可,arr[i]的初始值变为gap,对应与之相比较的arr[j]下标变为i-gap
  3. 在进行多次比较时,每次下标j的值需要进行j -= gap来确定一组中元素的位置、
java 复制代码
public class ShellSort {
    public static void main(String[] args) {
        int[] nums = {9, 5, 1, 8, 3, 7, 4, 6, 2};
        int n = nums.length;
        for (int gap = n / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < n; i++) {
                int temp = nums[i];
                int j = i;
                while (j >= gap && nums[j - gap] > temp) {
                    nums[j] = nums[j - gap];
                    j -= gap;
                }
                nums[j] = temp;
            }
        }
        System.out.println(Arrays.toString(nums));
    }
}
java 复制代码
public static void shellSort(int[] arr) {
        int gap = arr.length;

        while (gap > 1) {
            gap /= 2;
            shell(arr,gap);
        }
    }
    public static void shell(int[] arr,int gap) {
        for (int i = gap; i < arr.length; i++) {
            int j = i-gap;
            int tmp = arr[i];
            for (; j >= 0; j -= gap) {
                if(arr[j] > tmp) {
                    arr[j+gap] = arr[j];
                }else {
                    //arr[j+gap] = arr[j];
                    break;
                }
            }
            arr[j+gap] = tmp;
        }
    }

2.2 特性总结

  1. 希尔排序是对直接插入排序的优化。
  2. gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很 快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在一些书中给出的希尔排 序的时间复杂度都不固定,我们默认为
  4. 稳定性:不稳定

3.选择排序

3.1 直接选择排序

基本思想 :每次从待排序的数据元素中选出最小(或最大)的一个元素存放在序列的起始位置,知道全部待排序的数据元素排完;实际中我们玩扑克牌时,就用了插入排序的思想。

3.1.1 代码思路总结

当插入第i(i>=1)个元素时,前面的array[0],array[1..,array[i-1]已经排好序,此时用array[]的排序码与array[1],array[i-2]....的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移

java 复制代码
    public static void swap(int[] arr,int n1,int n2){
        int tmp=arr[n1];
        arr[n1]=arr[n2];
        arr[n2]=tmp;
    }



    public static void choiceSort(int[] arr){
        int len=arr.length;
        for (int i = 0; i < len; i++) {
            int tmp=arr[i];
            int index=i;
            for(int j=i+1;j<len;j++){
                if(arr[j]<tmp){
                    index=j;
                    tmp=arr[j];
                }
            }
            swap(arr,i,index);
        }
    }

3.1.2 特性总结

直接插入排序的特性总结:

1.元素集合越接近有序,直接插入排序算法的时间效率越高

2.时间复杂度:O(N^2)

3.空间复杂度:O(1),它是一种稳定的排序算法

4.稳定性:稳定

3.2 堆排序

3.2.1 代码思路总结

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。排升序要建大堆,排降序建小堆。

  1. 首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
  2. 将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
  3. 将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
java 复制代码
    public void creatBigHeap(int[] arr) {
        for (int parent = (arr.length-1-1)/2; parent >= 0; parent--) {
            siftDown(parent,arr,arr.length);
        }
    }
    public void siftDown(int parent,int[] arr,int end) {
        int child = 2 * parent + 1;
        while (child < end) {
            if (child + 1 < end && arr[child] < arr[child + 1]) {
                child++;
            }
            //此时child位最大孩子
            if (arr[child] > arr[parent]) {
                swap(arr, child, parent);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }

    public void heapSort(int[] arr) {
        creatBigHeap(arr);
        int end = arr.length-1;
        while (end >= 0) {
            swap(arr,0,end);
            siftDown(0,arr,end);
            end--;
        }
    }

3.2.2 特性总结

堆排序使用堆来选数,效率就高了很多

时间复杂度:O(N*logN)

空间复杂度:(1)

稳定性:不稳定

4.交换排序

4.1 冒泡排序

java 复制代码
public void bubbleSort(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            boolean flag = false;
            for (int j = 0; j < arr.length-i; j++) {
                if (arr[j] > arr[j+1]) {
                    swap(arr,j,j+1);
                    flag = true;
                }
            }
            if (!flag) {
                break;
            }
        }
    }
  1. 时间复杂度:O(N^2)
  2. 空间复杂度:O(1)
  3. 稳定性:稳定

4.2.快速排序

4.2.1 Hoare法

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,

基本思想:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有 元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
实现思路 :基于快排的基本思想,我们需要定义一个基准值,我们这里将给定数组的第一个元素作为基准值baseValue,然后我们需要调整基准值的位置来达到基准值的左侧为小于基准值的元素,右侧为大于基准值的元素

定义left变量指向数组的第一个元素,right变量指向数组的最后一个元素,先对right进行操作,right通过--操作向前进行遍历来找到比基准值baseValue小的元素;再对left进行操作,left通过++操作向后遍历来找到比基准值baseValue大的元素,找到后进行交换,在我们寻找的过程中left<right来限制下标越界问题,我们需要在找到一次left和right下标后进行一次交换,这就意味着我们需要一个大的循环来包含寻找left和right的两个小循环,外层循环的限制条件依旧是left<right

我们需要单独对left和right相遇时的情况进行单独处理,但由于left下标是不断变化的所以我们需要定义一个变量来记录初始left的值

先走right后走left的原因?

array[right] >= baseValue这里能不能不取等于号?

java 复制代码
    public void quickSortHoare(int[] arr){
        quickHoare(arr,0,arr.length-1);
    }

    private void quickHoare(int[] arr,int left,int right) {
        if(left >= right) {
            return;
        }
        //找基准值所在位置
        int mid = partitionHoare(arr,left,right);
        //递归左树
        quickHoare(arr,left,mid-1);
        //递归右树
        quickHoare(arr,mid+1,right);
    }
    private int partitionHoare(int[] arr,int left,int right) {
        int i = left;
        //找到基准值mid的下标
        int baseValue = arr[left];//默认基准值为arr[left]

        while (left<right) {
            while (left<right && arr[right] >= baseValue) {
                right--;
            }
            while (left<right && arr[left] <= baseValue) {
                left++;
            }
            swap(arr,left,right);
        }
        //此时left和right相遇
        swap(arr,left,i);
        return left;
    }

    private void swap(int[] arr,int left,int right) {
        int tmp = arr[left];
        arr[left] = arr[right];
        arr[right] = tmp;
    }

4.2.2 挖坑法

实现思路: 将arr[left]作为第一个坑位并作为tmp变量,从right开始找到小于tmp的就填补上一个坑位,自己又变成了坑位,再找left大于tmp的来填补上一个坑位,依此循环,直到相遇用tmp来补最后一个坑位

java 复制代码
private static int partition (int[] arr,int left,int right) {
        int tmp = arr[left];
        while(left < right) {
            while(left < right && arr[right] >= tmp) {
                right--;
            }
            arr[left] = arr[right];

            while(left < right && arr[left] <= tmp) {
                left++;
            }
            arr[right] = arr[left];
        }
        //left right相遇
        arr[left] = tmp;
        return left;
    }

4.2.3 快排优化

通过减少递归的次来进行优化,即让左右树均衡,我们需要将中位数作为基准值

java 复制代码
public void quickSort(int[] array) {
        quick(array,0,array.length-1);
    }
    /**
     * 快排的优化,减少递归次数
     * @param arr
     * @param left
     * @param right
     */
    public void quick(int[] arr,int left,int right){
        if(left >= right) {
            return;
        }
        //找到中位数下标,进而实现让中位数作为基准值
        int index = getMid(arr,left,right);
        swap(arr, index, left);
        int par = partitionHoare(arr,left,right);
        quickHoare(arr,left,par-1);
        //递归右树
        quickHoare(arr,par+1,right);
    }

    public int getMid(int[] arr,int left,int right) {
        int mid = (left+right)/2;
        if(arr[left] < arr[right]) {
            if(arr[mid] <arr[left]) {
                return left;
            }else if(arr[mid] > arr[right]) {
                return right;
            }else {
                return mid;
            }
        }else {
            if(arr[mid] < arr[right]) {
                return right;
            }else if(arr[mid] > arr[left]) {
                return left;
            }else {
                return mid;
            }
        }
    }

4.2.4 快排总结

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

6.归并排序

归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。

  1. 将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。

  2. 将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。

java 复制代码
public class MergeSort {
    public static int[] mergeSort(int[] nums, int l, int h) {
        if (l == h) {
            return new  int[]{nums[l]};
        }
        int mid = l + (h - l) / 2;
        int[] leftArr = mergeSort(nums, l, mid);
        int[] rightArr = mergeSort(nums, mid + 1, h);
        //存储合并后的有序数组
        int[] newNum = new int[leftArr.length+ rightArr.length];
        int m = 0, i = 0, j = 0;
        while (i < leftArr.length && j < rightArr.length) {
            newNum[m++] = leftArr[i] < rightArr[j] ? leftArr[i++] : rightArr[j++];
        }
        //处理剩余元素
        while (i < leftArr.length) {
            newNum[m++] = leftArr[i++];
        }
        while (j < rightArr.length) {
            newNum[m++] = rightArr[j++];
        }
        return newNum;
    }
    public static void main(String[] args) {
        int[] nums = new int[]{9, 8, 7, 6, 5, 4, 3, 2, 10};
        //要接收返回值
        int[] ints = mergeSort(nums, 0, nums.length - 1);
        System.out.println(Arrays.toString(ints));
    }
  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定
相关推荐
Slow菜鸟2 小时前
Java后端常用技术选型 |(四)微服务篇
java·分布式
武子康2 小时前
Java-168 Neo4j CQL 实战:WHERE、DELETE/DETACH、SET、排序与分页
java·开发语言·数据库·python·sql·nosql·neo4j
Filotimo_2 小时前
SpringBoot3入门
java·spring boot·后端
通往曙光的路上3 小时前
SpringIOC-注解
java·开发语言
一 乐3 小时前
校园墙|校园社区|基于Java+vue的校园墙小程序系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·小程序
阿巴~阿巴~3 小时前
IPv4地址转换函数详解及C++容器安全删除操作指南
linux·服务器·c++·网络协议·算法·c++容器安全删除操作·ipv4地址转换函数
TT哇3 小时前
【面经 每日一题】面试题16.25.LRU缓存(medium)
java·算法·缓存·面试
青云交3 小时前
Java 大视界 -- 基于 Java 的大数据联邦学习在跨行业数据协同创新中的实践突破
java·分布式计算·隐私保护·apache flink·大数据联邦学习·跨行业数据协同·安全通信
合作小小程序员小小店3 小时前
桌面开发,在线%考试管理%系统,基于eclipse,java,swing,mysql数据库。
java·数据库·mysql·eclipse·jdk