常见8大排序算法详解

本文详细介绍了八大排序算法的思路和Java代码实现,包括冒泡排序、选择排序、插入排序、希尔排序、快速排序、堆排序、归并排序和基数排序(桶排序)。这些排序算法各有特点,适用于不同的场景,如冒泡排序适合小规模数据,快速排序则在平均情况下效率较高。
摘要由CSDN通过智能技术生成

目录

常见8大排序算法

冒泡排序

思路

代码

选择排序

思路

代码

插入排序

思路

代码

希尔排序

思路

代码

快速排序

思路

演示图

代码

堆排序

思路

1.构建大顶堆

2.进行堆排序

代码

归并排序

思路

1.合并两个有序部分

2.递归将数组分成两份,进行合并

代码

基数排序(桶排序)

思路

代码

负数的问题


常见8大排序算法

分别是冒泡排序、选择排序、插入排序、希尔排序、快速排序、堆排序、归并排序、基数排序(桶排序)

冒泡排序

思路

n个数字从小到大排序,每个数和它后面的数比较,小的放前面,大的放后面,依次执行,这样一轮下来就能将一个最大的放到最后。执行n-1轮就能全部排序完。

代码

/**
 * @author 康有为
 * 冒泡排序
 */
public class BubbleSort {
    public void bubbleSort(int[] arr){
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1; j++) {
                if (arr[j] > arr[j+1]){
                    int index = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = index;
                }
            }
        }
    }
}

选择排序

思路

n个数字从小到大排序,每轮选择一个最小的放到最前面,执行n-1轮 

代码

package 排序;

/**
 * @author 康有为
 * 选择排序
 */
public class SelectSort {
    public void selectSort(int[] arr){
        for (int i = 0; i < arr.length - 1; i++) {
            int min = i;
            //找出一个最小的
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[j] < arr[min]){
                    min = j;
                }
            }
            //与第一个交换
            int index = arr[i];
            arr[i] = arr[min];
            arr[min] = index;
        }
    }
}

插入排序

思路

        类似打斗地主时,摸一张插入到手里排序,再摸一张插入到手里排序。

        n个数字从小到大排序,将数字分成未排序的和已排序的两部分,每次 从未排序的中拿出一个,放入到已排序中进行排序:如果比前面的大,那直接结束,如果比前面的小,就与之交换位置,再与前面的交换,一直到头,一轮结束。

代码

/**
 * @author 康有为
 * 插入排序
 */
public class InsertSort {
    public void insertSort(int[] arr){
        for (int i = 1; i < arr.length; i++) {
            for (int j = i; j > 0; j--) {
                if (arr[j] < arr[j - 1]){
                    int index = arr[j];
                    arr[j] = arr[j - 1];
                    arr[j - 1] = index;
                }else {
                    break;
                }
            }
        }
    }
}

希尔排序

思路

视频讲解:王道计算机考研 数据结构8.2_2_希尔排序_哔哩哔哩_bilibili

        希尔排序是在插入排序的基础上进行改进,所以请先理解好插入排序,再来看希尔排序。简单插入排序是对一组数据进行插入排序,而希尔排序是现将数组分成几个组,让其在小组里面排序,一开始分成 len/2个小组,排序完毕后,再分成len/2/2个小组,一直到分成1个小组,执行简单插入排序。

代码

/**
 * 希尔排序
 *
 * @author 康有为
 * @date 2023/04/25
 */
public class ShellSort {
    public void shellSort(int[] arr) {
        int gap = arr.length / 2;
        //while循环来改变gap,每次除2
        while (gap >= 1) {
            //遍历每个元素,将其在其所在的组里进行简单插入排序
            for (int i = 0; i < arr.length; i++) {
                for (int j = i; j >= gap; j -= gap) {
                    if (arr[j] < arr[j - gap]) {
                        int index = arr[j];
                        arr[j] = arr[j - gap];
                        arr[j - gap] = index;
                    } else {
                        break;
                    }
                }

            }
            gap = gap / 2;
        }
    }
}

快速排序

思路

视频讲解:基础篇-25-快速排序_单边循环(lomuto)_演示_哔哩哔哩_bilibili

核心思路

在数组中选一个基准值,扫描其余数字,小的放到基准值左边,大的放到基准值右边。然后再对基准值左右两边进行递归。

核心思路简单,但是代码思路不好想。

代码思路

