【Java数据结构与算法】排序七大算法,包括冒泡排序,选择排序,插入排序,希尔排序,快速排序,归并排序,基数排序

一、冒泡排序

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

因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,在进行)

在这里插入图片描述

import java.util.Arrays;

public class BubbleSort {

    public static void main(String[] args) {
		int arr[] = {3, 9, -1, 10, 20};

		System.out.println("排序前");
		System.out.println(Arrays.toString(arr));

//测试冒泡排序
        bubbleSort(arr);

        System.out.println("排序后");
        System.out.println(Arrays.toString(arr));
       
    }
    public static void bubbleSort(int[] arr){
        int temp = 0;//临时变量用作交换
        boolean flag = false; // 标识变量,表示是否进行过交换
        for (int i = 0; i < arr.length - 1; i++) {//比长度少一轮
            //每排序完都确定了最左边的是最小值,所以最左边的不用参与排序,所以这里要-1
            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;
                }
            }
            //假如一轮下来都没有交换,则代表已经是有序的了,直接退出循环
            if (!flag){
                break;
            }else {//否则继续循环,同时要把flag重置为false,这个思想经常用到,要熟悉这个思维
                flag = false;
            }
        }
    }
}

二、选择排序

选择排序(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次,得到一个按排序码从小到大排列的有序序列。

在这里插入图片描述


import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
//选择排序
public class SelectSort {

    public static void main(String[] args) {
        int [] arr = {101, 34, 119, 1, -1, 90, 123};

        System.out.println("排序前");
        System.out.println(Arrays.toString(arr));
        selectSort(arr);

        System.out.println("排序后");
        System.out.println(Arrays.toString(arr));


    }

    //选择排序
    public static void selectSort(int[] arr) {

        //最后一次不用比较所以是length -1
        for (int i = 0; i < arr.length - 1; i++) {
            int minIndex = i;
            int min = arr[i];
            //因为默认本论排序的第一个数是最小的,所以从第二个开始比较,因此j=1+1,要比较到最后一个数,所以长度是length
            for (int j = i + 1; j < arr.length; j++) {
                //第一次错误,arr[i] > arr[j],这里因为每次比较都可以产生一个新的最小值,所以要用最小值去比较,而不是arr[i]
                if (min > arr[j]){
                    min = arr[j];
                    minIndex = j;
            }
        }
            arr[minIndex] = arr[i];
            arr[i] = min;
    }


		/*

		//使用逐步推导的方式来,讲解选择排序
		//第1轮
		//原始的数组 : 	101, 34, 119, 1
		//第一轮排序 :   	1, 34, 119, 101
		//算法 先简单--》 做复杂, 就是可以把一个复杂的算法,拆分成简单的问题-》逐步解决

		//第1轮
		int minIndex = 0;
		int min = arr[0];
		for(int j = 0 + 1; j < arr.length; j++) {
			if (min > arr[j]) { //说明假定的最小值,并不是最小
				min = arr[j]; //重置min
				minIndex = j; //重置minIndex
			}
		}


		//将最小值,放在arr[0], 即交换
		if(minIndex != 0) {
			arr[minIndex] = arr[0];
			arr[0] = min;
		}

		System.out.println("第1轮后~~");
		System.out.println(Arrays.toString(arr));// 1, 34, 119, 101


		//第2轮
		minIndex = 1;
		min = arr[1];
		for (int j = 1 + 1; j < arr.length; j++) {
			if (min > arr[j]) { // 说明假定的最小值,并不是最小
				min = arr[j]; // 重置min
				minIndex = j; // 重置minIndex
			}
		}

		// 将最小值,放在arr[0], 即交换
		if(minIndex != 1) {
			arr[minIndex] = arr[1];
			arr[1] = min;
		}

		System.out.println("第2轮后~~");
		System.out.println(Arrays.toString(arr));// 1, 34, 119, 101

		//第3轮
		minIndex = 2;
		min = arr[2];
		for (int j = 2 + 1; j < arr.length; j++) {
			if (min > arr[j]) { // 说明假定的最小值,并不是最小
				min = arr[j]; // 重置min
				minIndex = j; // 重置minIndex
			}
		}

		// 将最小值,放在arr[0], 即交换
		if (minIndex != 2) {
			arr[minIndex] = arr[2];
			arr[2] = min;
		}

		System.out.println("第3轮后~~");
		System.out.println(Arrays.toString(arr));// 1, 34, 101, 119 */
    }

}

三、插入排序

插入排序(Insertion Sorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。

在这里插入图片描述

import java.util.Arrays;

public class InsertSort {
    public static void main(String[] args) {
        int[] arr = {101, 34, 119, 1, -1, 89};
        insertSort(arr); //调用插入排序算法
        System.out.println(Arrays.toString(arr));
    }

    //插入排序
    public static void insertSort(int[] arr){
        int insertVal;//用来临时存储当前值,因为后面代码后移的时候,当前值会被前面的值覆盖掉
        int insertIndex;//用来存放即arr[当前]的前面这个数的下标
        for (int i = 1; i < arr.length; i++) {//从一开始,一开始就把最左边的数,当成有序的了
            insertVal = arr[i];
            insertIndex = i -1;
            while (insertIndex >= 0 &&  insertVal < arr[insertIndex]){
                arr[insertIndex + 1] = arr[insertIndex];//整体往后移动一位
                insertIndex--;//减一是因为要跟上一个数组值比较
            }
            // 当退出while循环时,说明插入的位置找到, insertIndex + 1
            //这里我们判断是否需要赋值
            if (insertIndex + 1 != i){
                arr[insertIndex + 1] = insertVal;//因为前面多减了一次才退出循环,所以这里是insertIndex + 1
            }

        }

    }
}

四、希尔排序

希尔排序法介绍

希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。

希尔排序法基本思想

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
在这里插入图片描述

对有序序列在插入时采用交换法

import java.util.Arrays;

public class ShellSort {
    public static void main(String[] args) {
        int[] arr = { 8, 9, 1, 7, 2, 3, 5, 4, 6, 0 };
        shellSort(arr); //交换式
        System.out.println(Arrays.toString(arr));
        int[] arr2 = { 8, 9, 1, 7, 2, 3, 5, 4, 6, 0 };
        shellSort2(arr2); //移位方式
        System.out.println(Arrays.toString(arr2));
    }



    // 希尔排序时, 对有序序列在插入时采用交换法,
    // 思路(算法) ===> 代码
    public static void shellSort(int[] arr){
        int temp;
        //每次都将数组分成两半,第一次是10个数分成五组,每组2个数
        //第二次是将5组分成两组,每组5个数这样
        //第三次2再/2,就等于一组,一组10个数
        for (int gap = arr.length / 2 ; gap > 0 ; gap /= 2){
        /*
        /这里解释一下,假设数组为9 8 7 6 5 4 3 2 1 0以第一组和第二组为例解释:第一组分成五小组,
        每组两个元素,令i等于gap也就是下标为5,这里就是从第六个元素"4"开始循环,进入下一层循环,因为i等于gap就代表了本组的第二个元素
        但是我们要从第一个元素开始挨个比较,所以循环初始值为i-gap,5-5=0,然后开始比较,步长为5,所以j -= gap,就是"9"与“4”比较,
        第一组第一数比较完之后就跳出循环了,然后i++,就到了“3”,3再进入循环,然后6-5=1,所以下标为1的值跟3比较,然后循环退出,i++,以此类推
         */
        /*
        第二组也是如此,第一次循环,i=2,j=2-2=0,然后比较9跟7两个值,然后i=3,j=1,比较8跟6;然后i=4,j=2,比较5跟7的值,然后j-2=0
        再比较5跟7跟9的值
         */
            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;
                    }
                }
            }
        }
    }
    /*
// 希尔排序的第1轮排序
		// 因为第1轮排序,是将10个数据分成了 5组,五组处理5次,
		for (int i = 5; i < arr.length; i++) {
			// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5,也就是这里只比较一次就退出了,毕竟本组只有两个数据
			for (int j = i - 5; j >= 0; j -= 5) {
				// 如果当前元素大于加上步长后的那个元素,说明交换
				if (arr[j] > arr[j + 5]) {
					temp = arr[j];
					arr[j] = arr[j + 5];
					arr[j + 5] = temp;
				}
			}
		}


		// 希尔排序的第2轮排序
		// 因为第2轮排序,是将10个数据分成了 5/2 = 2组,
		for (int i = 2; i < arr.length; i++) {
			// 遍历各组中所有的元素(共5组,每组有2个元素), 步长2
			for (int j = i - 2; j >= 0; j -= 2) {
				// 如果当前元素大于加上步长后的那个元素,说明交换
				if (arr[j] > arr[j + 2]) {
					temp = arr[j];
					arr[j] = arr[j + 2];
					arr[j + 2] = temp;
				}
			}
		}


		// 希尔排序的第3轮排序
		// 因为第3轮排序,是将10个数据分成了 2/2 = 1组
		for (int i = 1; i < arr.length; i++) {
			// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5
			for (int j = i - 1; j >= 0; j -= 1) {
				// 如果当前元素大于加上步长后的那个元素,说明交换
				if (arr[j] > arr[j + 1]) {
					temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
			}
		}
 */

