【算法学习】常见的排序算法

1. 冒泡排序

冒泡排序是一种简单直观的排序算法,其基本思想是从左到右不断比较相邻的两个元素,如果顺序不符合要求就进行交换。通过多轮的比较和交换,最终将最大(或最小)的元素移动到末尾,重复这个过程直到所有元素排好序。

动画演示
这是交换元素的方法,下面都会使用该方法进行数组交换元素:

    /**
     * 交换元素
     */
    private static void swap(int[] arr, int i, int j) {
        // 使用中间变量
        /*int temp = arr[j];
        arr[j] = arr[i];
        arr[i] = temp;*/
        // 不使用中间变量,速度快 但是必须保证 i != j,否则会清除其中一个数据
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    }
    /**
     * 冒泡排序
     * 平均时间复杂度:O(n^2)
     * 最坏时间复杂度:O(n^2)
     * 空间复杂度:O(1)
     * 稳定排序
     */
    public void BubblingSort(int[] arr) {
//        记录最后一次交换的位置
        int lastExchangeIndex = 0;
//        无序数列的边界,每次比较只需要比到这里为止
        int sortBorder = arr.length - 1;
        for (int i = 0; i < arr.length - 1; i++) {
//            有序标记,每一轮初始值都为true
            boolean isSorted = true;
            for (int j = 0; j < sortBorder; j++) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j+1);
//                    有元素交换,所以不是有序的,标记变为flase
                    isSorted = false;
//                    更新最后一次交换元素的位置
                    lastExchangeIndex = j;
                }
            }
            sortBorder = lastExchangeIndex;
            if (isSorted) {
                break;
            }
        }
    }

应用场景:当待排序的数据规模相对较小的时候,冒泡排序是一种简单且容易实现的选择。但是由于其时间复杂度较高,在大规模数据的排序中性能较差。

2. 插入排序

插入排序的基本思想是将数据分为已排序和未排序两部分,初始时已排序部分只包含第一个元素,然后将未排序的第一个元素插入到已排序部分的合适位置,使得已排序部分仍然有序。重复这个过程直到所有元素都插入到已排序部分。

在这里插入图片描述

    /**
     * 插入排序
     * 平均时间复杂度:O(n^2)
     * 最坏时间复杂度:O(n^2)
     * 空间复杂度:O(1)
     * 稳定排序
     */
    public void InsertSort(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            // 从第二个数开始往前比较,确保前i个元素有序
            for (int j = i-1; j >= 0 && arr[j] > arr[j+1]; j--) {
                swap(arr, j, j+1);
            }
        }
    }

应用场景:插入排序适用于部分有序的序列,当数据规模较小或者已经有部分元素有序时,插入排序的性能较好。

3. 选择排序

选择排序的基本思想是每次都找出未排序部分的最小元素,并将其与未排序部分的第一个元素交换位置,重复这个过程直到所有元素都排好序。

在这里插入图片描述

	/**
     * 插入排序
     * 平均时间复杂度:O(n^2)
     * 最坏时间复杂度:O(n^2)
     * 空间复杂度:O(1)
     * 不稳定排序
     */
    public static void selectSorting(int[] arr) {
        int index = 0;
        for (int i = 0; i < arr.length - 1; i++) {
            int min = arr[i];
            for (int j = i + 1; j < arr.length; j++) {
                if (min > arr[j]) {
                    min = arr[j];
                    index = j;
                }
            }
            if (index != i) {
                arr[index] = arr[i];
                arr[i] = min;
            }
        }
    }

应用场景:选择排序适用于数据规模较小且对排序稳定性没有要求的场景。由于选择排序每次只交换一次元素,因此在某些情况下对于交换操作比较敏感,可以考虑使用选择排序。

4. 快速排序

快速排序是一种分治的排序算法,它的基本思想是选择一个基准元素,通过一趟扫描将待排序的序列分成两个部分,其中一部分的所有元素都比基准元素小,另一部分的所有元素都比基准元素大。然后对这两部分分别进行快速排序,最终使整个序列有序。

在这里插入图片描述

    /**
     * 快速排序
     * 平均时间复杂度:O(nlogn)
     * 最坏时间复杂度:O(n^2)
     * 空间复杂度:O(1)
     * 不稳定排序
     */
    public void QuickSort(int[] arr) {
        QuickSort(arr, 0, arr.length-1);
    }
    //    单边循环法
    private void quickSort(int[] arr,int left,int right){
//        递归结束条件
        if(left>=right){
            return;
        }
        int pivot = partition(arr, left, right);
        quickSort(arr,left,pivot-1);
        quickSort(arr,pivot+1,right);
    }
    //    单边循环法获取基准元素方法
    private static int partition(int[] arr,int startIndex,int endIndex){
        int pivot = arr[startIndex];
        int mark = startIndex;
        for (int i = startIndex+1; i < endIndex; i++) {
            if(arr[i]<pivot){
                mark++;
                int p = arr[mark];
                arr[mark] = arr[i];
                arr[i] = p;
            }
        }
        arr[startIndex] = arr[mark];
        arr[mark] = pivot;
        return mark;
    }

