Java实现排序算法

目录

1、冒泡排序

2、选择排序

3、插入排序

4、 希尔排序

5、快速排序

6、归并排序

7、堆排序

8、基数排序

9、算法总结


1、冒泡排序

算法思想:

        以升序为例,冒泡排序的核心思想如下:

  • Step1:从第一个元素开始,比较相邻的两个元素,如果前一个元素的值比后一个元素的值大,那么就进行交换。
  • Step2:每组相邻元素,都是同样的操作,直到没有相邻元素可以比较为止,那么此时最后的元素就是最大的数。
  • Step3:对剩余元素重复以上步骤,直到没有任何一对元素需要比较为止。

算法代码:

    public static int[] bobbleSort(int[] arr){
        //控制外层的循环次数
        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]) {
                    int temp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        return arr;
    }

2、选择排序

算法思想:

        选择排序首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

算法代码:

    public static int[] 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[min] > arr[j]){
                    min = j;
                }
            }
            //交换
            if(i != min){
                int temp = arr[min];
                arr[min] = arr[i];
                arr[i] = temp;
            }
        }
        return arr;
    }

3、插入排序

算法思想:

        插入排序会选定一个下标,认为在这个下标之前的元素都是有序的。将下标所在的元素插入到其之前的序列中。接着再选取这个下标的后一个元素,继续重复操作。直到最后一个元素完成插入为止。一般从序列的第二个元素开始操作。

算法代码:

     public static int[] insertSort(int[] arr){
        //遍历所有数字
        for(int i=1;i<arr.length;i++) {
            //当前数字比它的前一个数字小
            if(arr[i] < arr[i - 1]) {
                int j;
                // 把当前遍历的数字保存起来
                int temp = arr[i];
                for(j = i - 1; j >= 0 && arr[j] > temp; j--) {
                    // 前一个数字赋给后一个数字
                    arr[j + 1] = arr[j];
                }
                // 把临时变量赋给不满足条件的后一个元素
                arr[j + 1] = temp;
            }
        }
        return arr;
    }

4、 希尔排序

算法思想:

        某些情况下直接插入排序的效率极低。比如一个已经有序的升序数组,这时再插入一个比最小值还要小的数,也就意味着被插入的数要和数组所有元素比较一次。我们需要对直接插入排序进行改进。

        希尔排序把序列按下标的一定增量(步长)分组,对每组分别使用插入排序。随着增量(步长)减少,一直到一,算法结束,整个序列变为有序。因此希尔排序又称缩小增量排序。一般来说,初次取序列的一半为增量,以后每次减半,直到增量为一。

 

算法代码:

    public static int[] shellSort(int[] arr){
        for(int step = arr.length/2;  step > 0; step /= 2){
            for (int i = 0; i < arr.length; i++) {
                int temp = arr[i];
                int j = i - step;
                while(j >= 0 && temp < arr[j]){
                    arr[j+step] = arr[j];
                    j -= step;
                }
                arr[j+step] = temp;
            }
        }
        return arr;
    }

5、快速排序

算法思想:

        快速排序将待排序的数组拆分成左右两个区间,然后选择一个基准值,基准值可以选择数组最左边、中间或者最右边的值,也可以随机选取。然后使用双指针left和right分别指向数组的起始位置和末尾位置,并与基准值进行比较,使得数组左区间的值都小于基准值,数组右区间的值都大于基准值。然后继续拆分,执行相同操作,直到每个区间只有一个数为止。举例如下:

算法代码:

    public static int[] sort(int[] arr){
        quickSort(arr, 0, arr.length-1);
        return arr;
    }
    public static void quickSort(int[] array, int start, int end){
        if(start >= end){
            return;
        }
        //以最左边节点作为参考值
        int pivot = array[start];
        //定义两个指针
        int left = start, right = end;
        while(left < right){
            //找到第一个比基准值小的值
            while(left < right && array[right] >= pivot){
                right --;
            }
            //把左边的数替换成右边的数
            array[left] = array[right];
            //找到第一个比基准值大的值
            while(left < right && array[left] <= pivot){
                left ++;
            }
            //把右边的数替换成左边的数
            array[right] = array[left];
        }
        //把标准值赋给下标重合的位置
        array[left] = pivot;
        //处理左边
        quickSort(array, start, left);
        //处理右边
        quickSort(array, left+1, end);
    }

6、归并排序

算法思想:

        归并排序是建立在归并操作上的一种有效,稳定的排序算法。该算法采用分治法的思想,是一个非常典型的应用。归并排序的思路如下:

  • Step 1:将 n 个元素分成两个各含 n/2 个元素的子序列
  • Step 2:借助递归,两个子序列分别继续进行第一步操作,直到不可再分为止
  • Step 3:此时每一层递归都有两个子序列,再将其合并,作为一个有序的子序列返回上一层,再继续合并,全部完成之后得到的就是一个有序的序列

关键在于两个子序列应该如何合并。假设两个子序列各自都是有序的,那么合并步骤就是:

  • Step 1:创建一个用于存放结果的临时数组,其长度是两个子序列合并后的长度
  • Step 2:设定两个指针,最初位置分别为两个已经排序序列的起始位置
  • Step 3:比较两个指针所指向的元素,选择相对小的元素放入临时数组,并移动指针到下一位置
  • Step 4:重复步骤 3 直到某一指针达到序列尾
  • Step 5:将另一序列剩下的所有元素直接复制到合并序列尾

