三个简单排序算法:冒泡排序、选择排序、插入排序
以下从基础开始,逐步详细复习各个排序算法。先从三个最基础最简单的排序算法开始。他们分别是冒泡排序、选择排序、插入排序。以下都是java代码,并且认为升序是有序。
一、冒泡排序
1、代码
public class Main{//冒泡排序 public static void main(String[] args){ int[] a=new int[]{1,3,1,2,0,4,5,2,6,2,0}; int n=a.length; boolean flag=true;//如果对于一趟比较,没有交换的动作,也就是已经有序了,就直接结束排序,不需要进行后面的比较了 for(int i=n-1;i>0;i--){//第一层循环从n-1到1 flag=true; for(int j=0;j<i;j++)//第二层循环从0到n-1 if(a[j]>a[j+1]){//如果遇到不符合顺序的数据就交换他们 swap(a, j, j+1); flag=false; } if(flag==true) break; } print(a); } public static void swap(int[] a,int i,int j){ int temp=a[i]; a[i]=a[j]; a[j]=temp; } public static void print(int[] a){ int n=a.length; for(int i=0;i<n;i++) System.out.print(a[i]+" "); } }
2、时间和空间复杂度分析
(1)最好时间复杂度
如果文件初始状态刚好是正序的,一趟扫描便可以完成排序。这个时候只需要扫描n-1次,交换0次。因此最好的时间复杂度为O(n)。而做到这个最好时间复杂度的前提是有flag这个变量的存在,他可以判断是否序列已经有序了。
(2)最差时间复杂度
最坏的情况是如果序列是逆序的,那需要进行n-1次循环,每次循环需要进行n-i次比较(1<=i<=n),并且每次比较都需要移动三次记录来交换记录的位置。交换操作比比较事实上耗费更多的时间。因此需要比较n(n-1)/2次,交换3n(n-1)/2次。因此最坏的时间复杂度是O(n^2)。事实上,我们可以把交换的三步当做一步的交换操作,所以也可以认为是交换n(n-1)/2次。后面我们都这样分析,这样比较简便。
(3)平均时间复杂度
平均来说,是O(n^2)。
(4)空间复杂度
不需要用到额外的很多空间,因此空间复杂度是O(1)。
3、算法稳定性
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
二、选择排序
选择排序,顾名思义就是每一次从待排序的数据元素中选出最小的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
1、代码
public class Main{//选择排序 public static void main(String[] args){ int[] a=new int[]{1,3,1,2,0,4,5,2,6,2,0}; int n=a.length; int min;//记录最小那个数的下标 for(int i=0;i<n-1;i++){ min=i; for(int j=i+1;j<n;j++) if(a[j]<a[min]) min=j; swap(a, i, min); } print(a); } public static void swap(int[] a,int i,int j){ int temp=a[i]; a[i]=a[j]; a[j]=temp; } public static void print(int[] a){ int n=a.length; for(int i=0;i<n;i++) System.out.print(a[i]+" "); } }2、时间和空间复杂度分析
(1)最好时间复杂度
最好的情况下是序列本身已经有序。但是就算序列本身有序,算法也需要进行(n-1)+(n-2)+...+1=n*(n-1)/2次比较(无论有序还是无序,都是进行这么多次比较),但是只需要交换0次。所以最好时间复杂度是O(n^2)。
(2)最坏时间复杂度
最坏的情况,也只需要交换n-1次。最坏时间复杂度是O(n^2)。
(3)平均时间复杂度
O(n^2)。
(4)空间复杂度
只需要一个额外的辅助存储空间,因此是O(1)。
3、稳定性
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果一个元素比当前元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中两个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。
4、与其他算法比较
可以看到,交换次数比冒泡排序少多了,由于交换所需CPU时间比比较所需的CPU时间多,n值较小时,选择排序比冒泡排序快。
三、插入排序
插入排序的基本操作就是将一个数据按值大小插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据。当然,这个有序数组一开始就是要排序的那个数组的开始第一个数。
1、代码
public class Main{//插入排序 public static void main(String[] args){ int[] a=new int[]{1,3,1,2,0,4,5,2,6,2,0}; int n=a.length; int i,j,temp;//用来暂存 for(i=1;i<n;i++){ temp=a[i]; j=i-1; for(;j>=0&&a[j]>temp;j--)//找到插入位置的同时往一边整体移动数组 a[j+1]=a[j]; if(j!=i-1)//j等于i-1的话自然不需要填补了 a[j+1]=temp; } print(a); } public static void print(int[] a){ int n=a.length; for(int i=0;i<n;i++) System.out.print(a[i]+" "); } }2、时间和空间复杂度
(1)最好时间复杂度
最好的情况是已经升序了,这样的话只需要进行n-1次比较操作就可以了,因此是O(n)。
(2)最坏时间复杂度
最坏的情况是降序,这样要进行n(n-1)/2次比较和n(n-1)/2+n-1次赋值操作,因此是O(n^2)。
(3)平均时间复杂度
O(n^2)。
(4)空间复杂度
O(1)。
3、稳定性
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
4、适用性
当数组基本有序的情况下适合使用插入排序和冒泡排序,它们在基本有序的情况下排序的时间复杂度接近O(n)。当n比较小的时候,适合插入排序和选择排序,如果元素包含的内容过大,就不适合插入排序,因为插入排序需要移动元素的次数比较多。
阅读更多
- 三个典型的经典算法冒泡排序,插入排序,选择排序
- 简单排序算法之插入排序、选择排序和冒泡排序
- 基本算法简单实现-二分法查找、合并排序、冒泡排序、插入排序、选择排序、快速排序
- 算法系列(二)冒泡排序、选择排序、插入排序和希尔排序(Java实现)
- 【从头开始学算法】选择排序 插入排序 冒泡排序
- 冒泡排序,选择排序以及插入排序的算法
- 简单排序——冒泡排序,选择排序,插入排序,对象排序
- 算法系列(三)排序算法上篇--冒泡排序插入排序和选择排序
- 算法 ----- 排序lowB三人组 冒泡排序 选择排序 插入排序
- c语言中冒泡排序、插入排序、选择排序算法比较
- 算法导论之插入排序,选择排序,归并排序,冒泡排序,希尔排序,堆排序,快速排序的c语言实现
- 简单排序:冒泡排序、选择排序和插入排序
- C#算法 选择排序、冒泡排序、插入排序
- 笔试面试最常涉及到的12种排序算法(包括插入排序、二分插入排序、希尔排序、选择排序、冒泡排序、鸡尾酒排序、快速排序、堆排序、归并排序、桶排序、计数排序和基数排序)进行了详解。每一种算法都有基本介绍、算
- 内部排序冒泡排序、插入排序、选择排序、快速排序的算法和PHP实现
- 冒泡排序、选择排序、插入排序 算法实现(C++)
- 算法分析中最常用的几种排序算法(插入排序、希尔排序、冒泡排序、选择排序、快速排序,归并排序)C 语言版
- 几种常见排序算法之Java实现(插入排序、希尔排序、冒泡排序、快速排序、选择排序、归并排序)
- c语言中冒泡排序、插入排序、选择排序算法比较
- c语言中冒泡排序、插入排序、选择排序算法比较