基础排序算法—冒泡,插入,选择

前言

冒泡,插入,选择这三种基础的排序算法,比较简单效率不高,工作中一般不会使用,但是当做算法来研究还是能了解一些知识的,本文以<数据结构与算法之美>为基础,详细解析一下.

正文

首先要引入几个概念

稳定性

如果待排序数组中有相同的元素,排序过后它们的相对顺序不发生变化. 比如 2,9,3,4,8,3 排序过后为2, 3 , 3, 4, 8, 9 这两个3的相对顺序不变.这样就是具有稳定性. 稳定性可以保证复杂数据结构排序时的相对有序性. 比如我们要对一笔订单先按金额排列,金额相同的再按时间排序

就可以先将这笔订单按照时间排序,再采用具有稳定性的排序算法按金额进行排序. 猜测Java8中的compare().thenCompare()就是采用这种原理实现

原地排序

排序过程中只占用常数级的额外空间或者不占用额外空间

有序度

数组中具有有序关系的元素对的个数,数学表达式为 有序元素对:a[i] <= a[j], 如果i < j。

2,4,3,1,5,6 的有序度为11,有序元素对为(2,4),(2,3),(2,5),(2,6),(4,5),(4,6),(3,5),(3,6),(1,5),(1,6),(5,6)

6,5,4,3,2,1 这个完全逆序的数组,有序度为0

1,2,3,4,5,6 这个完全有序的数组,有序度为(n-1)+(n-2)+(n-3)+...+0 = n*(n-1)/2 = 15,即满有序度

逆序度

和有序度定义正好相反 逆序度 = 满有序度 - 有序度

约定

为了方便描述,这里约定数组为arr[0~n],n≥1,排列方式为正序(从小到大).

下面的描述不会举例讲解,推荐到 visualgo查看动画演示

冒泡排序

主要思路

首先将数组在逻辑划分为左侧的未排序部分和右侧的已排序部分

冒泡排序中对未排序部分的一次遍历称为一次冒泡,初始时未排序部分为arr[0~n-1],未排序部分为空.冒泡一次后,未排序部分为arr[0~n-2],已排序部分为arr[n-1~n-1],冒泡x(1≤x<n)次后,未排序部分为arr[0~n-1-x),已排序部分为arr[n-x~n-1],最终达成全部排序

每次冒泡未排序部分时,只会操作相邻的元素,方向为 arr[0]→ arr[n-1],如果a[i]>a[i+1]就进行交换,这样一次冒泡操作后,未排序部分中的最大值就会移动到已排序部分且在最左侧(即已排序部分的最小值).

同时,如果一次冒泡后没有进行过任何交换说明整个数组已经有序了,结合这个特性可以省去不必要的冒泡,核心代码如下

public void sort(Comparable[] a) {
        if (a.length <= 1) {
            return;
        }
        for (int i = a.length - 1; i > 0; i--) {
            boolean changeFlag = false;
            for (int j = 0; j < i; j++) {
                if (a[j + 1].compareTo(a[j]) < 0) {
                    swap(a, j + 1, j);
                    changeFlag = true;
                }
            }
            if (!changeFlag) {
                break;
            }
        }
    }

时间复杂度

最坏情况下,数组完全倒序, 时间复杂度为 (n-1)+(n-2)+(n-3)+...+1 = (n^2)/2 ~= O(n^2) 数字表示交换的次数

最好情况下,数组已经有序,只需要一次冒泡, 时间复杂度为 O(n) 这里不会有交换,以比较的次数作为计量单位

平均情况下,用概率推算会很困难,这里采用上面提到的有序度逆序度,并将元素的交换次数当做计量单位, 观察可以看到,每交换一次,都会使原来逆序的一组元素变为有序,逆序度即为交换次数,那么在最好情况下逆序度为0,最坏情况下逆序度为n(n-1)/2,平均时间复杂度为两者取平均值

(0 + n(n-1)/2)/2 = O(n^2)

是原地排序吗?

冒泡排序涉及到两个元素的交换,交换时只使用了一个单位临时空间,是原地排序

稳定吗?

冒泡排序中元素位置发生变化交换时,并且交换仅在a[i]>a[i+1]时才发生,a[i]==a[i+1]时时不会发生的,因此是稳定的

插入排序

主要思路

将数组分为左侧的已排序部分和右侧的未排序部分

初始时已排序部分为arr[0~0],未排序部分为arr[1~n-1]一次插入后,已排序部分为arr[0~1],未排序部分为arr[2~n-1],x(1≤x<n)次插入后,已排序部分为arr[0x],未排序部分为arr(x+1n-1]

