【Java】—— 常见的排序算法

发布于:2025-05-22 ⋅ 阅读:(15) ⋅ 点赞:(0)

一、排序的概念

排序的定义:将一组数据按照特定的顺序重新排列的过程。

排序的目的:是使数据更易于查找、比较和分析。

排序的适应范围:可以应用于各种数据类型。

排序的稳定性:指当出现相同元素时,排序后相同元素的先后顺序保持不变,则称这个排序为稳定排序

内部排序:指在内存中进行排序

外部排序:指借助硬盘完成排序

二、常见排序算法的实现

1、直接插入排序

核心思想:

将数组分为 已排序区间 和 未排序区间 , 初始情况下,已排序区间就是空,未排序区间就是整个数组,每次从 未排序区间 中选择一个元素,插入到 已排序区间 的合适位置上。

代码实现:

    private static void insertSort(int[] arr){
        //bound 变量表示边界
        //[0,bound) 区间表示有序区间
        //[bound,arr.length) 区间是无序区间
        for(int bound = 1; bound < arr.length ; bound++ ){
            //循环内部进行一次的插入操作
            //value 就是无序区间的第一个元素
            int value = arr[bound];
            //用这个元素在有序区间从后向前比较
            int cur = bound - 1;
            for(;cur >= 0 ; cur--){
                //前一个的值大于bound那么
                if(arr[cur] > value){
                    arr[cur + 1] = arr[cur];
                }else {
                    break;
                }
            }
            //交换元素
            arr[cur+1] = value;
        }
    }

特性:元素集合越接近有序,直接插⼊排序算法的时间效率越⾼

时间复杂度:O(n^2)

空间复杂度:O(1);

稳定性:稳定

2、希尔排序

核心思想:

希尔排序是对插入排序的一种优化,引入了 gap(间隔) 的概念,将元素分成多个组,并分别进行插入排序。再不断缩小 gap 直到 gap 为 1 时,就是普通的插入排序。

代码实现:

    private static void shellSort(int[] arr){
        //计算一个合适的gap值,再不断缩小
        int gap = arr.length / 2;
        while (gap >= 1){
            insertSortGap(arr,gap);
            gap /= 2;
        }
    }
    private static void insertSort(int[] arr){
        //bound 变量表示边界
        //[0,bound) 区间表示有序区间
        //[bound,arr.length) 区间是无序区间
        for(int bound = 1; bound < arr.length ; bound++ ){
            //循环内部进行一次的插入操作
            //value 就是无序区间的第一个元素
            int value = arr[bound];
            //用这个元素在有序区间从后向前比较
            int cur = bound - 1;
            for(;cur >= 0 ; cur--){
                //前一个的值大于bound那么
                if(arr[cur] > value){
                    arr[cur + 1] = arr[cur];
                }else {
                    break;
                }
            }
            //交换元素
            arr[cur+1] = value;
        }
    }

希尔排序的时间复杂度不好计算,因为 gap 的取值有很多

3、直接选择排序

核心思想:

将元素划分为 有序区间 和 无序区间,初始情况下 有序区间 为 空 ,每一次遍历都从无序区间里找到最小值,把这个最小值依次放到有序区间

代码实现:

    private static void selectSort(int[] arr){
        for(int bound = 0;bound < arr.length - 1;bound++){
            for (int cur = bound + 1 ; cur < arr.length ; cur++){
                if(arr[cur] < arr[bound]){
                    int temp = arr[cur];
                    arr[cur] = arr[bound];
                    arr[bound] = temp;
                }
            }
        }
    }

时间复杂度:O(N^2)

空间复杂度:O(1)

稳定性:不稳定

4、堆排序

核心思想:

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一 种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

代码实现:

    private static void selectSort(int[] arr){
        for(int bound = 0;bound < arr.length - 1;bound++){
            for (int cur = bound + 1 ; cur < arr.length ; cur++){
                if(arr[cur] < arr[bound]){
                    int temp = arr[cur];
                    arr[cur] = arr[bound];
                    arr[bound] = temp;
                }
            }
        }
    }
    private static void headSort(int[] arr){
        //1.建堆
        createHeap(arr);
        int bound = arr.length - 1;
        for(int i = 0; i < arr.length ; i++){
            //交换堆顶和待排序区间的最后一个元素
            int temp = arr[0];
            arr[0] = arr[bound];
            arr[bound] = temp;
            //重新调整堆
            //通过 bound 表示堆的元素个数
            shiftDown(arr,bound,0);
            //最后一个元素归入到 已排序区间
            bound--;
        }
    }
    private static void createHeap(int[] arr){
        //从最后一个非叶子节点触发,从后往前遍历
        for(int i=(arr.length-1-1)/2 ; i >= 0 ; i--){
            shiftDown(arr, arr.length, i);
        }
    }
    private static void shiftDown(int[] arr,int length,int index){
        int parent = index;
        int child = 2 * parent + 1;
        while (child < length){
            //建立大堆,需要取左右子节点中较大值
            if(child + 1 < length && arr[child + 1] > arr[child]){
                child += 1;
            }
            //比较 child 和 parent 元素大小
            if(arr[child] > arr[parent]){
                int temp = arr[child];
                arr[child] = arr[parent];
                arr[parent] = temp;
            }else{
                break;
            }
            parent = child;
            child = 2 * parent + 1;
        }
    }