单边循环快排(lomuto洛穆托分区方案)

  1. 选择最右元素作为基准点元素
  2. j 指针负责找到比基准点小的元素,一旦找到则与i 进行交换,交换完毕i++
  3. i 指针维护小于基准点元素的边界,也就是每次指向待交换的元素
  4. 最后基准点与i 交换,i 即为分区位置
  5. 交换完毕后,基准值左边比之小,右边比之大,再分别对其左右两边递归

演示图

代码


/**
 * 快速排序
 *
 * @author 康有为
 * @date 2023/05/05
 */
public class QuickSort {
    public QuickSort(int [] arr){
        helpSort(arr,0,arr.length-1);
    }

    void helpSort(int [] arr,int start,int end){
        int i = 0;
        //以最后一个数字作为基准点
        //j遍历数组从头到倒数第二个,负责找出比基准点小的数字,找出来之后与 i 交换,i右移
        for (int j = 0; j < end; j++) {
            if (arr[j] < arr[end]){
                swap(arr,j,i);
                i++;
            }
        }
        //遍历完毕之后,i的左边都是比基准值小的,i包括i的右边都是比基准值大的
        swap(arr,i,end);
        //交换完毕后,i所指的就是基准值

        //左递归:如果中间值i 的左边有两个以上数字时才需要左递归
        if (i >= start+2){
            helpSort(arr,start,i-1);
        }
        //右递归:如果中间值i 的右边有两个以上数字的时候才需要右递归
        if (i <= end-2){
            helpSort(arr,i+1,end);
        }

    }

    /**
     * 交换
     */
    void swap(int[] arr, int a ,int b){
        int index = arr[a];
        arr[a] = arr[b];
        arr[b] = index;
    }
}

堆排序

思路

视频讲解:排序算法:堆排序【图解+代码】_哔哩哔哩_bilibili

步骤就是两步:1.构建大顶堆 2.进行堆排序

1.构建大顶堆

什么是大顶堆?

下图就是一个大顶堆

大顶堆要求:每个根节点都要比两个子节点大。(小顶堆同理,每个根节点要比两个子节点小)

使用数组来存储大顶堆

规律

下标为i的节点的父节点下标::( i - 1 ) / 2【整数除法】

下标为i的节点的左孩子下标:i * 2 + 1

下标为i的节点的右孩子下标:i * 2 + 2

怎么维护一个大顶堆?

参数:

  1. 数组
  2. 要维护的父节点的小标

步骤

  1. 找到父节点的两个孩子节点
  2. 将大的那个孩子节点与父节点交换
  3. 交换之后,对交换的那个子节点,进行递归维护,因为刚刚的维护,原本子节点也是大顶堆,交换之后可能破坏了

代码

/**
 * heapify
 * 维护大顶堆
 *
 * @param arr        数组
 * @param parentNode 要维护的父节点的下标
 * @param len        大顶堆的长度
 */
public void heapify(int[] arr, int parentNode, int len){
    int lSon = parentNode * 2 + 1;
    int rSon = parentNode * 2 + 2;
    //左孩子节点 如果 大,那就交换位置
    if (lSon < len && arr[lSon] > arr[parentNode]){
        swap(arr,lSon,parentNode);
        //交换完毕后再对左孩子节点进行递归维护
        heapify(arr,lSon,len);
    }
    //右孩子节点 如果 大,那就交换位置
    if (rSon < len && arr[rSon] > arr[parentNode]){
        swap(arr,rSon,parentNode);
        //交换完毕后再对右孩子节点进行递归维护
        heapify(arr,rSon,len);
    }
}

/**
 * 数组元素交换
 */
void swap(int[] arr,int a, int b){
    int index = arr[a];
    arr[a] = arr[b];
    arr[b] = index;
}

构建大顶堆也就是将一个无序的堆维护成大顶堆

维护的顺序是从最后一个父节点开始,从后往前维护

 例如下面的大顶堆

 那么我们就得从最后一个4开始维护,维护完4这个父节点,再维护1这个节点。

因为 下标为i的节点的父节点下标::( i - 1 ) / 2【整数除法】,所以我们开始遍历的维护大顶堆

2.进行堆排序

思路

        将无序的数组排成大顶堆,这样首元素就是最大的,然后将其放到最后,大顶堆长度减1,再维护大顶堆,重复操作

        类似选择排序,选择排序是将最小的元素放到前面,堆排序是通过大顶堆,将最大的元素放到后面

         也就是将最后一个元素和 大顶堆堆顶的元素交换位置,并将最后一个元素从大顶堆中删除,也就是让大顶堆的长度减1,再维护以下这个大顶堆即可

