8种排序算法(Java实现)

目录

1.简介:

2.性能:

二.具体实现

1.冒泡排序

2.简单选择排序

3.直接插入排序

4.希尔排序(对直接插入的改进)

5.快速排序

6.归并排序

7.基数排序(桶排序的扩展)

8.堆排序


一.排序算法的简单介绍

1.简介:

2.性能:

3.总结和对比

二.具体实现

1.冒泡排序


每次遍历排序都找出一个最大值放在后面 就像冒泡一样 应用了交换的思想

[3, 9, -1, 10, 20]
第1次遍历排序:
[3, -1, 9, 10, 20]
第2次遍历排序:
[-1, 3, 9, 10, 20]
第3次遍历排序:
[-1, 3, 9, 10, 20]
第4次遍历排序:
[-1, 3, 9, 10, 20]
最终排序结果:
[-1, 3, 9, 10, 20]

*所以5个数组进行4次遍历排序就可

*根据上面的遍历我们还发现第三次遍历数组已经有序,无需进行第四次遍历,所以我们可以对这点进行优化 。  也就是如果经历一次遍历排序一次交换也没发生,那么我们就认为这个数组已经有序,直接retuen.

1.1代码实现:

    /**
     * 冒泡排序
     * @param arr 进行排序的数组
     * @return 排好序的数组
     */
    public static int[] bubbleSort(int[] arr) {
        int temp = 0;
        int count = 0;

        for (int i =  arr.length-1; i > 0; i--) {
            count = 0;
            //每次循环都遍历出了一个最大的放在数组的后面
            for (int j = 0; j < i ; j++) {
                //如果比后一个元素大则进行交换
                if(arr[j] > arr[j+1]) {
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                   //记录进行交换的次数
                    count++;
                }
            }

//            System.out.println("第" + (arr.length-i)  +"次排序:");
//            System.out.println(Arrays.toString(arr));
            //说明已经有序了 无须在排
            if (count == 0) {
                return arr;
            }
            
        }
        return arr;
    }

1.2测试排序十万个随机数所需要的时间:

        //创建随机数组
        int[] testArr = new int[100000];

        for (int i = 0; i < testArr.length; i++) {
            //生成[0,8000]间的随机数
            testArr[i] = (int) (Math.random()*8000000);
        }

        //开始时间
        Date dataBegin = new Date();

        //冒泡排序
        sort.bubbleSort(testArr);

        //结束时间
        Date dataEnd = new Date();
        System.out.println(dataEnd.getTime()-dataBegin.getTime());

结果:14523ms

2.简单选择排序


每次遍历排序都选择一个最小值 然后与最前面的数进行交换  与冒泡排序不同 只交换一次

[3, 9, -1, 10, 20]
第1次遍历排序:
[-1, 9, 3, 10, 20]
第2次遍历排序:
[-1, 3, 9, 10, 20]
第3次遍历排序:
[-1, 3, 9, 10, 20]
第4次遍历排序:
[-1, 3, 9, 10, 20]
最终排序结果:
[-1, 3, 9, 10, 20]

蓝色代表每次遍历要找到的最小值

2.1代码实现

    /**
     * 选择排序
     * @param arr 进行排序的数组
     * @return 排好序的数组
     */
    public static int[] selectSort(int[] arr) {
        int temp = 0;
        //最小值的下标
        int min = 0;
        for (int i = 0; i < arr.length - 1; i++) {
            //先假设最小的数是arr[i]
            min = i;
            //遍历找到最小的那个
            for (int j = i + 1; j < arr.length; j++) {
                //如果发现arr[j] 比 arr[min]小 则令min = j
                if(arr[j] < arr[min]) {
                    min = j;
                }
            }
            //将的到的最小值与arr[i]进行交换
            if (min != i) {
                temp = arr[min];
                arr[min] = arr[i];
                arr[i] = temp;
            }
//            System.out.println("第" + (i+1)  +"次排序:");
//            System.out.println(Arrays.toString(arr));
        }
        return arr;
    }