时间复杂度:O(N*logN)

空间复杂度:O(1)

稳定性:不稳定

5、冒泡排序

核心思想:

通过不断比较相邻元素,一趟过后,最大(最小)的一个元素就会在数组中最末尾的位置,再进行 n - 1 次就可以达到有序

代码实现:

    private static void bubbleSort(int[] arr){
        for(int bound = 0;bound < arr.length-1 ; bound++){
            for (int cur = arr.length - 1 ; cur > bound ; cur--){
                if(arr[cur-1] > arr[cur]){
                    int temp = arr[cur - 1];
                    arr[cur - 1] = arr[cur];
                    arr[cur] = temp;
                }
            }
        }
    }

时间复杂度:O(N^2)

空间复杂度:O(1)

稳定性:稳定

6、快速排序

核心思想:

任取待排序区间中选出某元素作为基准值,按照该排序码将待排序集合分割成两个子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

代码实现:

    //此处约定[left,right]闭区间为待处理区间
    //快速排序入口
    private static void quickSort(int[] arr){
        quickSort(arr,0, arr.length-1);
    }
    //快速排序辅助递归的方法
    private static void quickSort(int[] arr,int left,int right){
        if(left >= right){
            //待处理区间为空或者只有一个元素,无需排序
            return;
        }
        //针对区间进行整理,返回值 index 表示基准值所在的下标
        int index = partition(arr,left,right);
        //递归左半区间
        quickSort(arr,left,index-1);
        //递归右半区间
        quickSort(arr,index+1,right);
    }
    private static int partition(int[] arr,int left,int right){
        //设定最右侧元素为基准值
        int value = arr[right];
        //设定两个下标,分别从左往右和右往左
        int l = left;
        int r = right;
        while (l < r){
            while (l < r && arr[l] <= value){
                l++;
            }
            while (l < r && arr[r] >= value){
                r--;
            }
            swap(arr,l,r);
        }
        //循环完成交换重合位置和基准值
        swap(arr,l,right);
        return l;
    }
    private static void swap(int[] arr,int i,int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

快速排序在大多数情况下的表现都是非常优秀的,除了个别极端情况会让时间复杂度到O(N^2)

时间复杂度:O(N*logN)

空间复杂度:O(logN)

稳定性:不稳定

7、归并排序

核心思想:

通过递归将待排序的数组分成两个子数组,直到每个子数组只包含一个元素。此时,单个元素的数组自然是有序的。将两个有序的子数组合并成一个有序的数组。合并时,从两个子数组的起始位置开始比较,选择较小的元素放入结果数组中,直到其中一个子数组的所有元素都被合并。然后将另一个子数组的剩余元素直接放入结果数组中。

代码实现:

    //归并排序入口方法
    private static void mergeSort(int[] arr){
            mergeSort(arr,0,arr.length-1);
    }
    private static void mergeSort(int[] arr,int left,int right){
        //如果子区间没有元素或只有一个元素就不再递归
        if(left >= right){
            return;
        }
        //把区间分为等长的两个区间,不强制完全相等
        int mid = (left + right) / 2;
        //递归左半区间
        mergeSort(arr,left,mid);
        //递归右边区间
        mergeSort(arr,mid+1,right);
        //递归走到这,说明已经划分为只剩一个元素的有序数组
        //将有序数组合并
        merge(arr,left,mid,right);
    }
    private static void merge(int[] arr,int left,int mid,int right){
        //创建临时数组保存合并的结果,临时数组的长度应该是 right - left + 1
        int[] result = new int[right - left + 1];
        //通过判断大小尾插到新数组中
        int resultSize = 0;
        //两个下标指向每个区间的开头
        int cur1 = left;
        int cur2 = mid + 1;
        while (cur1 <= mid && cur2 <= right){
            if(arr[cur1] <= arr[cur2]){
                result[resultSize++] = arr[cur1];
                cur1++;
            }else{
                result[resultSize++] = arr[cur2];
                cur2++;
            }
        }
        //处理前面奇数划分不均的情况
        while (cur1 <= mid){
            result[resultSize++] = arr[cur1++];
        }
        while (cur2 <= right){
            result[resultSize++] = arr[cur2++];
        }
        //把临时数组的结果写回到 arr 的 [left,right] 中
        for(int i=0;i<result.length;i++){
            arr[left + i] = result[i];
        }
    }

归并排序的思考更多的是解决在磁盘中的外排序问题

时间复杂度:O(N*logN)

空间复杂度:O(N)

稳定性:稳定

三、小结

除了上文提到的这几种,还有许多其他的排序算法,例如 计数排序 ,基数排序 ,桶排序 等等,应该结合实际情况选择使用,学好排序算法的原理可以用这些思想去解决其他的问题。


网站公告

今日签到

点亮在社区的每一天
去签到