void heapSort(int[] arr){
    int len = arr.length;
    //1.建立大顶堆:从最后一个父节点开始向前遍历维护大顶堆
    for (int i = (len-1-1)/2; i >= 0 ; i--) {
        heapify(arr,i,len);
    }
    //2.开始排序:将最后一个元素与大顶堆堆顶元素交换,然后大顶堆长度减1
    for (int i = len-1; i > 0; i--) {
        swap(arr,i,0);
        heapify(arr,0,i);
    }
}

代码

package 排序;

/**
 * 堆排序
 *
 * @author 康有为
 * @date 2023/04/26
 */
public class HeapSort {
    public void heapSort(int[] arr){
        //1.建立大顶堆:需要从后往前的遍历每个父节点,对父节点进行维护
        int len = arr.length;
        for (int i = (len-2)/2; i >= 0 ; i--) {
            heapify(arr,i,len);
        }

        //2.开始排序
        for (int i = 0; i < len ; i++) {
            swap(arr,0,len-1);
            len--;
            heapify(arr,0,len);
        }
    }


    /**
     * heapify
     * 维护大顶堆
     *
     * @param arr        数组
     * @param parentNode 要维护的父节点的下标
     * @param len        维护数组的长度
     */
     void heapify(int[] arr, int parentNode, int len){
        int lSon = parentNode * 2 + 1;
        int rSon = parentNode * 2 + 2;
        //左孩子节点 如果 大,那就交换位置
        if (lSon < len && arr[lSon] > arr[parentNode]){
            swap(arr,lSon,parentNode);
            //交换完毕后再对左孩子节点进行递归维护
            heapify(arr,lSon,len);
        }
        //右孩子节点 如果 大,那就交换位置
        if (rSon < len && arr[rSon] > arr[parentNode]){
            swap(arr,rSon,parentNode);
            //交换完毕后再对右孩子节点进行递归维护
            heapify(arr,rSon,len);
        }
    }

    /**
     * 交换
     */
    void swap(int[] arr, int a ,int b){
        int index = arr[a];
        arr[a] = arr[b];
        arr[b] = index;
    }

}

归并排序

思路

视频讲解:8.5_1_归并排序_哔哩哔哩_bilibili

        先写出 将两个分别有序部门合并成一个整体有序的 代码:merge

        然后递归对每部分进行合并

1.合并两个有序部分

找一个辅助数组,从两个有序数组中分别取出小的,放到里面,最后就是有序的。


    /**
     * 合并
     * 合并数组中 两个 有序的连续片段
     * 也就是将左边有序的部分 和 右边有序的部分 合并成一个大的有序部分
     *
     * @param arr    数组
     * @param left   左
     * @param middle 中间
     * @param right  右
     * @param temp   临时数组
     */
    void merge(int [] arr,int left,int middle,int right,int []temp){

          int i = left; // 初始化i, 左边有序序列的初始索引
          int j = middle + 1; //初始化j, 右边有序序列的初始索引
          int t = 0; // 指向temp数组的当前索引

          //先把左右两边(有序)的数据按照规则填充到temp数组
          //直到左右两边的有序序列,有一边处理完毕为止
          while (i <= middle && j <= right) {//继续
              //如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
              //即将左边的当前元素,填充到 temp数组
              //然后 t++, i++
              if(arr[i] <= arr[j]){
                  temp[t] = arr[i];
                  t++;
                  i++;
              }else { //反之,将右边有序序列的当前元素,填充到temp数组
                  temp[t] = arr[j];
                  t++;
                  j++;
              }
          }

          //把有剩余数据的一边的数据依次全部填充到temp
          //左边的有序序列还有剩余的元素,就全部填充到temp
          while (i <= middle){
              temp[t] = arr[i];
              t++;
              i++;
          }

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

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

    }

2.递归将数组分成两份,进行合并

一直除2 来分成两部分,直到分成一个数,这时候,一个数是有序的,再回退就是两个数,将两个数合并,再往上。。。。。。

    void sortEntrance(int [] arr,int left, int right,int []temp){

        if (left < right){
            int mid = (left+right)/2;
            //向左递归
            sortEntrance(arr,left,mid,temp);
            //向右递归
            sortEntrance(arr,mid+1,right,temp);
            //归并
            merge(arr,left,mid,right,temp);
        }

    }

代码

package 排序;

import java.util.Arrays;

/**
 * 归并排序
 *
 * @author 康有为
 * @date 2023/04/28
 */
public class MergeSort {