对有序序列在插入时采用移动法, (就是使用了插入法)

public static void shellSort2(int[] arr){
        // 增量gap, 并逐步的缩小增量
        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];//记录值
                //当前值小于本组的上一个值,并且没有越界
                //跟普通插入排序不同的是,普通插入排序是跟上一个值比较就是-1,而这里是跟本组的上一个值比较
                //因此是-gap就是减步数
                while (j - gap >= 0 && temp < arr[j - gap]){
                     arr[j] = arr[j - gap];
                     j-=gap;
                }
                //当退出while后,就给temp找到插入的位置
                arr[j] = temp;
            }
        }
    }

五、快速排序

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

其实就是,选择一个参照数,然后两个指针,左边的往右移动,右边的往左移动,首先当右边的数大于参照数,则将左边的指针指向的数替换成右边指针指的数,然后再移动左指针,找到符合条件的数,然后将值赋给右边指针的数,然后移动右指针,以此循环,直到左右指针碰在一起,然后将参照数赋值给两指针指的地方

import java.lang.reflect.Array;
import java.util.Arrays;

public class QuickSort {
    public static void main(String[] args) {
        int [] arr= new int[]{3,4,6,1,2,9,6,0,7};
        quicksort(arr,0,arr.length-1);

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

    public static void quicksort(int[] arr , int start , int end){

        //递归结束条件就是没有数要比较了,意思就是开始的的数等于结束的数就退出递归
        if (start < end){
            int mun = arr[start];//定义参照数,选择最左边的哪个数
            int low = start;//定义左边开始的指针,就是最左边的数
            int high = end;//定义右边开始的指针,就是最右边的数

            //当左边指针小于右边指针开始执行
            while (low < high){
                //当左边low指针小于右边high指针,并且 参照数小于等于右边比较数的时候交换
                //特别注意,是小于等于,不然加入有两个相同的数,循环会卡死!!!!
                while (low < high && mun <= arr[high]){
                    //当参照数小于右边比较数时,不用交换,所以指针前移
                    high--;
                }
                当参照数大于右边比较数时,跟左边指针指向的数交换
                arr[low] = arr[high];

                while (low < high && mun >= arr[low]){
                    //当参照数小于左边比较数时,不用交换,所以指针后移
                    low++;
                }
                //同理
                arr[high] = arr[low];
            }
            //此时,low和high其实都是一样的,当指针重叠时,就结束了此次排序,就把两个指针同时指向的数换成参照数
            //这样就实现了参照数左边都是小的数,右边都是大的数
            arr[low] = mun;
            //进行递归,但不用比较整个数组了,而是本次参照数的左边数组在进行递归比较,右边同理
            //所以左边开始的指针就是数组最左边,左边结束的指针就是本次两个指针重合的地方也就是low或者high
            //右边开始的指针是本次重叠指针的后一个位置,因为重叠指针仔左边会参与比较,结束位置就是数组的最右边
            quicksort(arr,start,low);
            quicksort(arr,low+1,end);
        }
    }
}

六、归并排序

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

在这里插入图片描述

归并排序思想示意图2-合并相邻有序子序列:
再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤
在这里插入图片描述

import java.util.Arrays;

public class MergetSort {
    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 min = (right + left) / 2;//中间索引
            //向左递归进行分解
            mergeSort(arr,left,min,temp);
            //向右递归进行分解
            mergeSort(arr,min+1,right,temp);
            //合并
            merge(arr,left,min,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];
                i++;
                t++;
            }