2.2测试排序十万个随机数所需要的时间:

        //创建随机数组
        int[] testArr = new int[100000];
        for (int i = 0; i < testArr.length; i++) {
            //生成[0,8000]间的随机数
            testArr[i] = (int) (Math.random()*8000000);
        }

        //开始时间
        Date dataBegin = new Date();

        //选择排序
        sort.selectSort(testArr);

        //结束时间
        Date dataEnd = new Date();
        System.out.println(dataEnd.getTime()-dataBegin.getTime());

结果:4024ms

3.直接插入排序


先将要排序数组中第一个数当作以已排序好的数组,然后依次遍历剩下的数 按顺序插入已排序好的数组。全部遍历一遍后则得到一个排序好的数组。

[3, 9, -1, 10, 20]
第1次遍历排序:
[3, 9, -1, 10, 20]
第2次遍历排序:
[-1, 3, 9, 10, 20]
第3次遍历排序:
[-1, 3, 9, 10, 20]
第4次遍历排序:
[-1, 3, 9, 10, 20]
最终排序结果:
[-1, 3, 9, 10, 20]

蓝色对应代码中的insertValue

红色代表已排好序的数组

3.1代码实现:

    /**
     * 插入排序
     * @param arr 进行排序的数组
     * @return 排好序的数组
     */
    public static int[] insertSort(int[] arr) {
        //要插入的值
        int insertValue;
        //插入的下标
        int insertIndex;
        for (int i = 1; i < arr.length; i++) {
            insertValue = arr[i];
            //先从已经排好序的数组中最后一个数开始比较
            insertIndex = i - 1;
            //如果下标小于0了说明已经跟 已排好序的数组全部做过了比较
            //如果要插入的那个值 小于 已排好序的数组中最后一个 九将这个数后移以为 然后在往前遍历 依此比较
            //知道找到一个比要插入值小的 则该点就是要插入的点
            //如果一次while循环也没进入 则说明要插入的这个值比 已排好序的数组中所有的都要大
            while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
                arr[insertIndex + 1] = arr[insertIndex];
                insertIndex--;
            }
            //将值插入
            arr[insertIndex + 1] = insertValue;

            System.out.println("第" + i +"次遍历排序:");
            System.out.println(Arrays.toString(arr));
        }

        return arr;
    }

3.2测试排序十万个随机数所需要的时间:912ms   

4.希尔排序(对直接插入的改进)


希尔排序是一种高效的排序算法,它基于插入排序算法。如果较小的值在最右边并且必须移到最左边,在这样的情况下入过用直接插入排序代价很大,采用希尔排序就能很好的解决这个问题。希尔排序也称缩小排量排序

arr = [8, 9, 1, 7, 2, 3, 5, 4, 6, 0]

group = arr.lenght()/2

1)先将数组分成数group 个组 然后把每组中数据进行排序;

2)然后再令group = group/2 ,再对每组中数据进行排列;

3)重复上面步骤,知道最后分成 group = 1时,再对每组中数据进行排列,这时数组就是有序数组了。

[8, 9, 1, 7, 2, 3, 5, 4, 6, 0]
第1次遍历排序:分成5组进行排序
[3, 5, 1, 6, 0, 8, 9, 4, 7, 2]
第2次遍历排序:分成2组进行排序
[0, 2, 1, 4, 3, 5, 7, 6, 9, 8]
第3次遍历排序:分成1组进行排序
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
最终排序结果:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

4.1.1代码实现(交换法)

    /**
     * 希尔排序
     * @param arr 进行排序的数组
     * @return 排好序的数组
     */
    public static int[] shellSort(int[] arr) {
      
        int temp = 0;
        //分成几个一组
        int group = arr.length / 2;
        while (group != 0) {
            for (int i = group; i < arr.length; i++) {
                //遍历每组中的元素,步长为group
                for (int j = i - group; j >= 0; j -= group) {
                    //采用交换法
                    if (arr[j] > arr[j + group]) {
                        temp = arr[j];
                        arr[j] = arr[j + group];
                        arr[j + group] = temp;
                    }
                }
            }
//            System.out.println("第" + (++count)  +"次遍历排序:分成" + group +"组");
//            System.out.println(Arrays.toString(arr));
            //进行下一次分组
            group = group / 2;

        }
        return arr;
    }