    public void mergeSort(int []arr){
        int [] temp = arr.clone();
        sortEntrance(arr,0,arr.length-1,temp);
    }

    void sortEntrance(int [] arr,int left, int right,int []temp){

        if (left < right){
            int mid = (left+right)/2;
            //向左递归
            sortEntrance(arr,left,mid,temp);
            //向右递归
            sortEntrance(arr,mid+1,right,temp);
            //归并
            merge(arr,left,mid,right,temp);
        }

    }

    /**
     * 合并
     * 合并数组中 两个 有序的连续片段
     * 也就是将左边有序的部分 和 右边有序的部分 合并成一个大的有序部分
     *
     * @param arr    数组
     * @param left   左
     * @param middle 中间
     * @param right  右
     * @param temp   临时数组
     */
    void merge(int [] arr,int left,int middle,int right,int []temp){

          int i = left; // 初始化i, 左边有序序列的初始索引
          int j = middle + 1; //初始化j, 右边有序序列的初始索引
          int t = 0; // 指向temp数组的当前索引

          //先把左右两边(有序)的数据按照规则填充到temp数组
          //直到左右两边的有序序列,有一边处理完毕为止
          while (i <= middle && j <= right) {//继续
              //如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
              //即将左边的当前元素,填充到 temp数组
              //然后 t++, i++
              if(arr[i] <= arr[j]){
                  temp[t] = arr[i];
                  t++;
                  i++;
              }else { //反之,将右边有序序列的当前元素,填充到temp数组
                  temp[t] = arr[j];
                  t++;
                  j++;
              }
          }

          //把有剩余数据的一边的数据依次全部填充到temp
          //左边的有序序列还有剩余的元素,就全部填充到temp
          while (i <= middle){
              temp[t] = arr[i];
              t++;
              i++;
          }

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

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

    }
}

基数排序(桶排序)

思路

视频讲解:8.5_2_基数排序_哔哩哔哩_bilibili

之前的排序都是每次直接比较元素的大小,而基数排序不是。

  1. 将所有带比较数值统一为同样的数位长度,数据较短的数前面补0。
  2. 定义10个“桶子”,依次是0-9。
  3. 将待排序数组的元素的最低位 对应放到桶子里面,然后再收集起来。
  4. 在将待排序数组的元素的下一位 对应放到桶子里面,再收集。
  5. 循环到最高位,收集起来就排序好了。

总的来说就是将元素 按照它的从最低位放到对应的桶里面、收集起来,这样从最低位一直到最高位也放到桶里面,收集完毕,数组就有序了。

如果要从小到大排序,就从0号桶开始收集,如果要从大到小排序,就从10号桶排序

代码

package 排序;

/**
 * 基数排序
 *
 * @author 康有为
 * @date 2023/05/04
 */
public class RadixSort {

    public void radixSort(int []arr){
        int arrLen = arr.length;
        //找出数组最大的元素
        int max = arr[0];
        for (int i = 1; i < arrLen; i++) {
            if (arr[i] > max){
                max = arr[i];
            }
        }

        //统计最大元素的长度
        int maxLen = 1;
        int index = 1;
        while (true){
            index = index * 10;
            if (Math.abs(max) >= index){
                maxLen ++;
            }else {
                break;
            }

        }



        //将数组元素的 某一位 放到桶里面,位数从末位到首位
        //如何拿到元素的 每一位?例如数字321,321 % 10 /1 = 1,321 % 100 /10 = 2,321 % 1000 /100 = 3
        //从最低位 遍历 到最高位
        for (int i = 1; i <= maxLen; i++) {
            //定义桶
            int[][] barrel = new int[10][arrLen];
            //为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
            int[] barrelNum = new int[10];
            //将元素放到桶里面
            for (int j = 0; j < arrLen; j++) {
                //拿到数组的某一位
                int num =(int) (arr[j] % Math.pow(10,i) /Math.pow(10,i-1));
                //将数组的元素按照某一位放置到 对应的桶里面
                barrel[num][barrelNum[num]] = arr[j];
                barrelNum[num]++;
            }
            //从每个桶里取出元素
            index = 0;
            for (int j = 0; j < 10; j++) {
                //只有桶里面有数据才取出
                if (barrelNum[j] != 0){
                    for (int k = 0; k < barrelNum[j]; k++) {
                        arr[index] = barrel[j][k];
                        index++;
                    }
                }

            }

        }
    }
}

负数的问题

负数不能排序,要排序负数

桶排序对于负数的处理_桶排序可以排负数吗_macro_buaa的博客-CSDN博客

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值