各种排序算法的介绍、图解以及代码的实现(Java版)

1.排序算法的介绍

排序也称排序算法(SortAlgorithm),排序是将 一组数据,依 指定的顺序进行 排列的过程。

2 排序的分类:

  1. 内部排序:
    指将需要处理的所有数据都加载到 内部存储器( 内存)中进行排序。
  2. 外部排序法:
    数据量过大,无法全部加载到内存中,需要借助 外部存储( 文件等)进行排序。
  3. 常见的排序算法分类(见右图):
    在这里插入图片描述

3.内部排序

3.1插入排序

直接插入排序
1插入排序法介绍:
插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
2插入排序法思想:
插入排序(Insertion Sorting)的基本思想是:把 把 n 个待排序的元素看成为一个有序表和一个无序表,开始时 有序表中只包含一个元素,无序表中包含有 n-1 个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
3插入排序思路图:
在这里插入图片描述
代码实现

//插入排序
    public static void insertSort(int[] arr) {
        int insertVal = 0;
        int insertIndex = 0;
        //使用 for 循环来把代码简化
        for (int i = 1; i < arr.length; i++) {
//定义待插入的数
            insertVal = arr[i];
            insertIndex = i - 1; // 即 arr[1]的前面这个数的下标
// 给 insertVal 找到插入的位置
// 说明
// 1. insertIndex >= 0 保证在给 insertVal 找插入位置,不越界
// 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
// 3. 就需要将 arr[insertIndex] 后移
            while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
                arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]
                insertIndex--;
            }
// 当退出 while 循环时,说明插入的位置找到, insertIndex + 1
// 举例:理解不了,我们一会 debug
//这里我们判断是否需要赋值
            if (insertIndex + 1 != i) {
                arr[insertIndex + 1] = insertVal;
            }
//System.out.println("第"+i+"轮插入");
//System.out.println(Arrays.toString(arr));
        }



希尔排序
1简单插入排序存在的问题
我们看简单的插入排序可能存在的问题.
数组 arr = {2,3,4,5,6,1} 这时需要插入的数 1( 最小), 这样的过程是:
{2,3,4,5,6,6}
{2,3,4,5,5,6}
{2,3,4,4,5,6}
{2,3,3,4,5,6}
{2,2,3,4,5,6}
{1,2,3,4,5,6}
结论: 当 需要插入的数是较小的数时, 后移的次数明显增多,对 效率有影响.
2希尔排序法介绍
希尔排序是希尔(Donald Shell)于 1959 年提出的一种排序算法。希尔排序也是一种 插入排序,它是简单插入排序经过改进之后的一个 更高效的版本,也称为 缩小增量排序。
3希尔排序法基本思想
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多, 当增量减至 1 时,整个文件恰被分成一组,算法便终止
4希尔排序法的示意图
在这里插入图片描述
在这里插入图片描述
代码实现

  1. 希尔排序时, 对有序序列在插入时采用 交换法
  2. 希尔排序时, 对有序序列在插入时采用 移动法
// 希尔排序时, 对有序序列在插入时采用交换法,
 public static void ShellSort(int arr[]) {
        int temp = 0;
        //int count=0;
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            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;
                    }

                }

            }
        }
    }
//对交换式的希尔排序进行优化->移位法
    public static void ShellSort2(int arr[]) {
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            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;
                }

            }

        }
    }

3.2选择排序

1基本介绍
选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的。
2选择排序思想:

选择排序(select sorting)也是一种简单的排序方法。它的基本思想是:第一次从 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 次,得到一个按排序码从小到大排列的有序序列。
3选择排序思路分析图:
在这里插入图片描述

对一个数组的选择排序再进行讲解
在这里插入图片描述
代码实现

//选择排序
public static void selectSort(int[] arr) {
//在推导的过程,我们发现了规律,因此,可以使用 for 来解决
//选择排序时间复杂度是 O(n^2)
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]; // 重置 min
minIndex = j; // 重置 minIndex
}
}
// 将最小值,放在 arr[0], 即交换
if (minIndex != i) {
arr[minIndex] = arr[i];
arr[i] = min;
}
//System.out.println("第"+(i+1)+"轮后~~");
//System.out.println(Arrays.toString(arr));// 1, 34, 119, 101
}

3.3交换排序

冒泡排序
1基本介绍
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始), 依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
优化:
因为排序的过程中,各元素不断接近自己的位置, 如果一趟比较下来没有进行过交换 , 就说明序列有序,因此要在排序过程中设置一个标志 flag 判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,在进行)
2演示冒泡过程的例子(图解)
在这里插入图片描述

小结上面的图解过程:
(1) 一共进行 数组的大小-1 次 大的循环
(2)每一趟排序的次数在逐渐的减少
(3) 如果我们发现在某趟排序中,没有发生一次交换, 可以提前结束冒泡排序。这个就是优化