4.1.2测试排序十万个随机数所需要的时间: 7039ms

4.2.1代码实现(移位法)

    /**
     * 希尔排序 (移位法) 效率高
     * @param arr 进行排序的数组
     * @return 排好序的数组
     */
    public static int[] shellSort(int[] arr) {
        int temp = 0;
        //分成几个一组
        int group = arr.length / 2;
        while (group != 0) {
            for (int i = group; i < arr.length; i++) {
                //j代表待插入值的下标
                int j = i;
                temp = arr[j];
                if (arr[j] < arr[j - group]) {
                    //与直接插入的思想相似 找到比它小的 就让他后移group位
                    while (j - group >= 0 && temp < arr[j - group]) {
                        arr[j] = arr[j - group];
                        j -= group;
                    }
                    //退出while时 代表找到temp位置
                    arr[j] = temp;
                }

            }
            //进行下一次分组
            group = group / 2;

        }
        return arr;
    }

4.2.2测试排序十万个随机数所需要的时间: 21ms 

*所以移位法的性能远高于交换法

5.快速排序


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

缺点:不稳定

举例:

1 4 7 5 3 6 4 0

取中间值 (0+7)/2=3 所以也就是5

左边索引从0开始遍历 右边索引从7开始遍历

1 4 7 5 3 6 4 0

先遍历左边找到比5大的

1 4 7 5 3 6 4 0

然后再遍历右边找到个比5小的

1 4 7 5 3 6 4 0 ==》1 4 0 5 3 6 4 0

重复上述步骤

1 4 0 5 3 6 4 7 ==》1 4 0 4 3 6 5 7

1 4 0 4 3 6 5 7 ==》1 4 0 4 3 5 6 7

这就完成了第一次分割 1 4 0 4 3       5      6 7

然后继续左边1 4 0 4 3的分割     右边 6 7分割

5.1代码实现

public class QuickSort {
    public static void quickSort(int[] arr) {
        quickSort(arr, 0, arr.length - 1);
    }
    public static void quickSort(int[] arr, int star, int end) {
        if (star >= end) {
            return ;
        }
        int l = star;
        int r = end;
        int piovt = arr[(l + r)/2];
        while (l <= r) {
            while (l <= r && arr[l] < piovt) {
                l++;
            }
            while (l <= r && arr[r] > piovt) {
                r--;
            }
            //交换
            if (l <= r) {
                int temp = arr[l];
                arr[l] = arr[r];
                arr[r] = temp;
                l++;
                r--;
            }
        }
        quickSort(arr,star,r);
        quickSort(arr,l,end);

    }
}

5.2测试排序十万个随机数所需要的时间: 16ms

6.归并排序


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

缺点:需要额外的空间

 

 6.1.1代码实现

public class MergeSort {
    public static void mergeSort(int[] arr) {
        int[] temp = new int[arr.length];
        mergeSort(arr,0, arr.length-1, temp);
    }


    public static void mergeSort(int[] arr, int star, int end, int[] temp) {
        if (star >= end) {
            return;
        }
        int mid = star + (end - star) / 2;
        //分
        mergeSort(arr, star, mid, temp);
        mergeSort(arr, mid + 1, end, temp);
        
        //治
        merge(arr,star,mid,end,temp);
    }

    private static void merge(int[] arr, int star, int mid, int end, int[] temp) {
        int left = star;
        int right = mid + 1;
        int index = star;
        while (left <= mid && right <= end) {
            if (arr[left] < arr[right]) {
                temp[index++] = arr[left++];
            } else {
                temp[index++] = arr[right++];
            }
        }
        while (left <= mid) {
            temp[index++] = arr[left++];
        }
        while (right <= end) {
            temp[index++] = arr[right++];
        }
        for (int i = 0; i <= end; i++) {
            arr[i] = temp[i];
        }
    }
}

6.1.2测试排序十万个随机数所需要的时间:  2244ms

经过测试我发先 把临时数组当成参数时 可以大大提高该方法的效率 

原因可能是每次归并都需要创建一个新数组 所以花费了很多时间

