常见排序算法Java实现

本文详细介绍了多种排序算法,包括比较类的冒泡排序、快速排序、插入排序、选择排序和归并排序,以及非比较类的计数排序和桶排序。这些排序算法的时间复杂度从线性到平方级别不等,适用于不同的场景。对于每种排序算法,文章提供了实现代码和原理说明。
摘要由CSDN通过智能技术生成

排序方法分类

  • **比较类排序:**通过比较来决定元素间的相对次序 时间复杂度不能突破O(nlogn),因此又被称为非线性时间比较类排序;

    • 交换排序
      • 冒泡排序
      • 快速排序
    • 插入排序
      • (简单)插入排序
      • 希尔排序
    • 选择排序
      • (简单)选择排序
      • 堆排序
    • 归并排序
  • **非比较类排序:**不通过比较来决定元素的相对次序,时间复杂度可以突破到线性,又被称为线性时间非比较类排序。

    • 计数排序
    • 桶排序

冒泡排序 O(n^2)

不断比较相邻的两个元素,并交换位置,一次遍历确定一个最值放在最后。

// 冒泡排序:  稳定  时间复杂度O(n^2)  
public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = new int[]{5,2,8,3,7,0,-2,14};
        bubbleSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    private static void bubbleSort(int[] arr){
        int len = arr.length;
        for(int i = 1; i < len; i++){
            for(int j = 0; j < len - i; j++){
                if(arr[j] > arr[j+1]){
                    swap(arr, j, j+1);
                }
            }
        }
    }

    private static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

快速排序 O(nlogn) ★

整体流程:

  • 先从数列中取出一个数作为基准数pivot(一般取第一个数,但是可能会出现时间复杂度最差的情况)
  • 分区,把比pivot大的数都放在它的右边,比pivot小的数都放在它的左边
    • 设置两个遍历i、j,排序开始时取i=0,j=N-1
    • 以第一个元素为pivot,即pivot=arr[0]=arr[i]
    • 由后向前搜索(j–),找到第一个小于pivot的数arr[j],将arr[j]和arr[i]交换
    • 由前向后搜索(i++),找到第一个大于pivot的数arr[i],将arr[i]和arr[j]交换
    • 重复前两步,直到i==j,此时pivot左边的数都时比它小的(无序),右边的数都是比它大的(无序)
  • 对左右两个区间重复前两步,直到各个区间都只有一个数,此时排序就完成了
// 快速排序:  不稳定,时间复杂度为O(nlogn),当pivot为最值的时候,时间复杂度最差,为O(n^2)
public class QuickSort {
    public static void main(String[] args) {
        int[] arr = new int[]{5,2,8,3,7,0,-2,14};
        quickSort(arr, 0, arr.length-1);
        System.out.println(Arrays.toString(arr));
    }

    private static void quickSort(int[] arr, int low, int high){

        if(low < high){
            int i = low;
            int j = high;
            int pivot = arr[i];

            while(i < j){
                // 从前往后
                while (i < j && arr[j] > pivot){
                    j--;
                }
                swap(arr, i, j);

                // 从后往前
                while (i < j && arr[i] < pivot){
                    i++;
                }
                swap(arr, i, j);
            }

            // 当i=j的时候,开始递归,对左右两部分进行快排
            quickSort(arr,low,j-1);
            quickSort(arr, j+1, high);
        }
    }

