冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序、基数排序

一、冒泡排序

基本介绍:

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

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

代码示范:(测试80000条数据时间10S左右)

package com.zjl.recursion;

import java.util.Arrays;

public class BubbleSort {
    public static void main(String[] args) {
        int[] ints = {-2,3,5,53,18};
        int temp = 0;//临时值,用于交换
        //loop用于确定在一次循环中是否发生交换,如果没有发生交换就说明数组已经
        //有序,就直接退出外层循环,不在进行遍历操作
        boolean loop ;
        int count = 0;//用于统计交换的次数
        for (int i = 0; i < ints.length-1; i++) {
            loop = true;
            for (int j = 0; j < ints.length-i-1; j++) {
                if (ints[j] > ints[j + 1]) {
                    temp = ints[j];
                    ints[j] = ints[j + 1];
                    ints[j + 1] = temp;
                    count++;
                    loop = false;
                }
            }
            if (loop) {
                break;//退出外层循环
            }

        }
        System.out.println(count);//输出交换次数
        System.out.println(Arrays.toString(ints));//输出排序后的结果
    }
}

二、选择排序

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

选择排序的思想:

选择排序((select sorting)也是一种简单的排序方法。它的基本思想是:第一次从arr[o]~arr[n-1]中选取最小值,与arr[o]交换,第二次从
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次,得到一个按排序码从小到大排列的有序序列。

选择排序思路分析图:

 

 代码示范:(测试80000条时间结果大概为2秒)

package com.zjl.sort;

import java.util.Arrays;
import java.util.Random;

public class SelectSort {
    public static void main(String[] args) {
//        int[] nums = {12, 24, 8, 5, 33};//数组长度
        int[] nums = new int[80000];//80000条测试数据
        int temp = 0;//临时变量用于交换
        int minIndex = 0;//用于存储最小的变量下标
        int min = nums[0];//用于保存最小的数
        boolean loop;//判断数组是否进行了排序,没有进行排序就是已经是有序数列
        for (int i = 0; i < 80000; i++) {//在数组中放入80000条数据
            nums[i] = (int) (Math.random()*80000);
        }

        long start = System.currentTimeMillis();//开始时间
        for (int i = 0; i < nums.length; i++) {
            loop = true;
            min = nums[i];
            for (int j = i+1; j < nums.length; j++) {
                if (nums[j] < min) {
                    minIndex = j;
                    min = nums[j];
                    loop=false;
                }
            }
            if (loop) {
                continue;
            }
            temp = nums[i];
            nums[i] = min;
            nums[minIndex] = temp;
//        System.out.println(Arrays.toString(nums));
        }
        long end = System.currentTimeMillis();//结束时间
        System.out.println(end - start);//运行总时间
    }
}

三、插入排序

基本介绍:

插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。

插入排序的思路:在原始数组中,确定一个有序数组(可以只有一位),剩下的都是无序数组,依次将无序数组中的数,插入到有序数组的适当位置

 

 代码示范:(测试80000条数据大概0.5S,不超过1S)

package com.zjl.sort;

import java.util.Arrays;

public class InsertSort {
    public static void main(String[] args) {
//        int[] arr = {32, 23, 4, 8, 1};
        int[] arr = new int[80000];//测试80000条数据
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random() * 80000);
        }
        int insertVal;//用于保存要进行比较的数
        int insertIndex;//保存用于比较的数的前一个下标
        long start = System.currentTimeMillis();
        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--;//将下标向前移动,方便比较更前面的一个数
            }
            arr[insertIndex+1] = insertVal;//找到最适合待插入数的下标的前一个,将下标加一,在该位置放上待插入数

//            if (insertIndex+1 != i) {//加入判断条件,如果待插入数当前位置就最适合它,就不进行赋值操作
//                arr[insertIndex+1] = insertVal;//找到最适合待插入数的下标的前一个,将下标加一,在该位置放上待插入数
//            }

//            System.out.println(Arrays.toString(arr));
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);


    }
}

四、希尔排序 

基本介绍:

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

希尔排序法基本思想:

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

 

 希尔排序的两种完成形式:

(1)交换式希尔排序:对有序序列在插入时采用交换法,并测试排序速度

