IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 七大排序算法详解 -> 正文阅读

[数据结构与算法]七大排序算法详解

?前言?

定义: 排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。平时的上下文中,如果提到排序,通常指的是排升序(非降序)。通常意义上的排序,都是指的原地排序

稳定性: 两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则我们称该算法是具备稳定性的排序算法

在这里插入图片描述

🍓 一,插入排序

1,直接插入排序

(1)原理

首先分为有序区间(0,i),即(0,1),无序区间(i,n),默认第一个元素是有序的,每次选择无序区间的第一个元素,插入在有序区间的合适位置

在这里插入图片描述

(2)实现`

 /**
     * 直接插入排序
     * 每次选择无序区间的第一个元素,插入到有序区间的合适位置
     * @param arr
     */
    public static void insertionSortBase(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            for (int j = i; j > 0 && arr[j-1]>arr[j]; j--) {
                swap(arr,j,j-1);
                // arr[j] > arr[j - 1]此时,循环直接终止
                // j - 1已经是有序区间元素,大于前面的所有值
            }
        }
    }
     private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

(3)稳定性-时间复杂度

?稳定性
根据查入的位置,插入排序在排序的过程中,始终保持元素的在整体元素中的相对位置不变,所以是稳定的排序算法。

?时间复杂度
通过分析可以知道,在最好的情况下,即是数组本身有序的情况下,只需要遍历一遍即可,这时候的时间复杂度取决于元素个数,为:O(n)。

在最坏的情况下,内外都要循环一次,所以这时候时间复杂度为O(n2)。

平均时间复杂度为:O(n2)

2,希尔排序

(1)原理

不断地进行gap分组,组内排序,直到gap=1时,整个数组插入排序。其中插入排序最外层是从gap走到数组的末尾,最内层是从gap索引向前看,看距离gap长度的元素,j为当前元素,j-gap是距离gap长的元素,两者进行比,当j-gap>=0,说明还有元素,交换元素的位置

(2)具体实现

  /**
     *希尔排序
     * 不断地进行gap分组,组内排序,直到gap=1时,整个数组插入排序
     */
    public static void shellSort(int[] arr) {
        int gap = arr.length/2;
        while(gap > 1){
            //进行分组,组内插入排序
            insertionSortGap(arr,gap);
            gap/=2;
        }
        //整个数组的插入排序
        insertionSortGap(arr,1);
    }
    /**
     *根据gap分组,进行排序
     * @param gap 传入的步长
     */
    private static void insertionSortGap(int[] arr, int gap) {
        //最外层是从gap走到数组的末尾
        for (int i = gap; i < arr.length; i++) {
            //最内层是从gap索引向前看
            for (int j = i; j-gap>=0 && arr[j]<arr[j-gap]; j=j-gap) {
                swap(arr,j,j-gap);
            }
        }
    }
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

(3)稳定性-时间复杂度

🍌稳定性
在排序完成后,两个相同的元素位置(上面图示的7*和7)位置发生了改变,所以是不稳定的

🍌时间复杂度
介于O( n ^2)和O(nlogn)之间,视它的最好最坏以及平均复杂度具体分析
稳定性:

🍒二,选择排序

1,直接选择排序

(1)原理

每次将无序区间最小值放在有序区间的第一个,开始默认第一个元素最小,有序区间[0,i),无序区间[i+1,n), 类似于冒泡,循环到最后一次有序

(2)具体实现

 /**
     * 直接选择排序
     */
    public static void selectionSort(int[] arr) {
        for (int i = 0; i < arr.length-1; i++) {
            //min记录最小值
            int min = i;
            for (int j = i+1; j <arr.length ; j++) {
                if(arr[j] < arr[min]){
                    min = j;
                }
            }
            //此时min已经保存最小值下标,将min换到最前面
            swap(arr,i,min);
        }
    }
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

(3)稳定性-时间复杂度

🍓 稳定性
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

🍓时间复杂度
第一次内循环比较N - 1次,然后是N-2次,N-3次,……,最后一次内循环比较1次。
共比较的次数是 (N - 1) + (N - 2) + … + 1,求等差数列和,得 (N - 1 + 1)* N / 2 = N^2 / 2。
舍去最高项系数,其时间复杂度为 O(N^2)。
虽然选择排序和冒泡排序的时间复杂度一样,但实际上,选择排序进行的交换操作很少,最多会发生 N - 1次交换。
而冒泡排序最坏的情况下要发生N^2 /2交换操作。从这个意义上讲,交换排序的性能略优于冒泡排序。

(4)优化-双向选择排序

每一次从无序区间选出最小 + 最大的元素,存放在无序区间的最前和最后,直到全部待排序的数据元素排完 。

/**
     * 双向选择排序
     * @param array
     */
    public static void selectSortOP(int[] array) {
        int low = 0;
        int high = array.length - 1;
   // [low, high] 表示整个无序区间
   // 无序区间内只有一个数也可以停止排序了
        while (low <= high) {
            int min = low;
            int max = low;
            for (int i = low + 1; i <= max; i++) {
                if (array[i] < array[min]) {
                    min = i;
                }
                if (array[i] > array[max]) {
                    max = i;
                }
            }
            swap(array, min, low);
            if (max == low) {
                max = min;
            }
            swap(array, max, high);
        }
    }

array = { 9, 5, 2, 7, 3, 6, 8 }; // 交换之前
// low = 0; high = 6
// max = 0; min = 2
array = { 2, 5, 9, 7, 3, 6, 8 }; // 将最小的交换到无序区间的最开始后
// max = 0,但实际上最大的数已经不在 0 位置,而是被交换到 min 即 2 位置了
// 所以需要让 max = min 即 max = 2
array = { 2, 5, 8, 7, 3, 6, 9 }; // 将最大的交换到无序区间的最结尾后

2,堆排序

(1)原理

(1)将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆
(2)将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端
(3)重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序

(2)实现-时间复杂度

? 博客链接:?
https://blog.csdn.net/qq_55660421/article/details/122380669

🍓文章目录
🍌一,堆排序
1,什么是堆排序
2,堆排序思想
3,代码实现
🍇二,时间复杂度分析
1,初始化建堆
2,排序重建堆
3,总结

🌸三,交换排序

1,冒泡排序

(1)原理

每次冒泡排序操作都会将相邻的两个元素进行比较,看是否满足大小关系要求,如果不满足,就交换这两个相邻元素的次序,一次冒泡至少让一个元素移动到它应该排列的位置,重复N次,就完成了冒泡排序。

在这里插入图片描述

(2)具体实现

 /**
     *冒泡排序法
     */
    public void bubbleSort(int[] arr, int n){
        if(n<=1){
            return;
        }
        for(int i=0;i < n;i++){
            boolean flag = false;
            for (int j = 0; j < n-i-1; j++) {
                if (arr[i - 1] > arr[i]) {
                    swap(arr, i - 1, i);
                    flag = true;
                }
            }
            if(!flag) break;//没有数据交换,数组已经有序,退出排序
        }
    }
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

🌸🌸时间复杂度

原操作(基本操作)为交换操作,当数组按从小到大有序排列时,基本操作执行次数为0,当自大到小有序排列时,基本操作次数为n(n-1)/2,一般情况下讨论算法在最坏的情况下时间复杂度(个别取平均),所以时间复杂度为O(n^2).

2,快速排序

(1)原理思路

快速排序主要采用分治的基本思想,每次将一个位置上的数据归位,此时该数左边的所有数据都比该数小,右边所有的数据都比该数大,然后递归将已归位的数据左右两边再次进行快排,从而实现所有数据的归位。

(2)具体分析

 /**
     * 在l..r上进行快速排序
     * @param arr
     * @param l
     * @param r
     */
    private static void quickSortInternal(int[] arr, int l, int r) {
        // 递归终止时,小数组使用插入排序
        if (r - l <= 15) {
            insertBase(arr,l,r);
            return;
        }
        //选择基准值,找到该值对应的下标
        int p = partition(arr,l,r);
        quickSortInternal(arr,l,p-1);
        quickSortInternal(arr,p+1,r);
    }
    /**
     * 在arr[l..r]上选择基准值,将数组划分为 < v 和 >= v两部分,返回基准值对应的元素下标
     */
    private static int partition(int[] arr, int l, int r) {
        //随机选择基准值
        int randomIndex = random.nextInt(l,r);
        swap(arr,l,randomIndex);
        int v = arr[l];
        // arr[l + 1...j] < v
        int j = l;
        // i是当前处理的元素下标
        for (int i = l+1; i <= r; i++) {
            if(arr[i] < v){
                swap(arr,j+1,i);
                // 小于v的元素值新增一个
                j++;
            }
        }
        // 此时j下标对应的就是最后一个 < v的元素,交换j和l的值,选取的基准值交换j位置
        swap(arr,l,j);
        return j;
    }

(3)稳定性-时间复杂度

🍒稳定性🍒

快速排序有两个方向,左边的i下标一直往右走(当条件a[i] <= a[center_index]时),其中center_index是中枢元素的数组下标,一般取为数组第0个元素。
在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 9 10 11
现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱。
所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j]交换的时刻。

🍎时间复杂度🍎
最坏情况,
需要进行n‐1递归调用,其空间复杂度为O(n),
平均情况为O(logn)。

🍌四,归并排序

(1)原理

归并排序是一种概念上最简单的排序算法,与快速排序一样,归并排序也是基于分治法的。归并排序将待排序的元素序列分成两个长度相等的子序列,为每一个子序列排序,然后再将他们合并成一个子序列。合并两个子序列的过程也就是两路归并。

(2)具体实现

 /**
     * 在arr[l...r]上进行归并排序
     * @param arr
     * @param l
     * @param r
     */
    private static void mergeSortInternal(int[] arr, int l, int r) {
        if (r - l<= 15) {
            // 拆分后的小区间直接使用插入排序,不再递归
            insertBase(arr,l,r);
            return;
        }
        int mid = l + ((r - l) >> 1);
        // 先排序左半区间
        mergeSortInternal(arr,l,mid);
        // 再排序右半区间
        mergeSortInternal(arr,mid + 1,r);
        // 不是上来就合并,当两个小区间之前存在乱序时才合并
        if (arr[mid] > arr[mid + 1]) {
            merge(arr,l,mid,r);
        }
    }
    /**
     * 将已经有序的arr[l..mid] 和[mid + 1..r]合并为一个大的有序数组arr[l...r]
     * @param arr
     * @param l
     * @param mid
     * @param r
     */
    private static void merge(int[] arr, int l, int mid, int r) {
        int[] temp = new int[r - l + 1];
        // 将原数组内容拷贝到新数组中
        for (int i = l; i <= r; i++) {
            temp[i - l] = arr[i];
        }
        int i = l;
        int j = mid + 1;
        // k表示当前处理到原数组的哪个位置
        for (int k = l; k <= r; k++) {
            if (i > mid) {
                arr[k] = temp[j - l];
                j ++;
            }else if (j > r) {
                arr[k] = temp[i - l];
                i ++;
            }else if (temp[i - l] <= temp[j - l]) {
                arr[k] = temp[i - l];
                i ++;
            }else {
                arr[k] = temp[j - l];
                j ++;
            }
        }
    }

(3)稳定性-时间复杂度

🍓(1)可以说合并排序是比较复杂的排序,特别是对于不了解分治法基本思想的同学来说可能难以理解。总时间=分解时间+解决问题时间+合并时间。分解时间就是把一个待排序序列分解成两序列,时间为一常数,时间复杂度o(1).解决问题时间是两个递归式,把一个规模为n的问题分成两个规模分别为n/2的子问题,时间为2T(n/2).合并时间复杂度为o(n)。总时间T(n)=2T(n/2)+o(n).这个递归式可以用递归树来解,其解是o(nlogn).此外在最坏、最佳、平均情况下归并排序时间复杂度均为o(nlogn).从合并过程中可以看出合并排序稳定。

🍓(2)从这个递归树可以看出,第一层时间代价为cn,第二层时间代价为cn/2+cn/2=cn…每一层代价都是cn,总共有logn+1层。所以总的时间代价为cn*(logn+1).时间复杂度是o(nlogn).
在这里插入图片描述

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-02-09 20:57:15  更:2022-02-09 20:57:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 18:21:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码