算法代码:

    public static int[] mergeSort(int[] arr, int low, int high){
        //获取中间值
        int mid = low + (high - low) / 2;
        if(low < high){
            //处理左边数组
            mergeSort(arr, low, mid);
            //处理右边数组
            mergeSort(arr, mid+1, high);
            merge(arr, low, mid, high);
        }
        return arr;
    }
    public static void merge(int[] arr, int low, int mid, int high){
        int[] tempArray = new int[high-low+1];
        //记录第一个数组中需要遍历的下标
        int i = low;
        //记录第二个数组中需要的遍历的下标
        int j = mid + 1;
        //记录临时数组的下标
        int index = 0;
        //遍历两个数组,进行比较,将较小的数字放入临时数组
        while(i <= mid && j <= high){
            if(arr[i] <= arr[j]){
                tempArray[index] = arr[i];
                i++;
            }else{
                tempArray[index] = arr[j];
                j++;
            }
            index ++;
        }
        //将剩余数据放入临时数组
        while(i <= mid){
            tempArray[index++] = arr[i++];
        }
        while(j <= high){
            tempArray[index++] = arr[j++];
        }
        //把临时数组中的数据放入原数组
        for (int k = 0; k < tempArray.length; k++) {
            arr[k+low] = tempArray[k];
        }
    }

7、堆排序

算法思想:

        对于任何一个数组都可以看成一颗完全二叉树。堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

 

        像上图的大顶堆,映射为数组,就是 [50, 45, 40, 20, 25, 35, 30, 10, 15]。可以发现第一个下标的元素就是最大值,将其与末尾元素交换,则末尾元素就是最大值。所以堆排序的思想可以归纳为以下两步:

  • Step 1:根据初始数组构造堆
  • Step 2:每次交换第一个和最后一个元素,然后将除最后一个元素以外的其他元素重新调整为大顶堆

        重复以上两个步骤,直到没有元素可操作,就完成排序了。

        我们需要把一个普通数组转换为大顶堆,调整的起始点是最后一个非叶子结点,然后从左至右,从下至上,继续调整其他非叶子结点,直到根结点为止。

 算法代码:

    public static void constructMaxHeap(int[] arr, int size, int index){
        //左子节点
        int leftNode = 2 * index + 1;
        //右子节点
        int rightNode = 2 * index + 2;
        int parent = index;
        //和父节点的两个孩子节点进行比较,找到最大的节点
        if(leftNode < size && arr[leftNode] > arr[parent]){
            parent = leftNode;
        }
        if(rightNode < size && arr[rightNode] > arr[parent]){
            parent = rightNode;
        }
        //交换位置
        if(parent != index){
            int temp = arr[index];
            arr[index] = arr[parent];
            arr[parent] = temp;
            //因为交换位置之后可能出现字数不满足大顶堆的条件,因此要对字数进行调整
            constructMaxHeap(arr, size, parent);
        }
    }
    public static int[] heapSort(int[] arr){
        //起始位置是最有一个非叶子节点,即最后一个节点的父节点
        int start = (arr.length-1) / 2;
        //调整为最大堆
        for (int i = start; i >= 0; i--) {
            constructMaxHeap(arr, arr.length, i);
        }
        //先把数组中第0个位置的数和堆中最后一个数字交换位置,再把前面的处理为大顶堆
        for (int i = arr.length-1; i > 0; i--) {
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;
            constructMaxHeap(arr, i, 0);
        }
        return arr;
    }

8、基数排序

算法思想:

        基数排序的原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。为此需要将所有待比较的数值统一为同样的数位长度,数位不足的数在高位补零。

算法代码:

/**
 * 基数排序
 */
public static void radixSort(int[] arr) {
    // 存放数组中的最大数字
    int max = Integer.MIN_VALUE;
    for (int value : arr) {
        if (value > max) {
            max = value;
        }
    }
    // 计算最大数字是几位数
    int maxLength = (max + "").length();
    // 用于临时存储数据
    int[][] temp = new int[10][arr.length];
    // 用于记录在 temp 中相应的下标存放数字的数量
    int[] counts = new int[10];
    // 根据最大长度的数决定比较次数
    for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
        // 每一个数字分别计算余数
        for (int j = 0; j < arr.length; j++) {
            // 计算余数
            int remainder = arr[j] / n % 10;
            // 把当前遍历的数据放到指定的数组中
            temp[remainder][counts[remainder]] = arr[j];
            // 记录数量
            counts[remainder]++;
        }
        // 记录取的元素需要放的位置
        int index = 0;
        // 把数字取出来
        for (int k = 0; k < counts.length; k++) {
            // 记录数量的数组中当前余数记录的数量不为 0
            if (counts[k] != 0) {
                // 循环取出元素
                for (int l = 0; l < counts[k]; l++) {
                    arr[index] = temp[k][l];
                    // 记录下一个位置
                    index++;
                }
                // 把数量置空
                counts[k] = 0;
            }
        }
    }
}

9、算法总结

排序法最好情形平均时间最差情形稳定度空间复杂度
冒泡排序O(n)O(n^2)O(n^2)稳定O(1)
快速排序O(nlogn)O(nlogn)O(n^2)不稳定O(nlogn)
直接插入排序O(n)O(n^2)O(n^2)稳定O(1)
希尔排序O(n)O(nlogn)O(nlogn)不稳定O(1)
直接选择排序O(n^2)O(n^2)O(n^2)不稳定O(1)
堆排序O(nlogn)O(nlogn)O(nlogn)不稳定O(nlogn)
归并排序O(nlogn)O(nlogn)O(nlogn)稳定O(n)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值