(2)移动/移位(插入)式希尔排序:对有序序列在插入时采用移动法,并测试排序速度

代码示范:

package com.zjl.sort;

import org.junit.jupiter.api.Test;

import java.util.Arrays;

public class ShellSort {
    //交换式希尔排序,如果存在大小逆序就交换,80000条数据测试10704毫秒,10秒以上
    public static void main(String[] args) {
//        int[] arr = {8, 9, 1, 7, 2, 3, 11,5, 4, 6, 0,10};//初始化测试数组
        int[] arr = new int[80000];//80000条随机大数组测试
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random() * 80000);
        }
        int temp = 0;
        int length = arr.length;
        long start = System.currentTimeMillis();
        while ((length=(length / 2)) != 0) {//确定每次的间隔大小变小且大于0,逐步减小增量
            for (int i = length; i < arr.length; i++) {//从第一个分段开始,比较的值从数组前往后移动
                //比较方向是从后往前比较,相对于从前往后比较会减少部分时间
                for (int j = i - length; j >= 0; j -= length) {//从后往前比较
                    if (arr[j]>arr[j+length]) {//交换
                        temp = arr[j];
                        arr[j] = arr[j + length];
                        arr[j + length] = temp;
                    }
                }
            }
//            System.out.println(Arrays.toString(arr));

        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
    @Test
    //交换式希尔排序:80000条数据测试,20684毫秒,20秒以上
    public void ShellSort1(){
        //        int[] arr = {8, 9, 1, 7, 2, 3, 11,5, 4, 6, 0,10};
        int[] arr = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random() * 80000);
        }
        int temp = 0;
        int length = arr.length;
        long start = System.currentTimeMillis();
        while ((length = length / 2) != 0) {
            for (int i = 0; i < arr.length; i++) {//从前往后比较,增加了更多的负担,有较多重复比较的过程
                for (int j = 0; j+length < arr.length; j+=length) {//从前往后比较
                    if (arr[j] > arr[j + length]) {
                        temp = arr[j];
                        arr[j] = arr[j + length];
                        arr[j + length] = temp;
                    }
                }
            }
//            System.out.println(Arrays.toString(arr));
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

    @Test
    //移位式/插入式希尔排序:80000条测试数据16毫秒,速度显著提升
    public void ShellSort2(){
//        int[] arr = {8, 9, 1, 7, 2, 3, 11,5, 4, 6, 0,10};//测试用例
        int[] arr = new int[80000];//进行80000次测试用例
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random() * 80000);
        }
        int min = 0;
        int minIndex = 0;
        int length = arr.length;
        long start = System.currentTimeMillis();//开始时间
        while ((length = length / 2) != 0) {
            for (int i = length; i < arr.length; i++) {//从第一个分段开始,比较的值从数组前往后移动
                //比较方向是从后往前比较,相对于从前往后比较会减少部分时间
                minIndex=i;//将当前数组的下标假设为当前分段的最小下标
                min = arr[minIndex];//将当前下标所对应的值当做当前最小的值
                if (arr[i] < arr[i - length]) {//按照从小到大排序,当同一组的后一个大于前一个时就开始比较查找最小的一个值
                    //当当前组的最小值的下标比0小时说明到达该组的最前面,那么该下标对应的值就是最小的值
                    //当当前假定的最小值min大于它的前一个值的时候说明此时最小值对应的下标就是最适合它的位置
                    while (minIndex - length >=0 && min < arr[minIndex - length]) {
                        //还没有找到最合适min的下标,需要将较大的数向后移,minIndex也要向前移动,便于比较最小值与该组前一个的值的大小
                        arr[minIndex] = arr[minIndex - length];
                        minIndex -= length;
                    }
                }
                arr[minIndex] = min;//找到假定的最小值的最合适的位置放入其中
            }
//            System.out.println(Arrays.toString(arr));
        }
        long end = System.currentTimeMillis();//结束时间
        System.out.println(end-start);
    }
}

五、快速排序

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

效率高的原因:使用了递归,会开辟栈,利用空间换时间

 快速排序思路分析:

 

 代码实现:搞清楚流程和判断条件(80000条测试时间为20到40毫秒之间)