            else {
                temp[t] = arr[j];
                j++;
                t++;
            }
        }
        //(二)
        //把有剩余数据的一边的数据依次全部填充到temp
        while (i <= mid){
            temp[t] = arr[i];
            t++;
            i++;
        }

        while (j <= right){
            temp[t] = arr[j];
            t++;
            j++;
        }

        //(三)
        //将temp数组的元素拷贝到arr
        //注意,并不是每次都拷贝所有
        t = 0;
        int tempLeft = left; //从本次数组的左边不一定是0,开始
        //第一次合并 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;
        }
    }
}

七、基数排序

  1. 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用

  2. 基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法

  3. 基数排序(Radix Sort)是桶排序的扩展

  4. 基数排序是1887年赫尔曼·何乐礼发明的。它是这样实现的:将整数按位数切割成不同的数字,然后按每个位数分别比较。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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) {
        //根据前面的推导过程,我们可以得到最终的基数排序代码

        //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;//n=1,=10,=100,就可以拿出个位数,十位数和百位数了
                //放入到对应的桶中,取出的个位值就正好放入二位数组的下标对应的地方,比如取出是2,刚好就放入二维数组下标为2的一维数组里
                //而bucket第2个值,初始是0,第一次放入就是[3][0],然后后面++,记录整个个位数下标的数组值就会加1,如果再次放入数据,位置就是[3][1]
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                bucketElementCounts[digitOfElement]++;
            }
            //按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
            int index = 0;
            //遍历每一桶,并将桶中的数据,放入到原数组
            for (int k = 0; k < bucket.length; k++) {
                //如果桶中,有数据,我们才放入到原数组
                if (bucketElementCounts[k] != 0) {
                    //循环该桶即第k个桶(即第k个一维数组), 放入
                    for (int l = 0; l < bucketElementCounts[k]; l++) {
                        //取出元素放入到arr
                        arr[index++] = bucket[k][l];
                    }
                }
                //第l轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
                //因为如果不清零会影响下一位数的判断
                bucketElementCounts[k] = 0;
            }

        }
    }
    /*

		//第1轮(针对每个元素的个位进行排序处理)
		for(int j = 0; j < arr.length; j++) {
			//取出每个元素的个位的值
			int digitOfElement = arr[j] / 1 % 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];
				}
			}
			//第l轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
			bucketElementCounts[k] = 0;

		}
		System.out.println("第1轮,对个位的排序处理 arr =" + Arrays.toString(arr));
		*/
}

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值