从未排序部分的起始位置开始(即arr[1]),每次插入时,方向为 arr[n-1]→ arr[0],找出第一个小于等于arr[i]的元素,有两种情况

  1. 找到了,插入在它之后(具体实现时 假设找到的下标为j, 将a[j+1~i-1]的元素依次后移,再将a[i]设置到a[j+1]),
  2. 没找到,说明a[i]就是当前最小的元素,将a[0~i-1]的元素依次后移,将a[i]设置到a[0]

    核心代码如下

    public void sort(Comparable[] arr) {
            if (arr.length <= 1) {
                return;
            }
            for (int i = 1; i < arr.length; i++) {
                Comparable insertValue = arr[i];
                int insertPos = i;
                for (int j = i - 1; j >= 0; j--) {
                    if (insertValue.compareTo(arr[j]) < 0) {
                        arr[j + 1] = arr[j];
                        insertPos = j;
                    } else {
                        insertPos = j + 1;
                        break;
                    }
                }
                arr[insertPos] = insertValue;
            }
        }

时间复杂度

以元素移动的次数作为计量单位

最坏情况下,数组完全倒序,时间复杂度为 0 + 1 + 2 + 3 +...+ (n-1) = n*(n-1)/2 ~= O(n^2)

最好情况下,数组完全顺序, 时间复杂度为 1 + 1 + 1 + ... + 1= n ~=O(n)

平均情况下, 我们仍然使用逆序数这个概念,再观察依旧可以得知: 逆序数等于移动次数,所以时间复杂度为 (0 + n(n-1)/2)/2 ~= O(n^2)

是原地排序吗?

插入排序中,没有使用额外的空间,因此是原地排序

稳定吗

插入排序中,只有移动操作会改变元素位置,而判定条件是小于等于arr[i] ,符合情况1,a[i]会被设置到a[j+1],从而保证相对顺序的稳定.

选择排序

主要思路

将数组分为左侧的已排序部分和右侧的未排序部分

选择排序中选择表示对未排序部分的一次遍历,初始时已排序部分为空,未排序部分为arr[0~n-1],

一次选择后,已排序部分为arr[0~0],未排序部分为arr[1~n-1],x(1≤x<n)次选择后,已排序部分为arr[0~x-1],未排序部分为arr(x~n-1]

x(1≤x<n)次选择时,找出未排序数组中的最小值,放入已排序数组(与arr[x-1]交换),核心代码如下

    public void sort(Comparable[] arr) {
            if (arr.length <= 1) {
                return;
            }
            for (int i = 1; i < arr.length ; i++){
                int minPos=i-1;
                Comparable minValue=arr[i-1];
                for(int j=i;j<arr.length;j++){
                    if (minValue.compareTo(arr[j])>0){
                        minPos=j;
                        minValue=arr[j];
                    }
                }
                swap(arr,minPos,i-1);
            }
        }

时间复杂度

以minValue被替换的次数作为计量单位.

最坏情况下为 (n-1) + (n-2) + ... + 0 =n(n-1)/2 ~= O(n^2)

最好情况下为 1+1+1...+1 =n ~= O(n)

平均情况下,仍然使用逆序数, 可以发现: 逆序数等于替换的次数,因此时间复杂度为(0 + n(n-1)/2)/2 ~= O(n^2)

是原地排序吗?

选择排序中,minValue使用了一个单位的额外空间,因此是原地排序

稳定吗

选择排序中,没有约束可以确保稳定, 比如 4, 3, 2, 4, 1 排序过后,两个4的相对位置会发生变化

冒泡,插入,选择的比较

三者的相同之处

  • 将数组虚拟为已排序和未排序部分,在排序过程中逐步将未排序部分转化为已排序部分.
  • 都是原地排序
  • 平均时间复杂度都是O(n^2)

三者的不同之处

  • 冒泡排序,插入排序是稳定的,选择排序时不稳定
  • 冒泡和选择频繁使用交换操作,插入排序较多使用赋值操作,而赋值相比交换性能更好

    三者比较,根据我这里的简单测试 性能方面 选择>插入>冒泡, 但是选择排序不稳定,仅此一点很多场景下就不适用,实用性大大降低. 因此插入排序时三者中较好的选择.

总结

冒泡,插入,选择是三种基本的排序算法,研究他们对学习更高效的排序算法有帮助,相关源码已经上传到这里

原文地址:https://www.cnblogs.com/alonwang/p/12670420.html

时间: 2024-11-10 01:14:48

基础排序算法—冒泡,插入,选择的相关文章

排序算法-冒泡——插入——快排