package com.zjl.sort;

import java.util.Arrays;

public class QuickSort {
    public static void main(String[] args) {
//        int[] arr = {18, 7,12, -3, 13, -9, -6, 20, 11};
        int[] arr = new int[80000];//测试80000条数据
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random()*80000);
        }
        long start = System.currentTimeMillis();
        quickSort(arr,0,arr.length-1);
        long end = System.currentTimeMillis();
        System.out.println(end - start);//打印执行80000次所花费的时间(20多毫秒)
//        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 (l < r) {
            //获取左边比pivot大的数,如果arr[l]大于pivot就退出循环
            while (arr[l] < pivot) {
                l += 1;
            }
            //获取右边比pivot小的数,如果arr[r]小于pivot就退出循环
            while (arr[r] > pivot) {
                r -= 1;
            }
            //如果最左边的索引已经大于或等于右边就直接退出
            if (l >= r) {
                break;
            }
            //进行交换
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;
            //如果交换之后,左边的索引对应的值已经变成pivot,r++
            //理由:此时的arr[l]如果重新进入到循环是不会进行移位的,如果对于枢轴pivot来说不是数组唯一的
            //那就不能排除此时的arr[r]不等于pivot,如果等于了,就会一直进行交换,一直处相同在循环中
            if (arr[l] == pivot) {
                r--;
            }
            //理由同上,只是方向变化了而已
            if (arr[r] == pivot) {
                l++;
            }
//            System.out.println(Arrays.toString(arr));//输出该次交换的结果
//            if (l <= left || r >= right ) {
//                break;
//            }
        }
        //防止在退出循环后,r和l两个位置重合(索引相等)时,在比较的数越来越少时
//出现(r=0,l=0)会在比较的最后出现死循环,造成栈溢出
if (r == l) { r--; l++; } //对左边进行排序操作,因为在循环的排序中,枢轴pivot的位置可能已经改变,最坏就是arr[r] =pivot //所以就是下标r之前都是比枢轴小的 if (left<r) { quickSort(arr, left, r); } //对右边进行排序操作,同理,对于右边的排序来说,在枢轴右边的所有数都是比枢轴大的数 if (l<right) { quickSort(arr, l, right); } } }

 六、归并排序

介绍:

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

归并排序思想示意图:基本思想

 

 说明:

可以看到这种结构很像—棵完全二叉树,归并排序我们采用递归去实现(也可采用迭代的方式去实现)分阶段可以理解为就是递归拆分子序列的过程。

治的阶段:

需要将两个已经有序的子序列合并成一个有序序列,如下,最后一次合并的操作:

代码实现:(80000测试用例时间为18毫秒)

package com.zjl.sort;

import java.util.Arrays;

public class MergeSort {
    public static void main(String[] args) {
//        int[] arr = {0, 3, 7, 2, 9, 4, 8, 6, 5, 1};//测试用例
//        int[] temp = new int[arr.length];
        int[] arr = new int[80000];//测试80000条用例(测试时间18毫秒)
        int[] temp = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random() * 80000);
        }
        long start = System.currentTimeMillis();
        new MergeSort().mergeSort(arr,0,arr.length-1,temp);//进行测试
        System.out.println(System.currentTimeMillis()-start);
//        System.out.println(Arrays.toString(arr));
    }


    //进行数组的分离和合并(采用递归方法)
    public 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);
        }
    }

    //将已经分开的小段数组进行合并
    public void merge(int[] arr, int left, int mid, int right, int[] temp) {
        int l = left;
        int r = mid+1;
        int t = 0;
        //1、先比较两个数组,将较小的放入到临时数组
        while (l <= mid && r <= right) {
            if (arr[l] <= arr[r]) {
                temp[t] = arr[l];
                t++;
                l++;
            }else {
                temp[t] = arr[r];
                t++;
                r++;
            }
        }
        //2、在完成上述的合并后,必然有一边会还有剩余
        //判断左边是否有剩余,并将有序数组按照顺序加入到临时数组
        while (l <= mid) {
            temp[t] = arr[l];
            t++;
            l++;
        }
        //判断右边是否有剩余,并将有序数组按照顺序加入到临时数组
        while (r <= right) {
            temp[t] = arr[r];
            t++;
            r++;
        }
        //3、将临时数组拷贝到原来的数组中
            t = 0;
        while (left <= right) {
            arr[left] = temp[t];
            left++;
            t++;
        }
    }
}