    private static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

插入排序 O(n^2)

将n个待排序数组看成一个有序表和一个无序表,最开始有序表有1个元素,无序表有n-1个元素,排序时,每次从无序表中取出第一个元素,插入到有序表中的对应位置(从有序表的最后开始往前一个一个进行比较交换),使之成为新的有序表,重复n-1次即可完成排序。

// 插入排序:  稳定,时间复杂度为O(n^2)
public class InsertSort {
    public static void main(String[] args) {
        int[] arr = new int[]{5,2,8,3,7,0,-2,14};
        insertSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    private static void insertSort(int[] arr){
        for(int i = 1; i < arr.length; i++){
            // 取出无序表中的第一个元素arr[i],其前面就是有序表,从后往前逐次进行比较交换,找到其对应位置
            for(int j = i; j > 0; j--){
                if(arr[j] < arr[j-1]){
                    swap(arr, j, j-1);
                }
                else {
                    break;
                }
            }
        }
    }

    private static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

希尔排序 O(nlogn)-O(n^2)

希尔排序是对简单插入排序的改进,把数组按照下标的一定增量分组,对每组直接使用插入排序进行排序,随着增量的逐渐减小,每组包含的元素越来越多,当增量减至1时,整个元素被分为一组,算法便终止。

示例:

image-20220505210208618

image-20220505210312021

// 希尔排序:   不稳定(先分组才排序),时间复杂度为O(nlogn)-O(n^2)
public class ShellSort {
    public static void main(String[] args) {
        int[] arr = new int[]{5,2,8,3,7,0,-2,14};
        shellSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    private static void shellSort(int[] arr){
        // 每一个增量(间隔)排序一轮
        for(int gap = arr.length / 2; gap > 0; gap /= 2){
            // 注意i不从第一个元素开始,因为插入排序是从无序序列的第一元素开始,与前面的元素进行比较交换
            for(int i = gap; i < arr.length; i += gap){
                for(int j = i; j >= gap; j -= gap){
                    if(arr[j] < arr[j-gap]){
                        swap(arr, j, j-gap);
                    }else {
                        break;
                    }
                }
            }
        }
    }

    private static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

选择排序 O(n^2)

以升序排序为例,把数组也是分为无序序列和有序序列,每次从无序序列中选择出最小的元素,放到有序序列的末尾。

// 选择排序:  不稳定,时间复杂度为O(n^2)
public class SelectionSort {
    public static void main(String[] args) {
        int[] arr = new int[]{5,2,8,3,7,0,-2,14};
        selectionSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    private static void selectionSort(int[] arr){
        for(int i = 0; i < arr.length-1; i++){
            // 选择最小的
            int min_index = i;
            for(int j = i+1; j < arr.length; j++){
                if(arr[j] < arr[min_index]){
                    min_index = j;
                }
            }
            swap(arr, i, min_index);
        }
    }

    private static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

堆排序 O(nlogn) ★

前置知识:大顶堆、小顶堆;节点i的父节点为(i - 1) / 2,左子孩子为2 * i + 1,右子孩子为2 * i + 2;

步骤:

  • 根据初始数组取构造初始堆
  • 每次交换第一个和最后一个元素,输出最后一个元素(最大值),然后把剩下的元素重新调整为大根堆,再进行交换第一个元素和最后一个元素,再调整大顶堆,重复执行,直到整个数组排序完成(堆里只剩下一个元素)。

调整大顶堆:从最后一个非叶子节点(arr.length - 1 - 1) / 2开始,从右至左,从下至上进行调整,调整为大顶堆;

// 堆排序:   稳定,时间复杂度为建队O(n),排序O(nlogn),总为O(nlogn)
public class HeapSort {
    public static void main(String[] args) {
        int[] arr = new int[]{5,2,8,3,7,14,5,-3};
        heapSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void heapSort(int[] arr){
        // 初始建堆
        for(int i = arr.length/2-1; i >= 0; i--){
            heapify(arr, i, arr.length-1);
        }
        // 堆排序过程
        for(int i = arr.length-1; i > 0; i--){
            // 交换当前节点和堆顶节点
            swap(arr, 0, i);
            heapify(arr, 0, i-1);
        }
    }

    private static void heapify(int[] arr, int i, int last_index){
        int max = i;
        if(2*i+1 <= last_index && arr[2*i+1] > arr[max]){
            max = 2*i+1;    // 记左节点为最大值
        }
        if(2*i+2 <= last_index && arr[2*i+2] > arr[max]){
            max = 2*i+2;    // 记右节点为最大值
        }
        if(max != i){
            swap(arr, i, max);
            heapify(arr, max, last_index);
        }
    }

    private static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

归并排序 O(nlogn) ★

分治策略:

  • 分:递归拆分子序列,直到拆分成子序列长度为1;

  • 治:将两个已经有序的子序列合并成一个有序序列。

示例:

image-20220505214309875

// 归并排序:  稳定,时间复杂度为O(nlogn)
public class MergeSort {
    public static void main(String[] args) {
        int[] arr = new int[]{5,2,8,3,7,0,-2,14};
        mergeSort(arr, 0, arr.length-1);
        System.out.println(Arrays.toString(arr));
    }

    private static void mergeSort(int[] arr, int low, int high){
        if(low < high){
            int mid = low + (high - low) / 2;
            // 对左边进行归并排序
            mergeSort(arr, low, mid);
            // 对右边进行归并排序
            mergeSort(arr, mid+1, high);
            // 合并
            merge(arr, low, mid, high);
        }
    }

    private static void merge(int[] arr, int low, int mid, int high){
        // 创建一个临时数组,用于存储有序序列
        int[] tmp = new int[arr.length];
        int i = low;
        int j = mid+1;

        int k = 0;
        while(i <= mid && j <= high){
            if(arr[i] <= arr[j]){
                tmp[k++] = arr[i++];
            }else {
                tmp[k++] = arr[j++];
            }
        }

        // 如果左边还有剩余
        while (i <= mid){
            tmp[k++] = arr[i++];
        }

        // 如果右边有剩余
        while (j <= high){
            tmp[k++] = arr[j++];
        }

        // 把有序序列放回到原数组中
        k = 0;
        while (low <= high){
            arr[low++] = tmp[k++];
        }
    }
}

计数排序 O(n+k) ★

非比较排序算法,将输入的数据值转化为键存储在额外开辟的数组空间中。要求输入的数据必须是有确定范围的整数

示例:

image-20220505214927581image-20220505215002673

// 计数排序:  稳定排序,时间复杂度为O(n+k)
public class CountSort {
    public static void main(String[] args) {
        int[] arr = new int[]{5,2,8,3,7,14,5,-3};
        countSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    private static void countSort(int[] arr){
        // 先找出数组的最大值
        int max = arr[0];
        int min = arr[0];
        for(int i = 1; i < arr.length; i++){
            if(arr[i] > max){
                max = arr[i];
            }
            if(arr[i] < min){
                min = arr[i];
            }
        }

        // 创建计数数组
        int[] cnt = new int[max-min+1];
        for(int num : arr){
            cnt[num-min]++;
        }

        int k = 0;
        // 遍历计数数组
        for(int i = 0; i < cnt.length; i++){
            while (cnt[i]>0){
                arr[k++] = i + min;
                cnt[i]--;
            }
        }
    }
}

桶排序 O(n) ★

计数排序的改进。根据一定的映射规则/函数,把每一个元素放入到其对应的桶中,再对每一个桶中所有元素进行排序,最后依次输出每个桶中元素,即为有序序列。高效与否就在于这个映射规则。

示例:

image-20220506101826197

image-20220506101915569

// 桶排序:  稳定性取决于其桶内的排序算法稳定性,时间复杂度最好为O(n)
public class BucketSort {
    public static void main(String[] args) {
        int[] arr = new int[]{5,2,8,3,7,14,5,-3};
        bucketSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void bucketSort(int[] arr){
        // 先找出数组的最大值和最小值
        int max = arr[0];
        int min = arr[0];
        for(int i = 1; i < arr.length; i++){
            if(arr[i] > max){
                max = arr[i];
            }
            if(arr[i] < min){
                min = arr[i];
            }
        }

        // 桶的个数
        int count = (max - min) / arr.length + 1;
        List<ArrayList<Integer>> buckets = new ArrayList<>();
        for(int i = 0; i < count; i++){
            buckets.add(new ArrayList<Integer>());
        }

        // 遍历数组,把数据放在对应的桶里
        for(int i = 0; i < arr.length; i++){
            buckets.get((arr[i] - min) / arr.length).add(arr[i]);
        }

        // 对每个桶分别进行排序
        for(int i = 0; i < buckets.size(); i++){
            Collections.sort(buckets.get(i));
        }

        // 遍历每个桶里的每个数据,输出到arr
        int k = 0;
        for(int i = 0; i < buckets.size(); i++){
            ArrayList<Integer> list = buckets.get(i);
            for(int j = 0; j < list.size(); j++){
                arr[k++] = list.get(j);
            }
        }
    }
}
] - min) / arr.length).add(arr[i]);
        }

        // 对每个桶分别进行排序
        for(int i = 0; i < buckets.size(); i++){
            Collections.sort(buckets.get(i));
        }

        // 遍历每个桶里的每个数据,输出到arr
        int k = 0;
        for(int i = 0; i < buckets.size(); i++){
            ArrayList<Integer> list = buckets.get(i);
            for(int j = 0; j < list.size(); j++){
                arr[k++] = list.get(j);
            }
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值