6.2.1代码改进

/**
     * 归并排序
     * @param arr 待排序数组
     * @param leftIndex 最左侧下标
     * @param rightIndex 最右侧下标
     * @param tempArr 临时数组
     * @return
     */
    public static int[] mergeSort(int[] arr,int leftIndex, int rightIndex, int[] tempArr) {
        //先分 通过递归将数组分成单个 再归并
        if (leftIndex < rightIndex) {
            //中间索引 也就是左侧最后一个数的索引
            int midIndex = (leftIndex + rightIndex)/2;
            //向左递归
            mergeSort(arr, leftIndex, midIndex, tempArr);
            //向右递归
            mergeSort(arr, midIndex + 1, rightIndex, tempArr);
            //全部分好了之后 进行合并
            merge(arr,leftIndex,rightIndex,tempArr);
        }
        return arr;
    }

    /**
     * 将已经分好的 进行归并
     * @param arr 待归并数组
     * @param leftIndex 左边第一个数的索引
     * @param rightIndex 右侧最后一个数的索引
     * @param tempArr 临时数组
     */
    public static void merge(int[] arr, int leftIndex, int rightIndex, int[] tempArr) {
        //中间索引 也就是左侧最后一个数的索引
        int midIndex = (leftIndex + rightIndex)/2;
        //左边第一个数的索引
        int l = leftIndex;
        //右侧第一个数的索引
        int r = midIndex + 1;

        int tempIndex = 0;
        //当有一侧都遍历完成时退出while
        while (l <= midIndex && r <= rightIndex) {
            if (arr[l] <= arr[r]) {
                tempArr[tempIndex] = arr[l];
                tempIndex++;
                l++;
            } else {
                tempArr[tempIndex] = arr[r];
                tempIndex++;
                r++;
            }

        }
        //说明左面没遍历完 仍须遍历
        while (l <= midIndex) {
            tempArr[tempIndex] = arr[l];
            tempIndex++;
            l++;
        }
        //说明右面没遍历完 仍须遍历
        while (r <= rightIndex) {
            tempArr[tempIndex] = arr[r];
            tempIndex++;
            r++;
        }
        //都遍历完成后 把tempArr赋给arr
        //注意 这时并不是copy所有数据 它可能只时归并中的一个小步骤
        tempIndex = 0;
        l = leftIndex;
//        System.out.println("左索引"+l+"右索引" +rightIndex);
//        System.out.println("要归并的数组" + Arrays.toString(tempArr));
        while (l <= rightIndex) {
            arr[l] = tempArr[tempIndex];
            l++;
            tempIndex++;
        }

    }

6.2.2测试排序十万个随机数所需要的时间:  13ms

7.基数排序(桶排序的扩展)


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

先创建10个和待排序数组一样大的桶,把待排序数组一次遍历,按照个位数字放在对应的 0 1 2 3 4 5 6 7 8 9的10个桶里,放完后在从0~9号桶按顺序在放回数组,然后再按照十位数.....只到遍历完数组最大值的位数(比如待排序数组最大值只有三位 这是我们只需要遍历到百位就好)。数列就变成一个有序序列。

要注意的是基数排序非常消耗内存 因为他会创建10个跟待排序数组一样大的桶;