冒泡排序,往两个方向泡,一个往小泡,一个网大泡 #include<stdio.h> #include<stdlib.h> #include<time.h> void bubble_sort(int *a,int n){ int temp; for(int i=0;i<n;i++) for(int j=0;j<n-i-1;j++){ if(a[j]>a[j+1]){ temp=a[j]; a[j]=a[j+1]; a[j+1]=temp; } } } v

排序算法(冒泡,选择,插入,快速,希尔,堆排序,基数,计数排序 )

时间复杂度为O(n^2)的排序算法:冒泡排序,选择排序,插入排序 数组a[N]中进行冒泡排序 冒泡排序: 假如数组为[3,7,4,6,8,9,1]-->[3,7,4,6,8,9,1]-->[3,4,7,6,8,9,1]....一直进行下去,相邻2个数进行比较. 1.第一轮:数组相邻2个元素相比较(a[0]和a[1]比,a[1]和a[2]比,a[2]和a[3]比,a[3]和a[4]比...),较大数往后放,一直相邻比较到数组最后一个数. 第二轮继续数组相邻比较,此时一直相邻比较到数组倒数第二个元

简单数值排序算法 冒泡、选择、 直接插入排序

1. 选择排序法 找到第一小的数字,放在第一個位置:再找到第二小的数字,放在第二個位置 依次找到每次遍历的最小的值.一次找一個数字,如此下去就会把所有数值按照 順序排好.注意,与冒泡不同,遍历的过程中并不交换数值.一次遍历结束才交换. package com.sphere.algorith.design; import java.util.Arrays; public class Test { public static void main(String[] args) { int[] arr

经典排序算法-冒泡与选择

1 冒泡排序的思想类似气泡上升,数组前后元素两两比较,如果前面的元素比后面的大就交换位置,每趟排序都可以确定最大值的位置 2 选择排序思想类似打擂台,每趟比较都把最小的值排在数组最前面,每趟比较都可以确定最小值的位置 public class SortTest { public static void main(String[] args) { //选择排序 int arr[] = {2,9,10,5,4,8,7,1,3,6}; choose(arr); System.out.println()

基础排序算法 c#

算法知识欠缺,学习几种基础排序算法,1. 选择排序  2.冒泡排序 3.插入排序 以升序为例,程序中凡是 两个值的比较前者大于后者,则调换位置 1. 选择排序 最容易想到的排序算法,但却不知道这种叫做选择排序法 1 /// <summary> 2 /// 选择排序 3 /// </summary> 4 /// <param name="sourceValues"></param> 5 /// <returns></ret

排序算法-冒泡、插入、归并、希尔、快速、选择--代码总结

冒泡排序代码: #include <iostream> #include <string> using namespace std; template<class ItemType> void bubbleSort(ItemType theArray[], int n) { bool sorted = false; // False when swaps occur int pass = 1; while (!sorted && (pass < n

排序算法比较和选择

排序算法有不少,当然,一般的语言中都提供某个排序函数,比如Python中,对list进行排序,可以使用sorted(或者list.sort()),关于这方面的使用,在我的github代码库algorithm中有几个举例,有兴趣的看官可以去那里看看(顺便告知,我在Github中的账号是qiwsir,欢迎follow me).但是,在某些情况下,语言中提供的排序方法或许不适合,必须选择某种排序算法. 这里整理一些排序算法的比较,供看官参考. 计算复杂度比较 名称 平均速度 最坏情况 冒泡排序法 O(

九大基础排序算法总结

九大基础排序算法小结 一直想做份总结,总是抽不出时间,趁着周末闲着直接用了一整天做一份,一些内容参考了网上的一些博主的总结,还有网络上的一些图片. 好了,接下来就看看内容吧! 排序算法: 排序方法 时间复杂度 空间复杂度 稳定性 选用情况 平均情况 最坏情况 最好情况 Insertion Sort O(n^2) O(n^2) O(n) O(1) 稳定 n较小时 Selection Sort O(n^2) O(n^2) O(n^2) O(1) 不稳定 n较小时 Bubble Sort O(n^2)

排序算法七:选择排序之堆排序

排序算法七:选择排序之堆排序 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 引言 在我的博文<"主宰世界"的10种算法短评>中给出的首个算法就是高效的排序算法.本文将对排序算法做一个全面的梳理,从最简单的"冒泡"到高效的堆排序等. 上博文讲述了选择排序中的简单排序算法,本文介绍的堆排序是树性选择排序,采用堆这个数据结构来辅助排序. 排序相关的的基本概念 排序:将一组杂乱无章的数据按一定的规律顺次排列起来. 数据