代码实现

// 将前面额冒泡排序算法,封装成一个方法
public static void bubbleSort(int[] arr) {
// 冒泡排序 的时间复杂度 O(n^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];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
//System.out.println("第" + (i + 1) + "趟排序后的数组");
//System.out.println(Arrays.toString(arr));
if (!flag) { // 在一趟排序中,一次交换都没有发生过
break;
} else {
flag = false; // 重置 flag!!!, 进行下次判断
}
}
}

快速排序
1快速排序法介绍:
快速排序(Quicksort)是对 冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序, 整个排序过程可以递归进行,以此达到整个数据变成有序序列
2快速排序法示意图:
在这里插入图片描述
在这里插入图片描述
代码实现

public static void quickSort(int[] arr, int left, int right) {
        int l = left; //左下标
        int r = right; //右下标
//pivot 中轴值
        int pivot = arr[(left + right) / 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 值 相等 l++, 后移
            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);
        }
    }

3.4归并排序

1 归并排序介绍:
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的 分治 (divide-and-conquer )策略(分治法将问题分(divide)成一些 小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
2 归并排序思想示意图 1-基本思想:
在这里插入图片描述
3 归并排序思想示意图 2-合并相邻有序子序列:
再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤
在这里插入图片描述
代码实现

//分+合方法
    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; // 指向 temp 数组的当前索引
        //(一)
        //先把左右两边(有序)的数据按照规则填充到 temp 数组
        //直到左右两边的有序序列,有一边处理完毕为止
        while (i <= mid && j <= right) {//继续
            //如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
            //即将左边的当前元素,填充到 temp 数组
            //然后 t++, i++
            if (arr[i] <= arr[j]) {
                temp[t] = arr[i];
                t += 1;
                i += 1;
            } else { //反之,将右边有序序列的当前元素,填充到 temp 数组
                temp[t] = arr[j];
                t += 1;
                j += 1;
            }
        }
//(二)
//把有剩余数据的一边的数据依次全部填充到 temp
        while (i <= mid) { //左边的有序序列还有剩余的元素,就全部填充到 temp
            temp[t] = arr[i];
            t += 1;
            i += 1;
        }
        while (j <= right) { //右边的有序序列还有剩余的元素,就全部填充到 temp
            temp[t] = arr[j];
            t += 1;
            j += 1;
        }
//(三)
//将 temp 数组的元素拷贝到 arr
//注意,并不是每次都拷贝所有
        t = 0;
        int tempLeft = left; //
//第一次合并 tempLeft = 0 , right = 1 // tempLeft = 2 right = 3 // tL=0 ri=3
//最后一次 tempLeft = 0 right = 7
        while (tempLeft <= right) {
            arr[tempLeft] = temp[t];
            t += 1;
            tempLeft += 1;
        }
    }

3.5基数排序

1 基数排序(桶排序)介绍:

  1. 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或 bin sort,顾
    名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用
  2. 基数排序法是属于稳定性的排序,基数排序法的是效率高的 稳定性排序法
  3. 基数排序(Radix Sort)是桶排序的扩展
  4. 基数排序是 1887 年赫尔曼·何乐礼发明的。它是这样实现的:将整数按位数切割成不同的数字,然后按每个
    位数分别比较。
    2 基数排序基本思想
  5. 将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
  6. 这样说明,比较难理解,下面我们看一个图文解释,理解基数排序的步骤
    3 基数排序图文说明
    将数组 {53, 3, 542, 748, 14, 214} 使用基数排序, 进行升序排序
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

代码实现

//基数排序方法
    public static void radixSort(int[] arr) {
//根据前面的推导过程,我们可以得到最终的基数排序代码
        //1. 得到数组中最大的数的位数
        int max = arr[0]; //假设第一数就是最大数
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
//得到最大数是几位数
        int maxLength = (max + "").length();
//定义一个二维数组,表示 10 个桶, 每个桶就是一个一维数组
//说明
//1. 二维数组包含 10 个一维数组
//2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为 arr.length
//3. 名明确,基数排序是使用空间换时间的经典算法
        int[][] bucket = new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
//可以这里理解
//比如:bucketElementCounts[0] , 记录的就是 bucket[0] 桶的放入数据个数
        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 < bucketElementCounts.length; k++) {
//如果桶中,有数据,我们才放入到原数组
                if (bucketElementCounts[k] != 0) {
//循环该桶即第 k 个桶(即第 k 个一维数组), 放入
                    for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放入到 arr
                        arr[index++] = bucket[k][l];
                    }
                }
//第 i+1 轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
                bucketElementCounts[k] = 0;
            }
//System.out.println("第"+(i+1)+"轮,对个位的排序处理 arr =" +Arrays.toString(arr));
        }
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值