七、基数排序

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

1)基数排序( radixsort)属于“分配式排序”(distribution sort),又称“桶
子法”(bucket sort)或bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用
2)基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法
3)基数排序(Radix Sort)是桶排序的扩展
4)基数排序是1887年赫尔曼:何乐礼发明的。它是这样实现的:将整数按位数切
割成不同的数字,然后按每个位数分别比较。

 基数排序基本思想:

1)将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,
从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

模拟排序:基数排序进行的次数,决定于该数组中数的最大位数是多少位,就进行几次排序

第一轮

 

 第二轮

 

 第三轮

 基数排序代码实现:

package com.zjl.sort;

import java.util.Arrays;

public class RadixSort {
    public static void main(String[] args) {
//        int[] arr = {53, 3, 542, 748, 14, 214};//测试数据
        int[] arr = new int[80000];//80000条数据测试(38毫秒)
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random()*80000);
        }
        long start = System.currentTimeMillis();
        new RadixSort().radixSort(arr);
        System.out.println(System.currentTimeMillis()-start);
    }

    public void radixSort(int[] arr){
        //找到数组中的最大值的长度
        int max= 0;//获取数组中的最大值
        for (int i : arr) {
            if (i > max) {
                max=i;
            }
        }
        int maxLenght = (max + "").length();//将最大值变为字符串然后计算字符串的长度
        //定义一个二维数组,用来存放基数排序中数值的大小(桶排序中桶和桶的大小)
        //因为对于所有的数来说可能每个桶数的奇数存在相同的可能
        //所以将每个桶的最大长度设置为待排序数组的长度
        int[][] bucket = new int[10][arr.length];
//        //再开辟一个数组,用来表示每个桶中存放的数据个数
        int[] bucketNum = new int[10];

        for (int m=0;m<maxLenght;m++) {//每次循环都会将基数位变大(个十百千)
            for (int i = 0; i < arr.length; i++) {
                //radix表示当前数在当前轮基数值的大小
                // 其中(int) Math.pow(10,m),除10对应的幂次方获取当前基数位的对应商,
                // %10表示对应位的大小,用于放置在对应的桶中
                int radix = arr[i]/(int) Math.pow(10,m)%10;
                //bucket[radix][bucketNum[radix]++]表示该值放置在桶中的位置,
                // bucketNum[radix]++,可以确定在该桶的第几个,也可以获取该桶中所放的数值的个数
                //特别注意的是bucketNum[radix]++是先存放数值,再进行++;
                bucket[radix][bucketNum[radix]++] = arr[i];
            }
            //将所有桶中的元素重新按顺序放回到arr[]数组中
            int k = 0;
            for (int i = 0; i < bucket.length; i++) {
                //检验每个桶,看桶中是否放有数值
                if (bucket[i].length > 0) {
                    //获取桶中数值的个数,依次放回到arr[]数组中
                    //arr[k++]也是先将数值放入arr[k]之后,再K++;
                    for (int j = 0; j < bucketNum[i]; j++) {
                        arr[k++] = bucket[i][j];
                    }
                    //将该桶所有数值放回arr后,将该桶的数据个数置为0,相当于该桶中的数据在后面的轮次
                    //中会被覆盖,个数也会重新计算,所以不用清空原来桶中的数据
                    bucketNum[i] = 0;
                }
            }
//        System.out.println(Arrays.toString(arr));
        }
    }
}

说明:

1)基数排序是对传统桶排序的扩展,速度很快.
2)基数排序是经典的空间换时间的方式,占用内存很大,当对海量数据排序时
容易造成OutOfMemoryError .
3)基数排序是稳定的。[注:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,
若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i] =r[j],且r[i]在r[j]们之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的]

4)有负数的数组,不用基数排序来进行排序

 

 

 

posted @ 2021-08-31 16:39  代码红了一大片  阅读(302)  评论(0)    收藏  举报