应用场景:快速排序适用于各种数据规模的排序。由于其时间复杂度相对较低,在实际应用中被广泛使用。

5. 归并排序

归并排序也是一种分治的排序算法,它的基本思想是将待排序的序列划分为两个子序列,分别进行递归排序,然后将两个已排序的子序列合并成一个有序序列。

在这里插入图片描述

    /**
     * 归并排序
     * 平均时间复杂度:O(nlogn)
     * 最坏时间复杂度:O(nlogn)
     * 空间复杂度:O(n)
     * 不稳定排序
     */
    public void MergerSort(int[] arr) {
        mSort(arr, 0, arr.length-1);
    }
    private static void mSort(int[] arr, int left, int right) {
        if (left == right){
            return;
        }
        // 递归算法 主要负责切片 即把大的数组切成一个小数组
        int mid = left + ((right-left) >> 1);
        mSort(arr, left, mid);
        mSort(arr, mid+1, right);
        merger(arr, left, mid, right);
    }
    private static void merger(int[] arr, int left, int mid, int right){
        int[] temp = new int[right-left+1];
        int i = 0;
        int p1 = left; // 指针1从左数组开始遍历
        int p2 = mid + 1; // 指针2从右数组开始遍历
        while (p1 <= mid && p2 <= right){
            temp[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }
        // 左边还没遍历完
        while (p1 <= mid) {
            temp[i++] = arr[p1++];
        }
        // 右边没遍历完
        while (p2 <= right) {
            temp[i++] = arr[p2++];
        }
        for (i = 0; i < temp.length; i++) {
            arr[left+i] = temp[i];
        }
    }

应用场景:归并排序适用于需要稳定排序且数据规模较大的场景。

6. 堆排序

堆排序是一种基于二叉堆的排序算法,其基本思想是将待排序的序列构建成一个二叉堆,然后重复从堆顶取出最大(或最小)元素放到已排序部分的末尾,并调整剩余元素的位置,直到所有元素都排好序。
在这里插入图片描述

    /**
     * 堆排序
     * 不稳定
     * 时间复杂度度O(nlogn)
     */
    public void HeapSort(int[] arr){
        // 用数组模拟堆要保证:左子树为2i+1,右子树为2i+2,父节点为(i-1)/2
        for (int i = 0; i < arr.length; i++) {
            int index = i;
            while (arr[index]>arr[(index-1)/2]){
                swap(arr, index, (index-1)/2);
                index = (index-1)/2;
            }
        }
        int heapSize = arr.length;
        swap(arr, 0, --heapSize);
        while (heapSize > 0) {
            adjustHeap(arr, 0, heapSize);
            swap(arr, 0, --heapSize);
        }
    }
    /**
     * 将一个数组调整成一个大顶堆
     */
    private static void adjustHeap(int[] arr, int index, int heapSize){
        int left = index * 2 + 1;   // 左孩子下标
        // 判断是否有孩子
        while (left < heapSize) {
            // 两个孩子中谁值大 就赋给largest
            int largest = left+1 < heapSize && arr[left+1] > arr[left]
                    ? left+1 : left;
            // 父子之间谁值大 就赋给largest
            largest = arr[largest] > arr[index] ? largest:index;
            if (largest == index){
                break;
            }
            swap(arr, largest, index);
            index = largest;
            left = index*2+1;
        }
    }

应用场景:堆排序主要用于需要保持序列动态更新的场景,例如实时排行榜等。

7. JDK8提供的排序工具类

当然除了手动实现排序算法,Java也提供了一些排序工具类,例如Arrays.sort()和Collections.sort()。这些工具类使用了高效的排序算法,并且对不同类型的数据进行了优化,可以满足绝大部分排序需求。我们在实际开发中,可以优先选择使用JDK提供的排序工具类。

public class SortingExample {
    public static void main(String[] args) {
        // 对数组进行排序
        Integer[] numbers = {5, 2, 8, 10, 1};
        Arrays.sort(numbers);
        System.out.println("数组排序结果:" + Arrays.toString(numbers));

        // 对List进行排序(升序)
        List<Integer> numberList = Arrays.asList(5, 2, 8, 10, 1);
        Collections.sort(numberList);
        System.out.println("List排序结果(升序):" + numberList);

        // 对List进行排序(降序)
        List<Integer> numberList2 = Arrays.asList(5, 2, 8, 10, 1);
        Collections.sort(numberList2, Collections.reverseOrder());
        System.out.println("List排序结果(降序):" + numberList2);
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值