7.1代码实现

    /**
     * 基数排序
     * @param arr 待排序数组
     * @return 排好序的数组
     */
    public static int[] radixSort(int[] arr) {
        int max = arr[0];
        //遍历出数组的最大值
        for (int t = 0; t < arr.length; t++) {
            if (max < arr[t]) {
                max = arr[t];
            }
        }
        //最大位数
        int digit = (max+"").length();
        //创建一个二维数组 模拟10个桶 分别装 0 1 2 3 4 5 6 7 8 9
        int[][] bucket = new int[10][arr.length];
        //用来记录桶中存放了几个数据  比如bucketElementCounts[2] = 3; 就代表存放2的那个桶中有3个数据
        int[] bucketElementCounts = new int[10];
        int temp;
        //个 十 百 位....只到遍历到最高位
        for (int w = 0; w < digit; w++) {
            //放进对应的桶中
            for (int i = 0; i < arr.length; i++) {
                //获取对应的位的值 比如第一次就是取个位的值 然后再取十位的值 以此类推
                temp = (int) (arr[i] / ( Math.pow(10,w)) % 10);
                //放进对应的桶中
                bucket[temp][bucketElementCounts[temp]] = arr[i];
                //记录桶中存放元素的个数++
                bucketElementCounts[temp]++;
            }
            //当做遍历arr的索引
            int index = 0;
            //从桶中依次放进arr中 j代表哪个桶
            for (int j = 0; j < 10; j++) {
                //如果桶中有数据在遍历
                if(bucketElementCounts[j] != 0) {
                    for (int k = 0; k < bucketElementCounts[j]; k++) {
                        //取出元素放在arr中
                        arr[index] = bucket[j][k];
                        index++;
                    }
                }
                //遍历完一个桶后 要把记录桶中数据的个数清零 要不然会影响下一次的存放
                bucketElementCounts[j] = 0;
            }
        }
        return arr;
    }

7.2测试排序一百万个随机数所需要的时间:  526ms

 所以它在处理大量数据时效率非常高

本代码没有考虑负数问题 后面补充

8.堆排序


一般升序采用大顶堆,降序采用小顶堆

大顶堆:父节点比子节点都要大

小顶堆:父节点比子节点都要小

8.1堆排序基本思想

1) 将待排序序列构造成一个大顶堆

2) 此时,整个序列的最大值就是堆顶的根节点.

3) 将其与末尾元素进行交换,此时末尾就为最大值。

4) 然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n 个元素的次小值。如此反复执行,能得到一个有序序列了。

8.2过程分析

步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

假定待排序数组arr = { 4, 6, 8, 5, 9} 要求将数组升序排序。

1) .先把它看成个堆(二叉树)

2) .此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点

arr.length/2-1=5/2-1=1,也就是下面的 6 结点),从左至右,从下至上进行调整。

 

3) .找到第二个非叶节点 4,由于[4,9,8]中 9 元素最大,4 和 9 交换。

 4) 这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中 6 最大,交换 4 和 6。

 此时,我们就将一个无序序列构造成了一个大顶堆。

步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
1) 将堆顶元素 9 和末尾元素 4 进行交换

2) .重新调整结构,使其继续满足堆定义
3) .再将堆顶元素 8 与末尾元素 5 进行交换,得到第二大元素 8. 

 4) 后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

总结:
1).将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
2).将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
3).重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤, 直到整个序列有序。
8.4代码实现
/**
     * 堆排序
     * @param arr 待排序数组
     * @return 有序数组
     */
    public static int[] heapSort(int[] arr) {
        int temp = 0;
        //把无序数组 变成大顶堆
        for (int i = arr.length/2 -1; i >= 0; i--) {
            max2Root(arr, i, arr.length);
        }
//        System.out.println(Arrays.toString(arr));
        //将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
        //重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
        for (int j = arr.length - 1; j > 0; j--) {
            //交换
            temp = arr[j];
            arr[j] = arr[0];
            arr[0] = temp;
            //把剩下数组继续变成大顶堆
            max2Root(arr, 0, j);
        }
        return arr;
    }

    /**
     * 将数组变成大顶堆
     * @param arr 待排序数组
     * @param i 父节点下标
     * @param lenght 数组长度
     */
    public static void max2Root(int[] arr, int i, int lenght) {
        int temp = arr[i];
        //将最大值与父节点交换
        for (int k = i * 2 + 1; k < lenght; k = k * 2 + 1) {
            //如果左子节点大于右子节点 则k指向右子节点
            if (k + 1 < lenght && arr[k] < arr[k + 1]) {
                k = k + 1;
            }
            //如果父节点 小于 最大的子节点
            if (temp < arr[k]) {
                arr[i] = arr[k];
                //让i指向k 继续循环
                i = k;
            } else {
                //父节点就是最大的了 直接退出
                break;
            }
        }
        //将 temp放到调整后的位置
        arr[i] = temp;
    }

8.5测试排序十万个随机数所需要的时间: 14ms

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

w7486

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值