最熟悉的几种排序——冒泡排序插入排序和选择排序
Posted 一个程序员的取经之路
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最熟悉的几种排序——冒泡排序插入排序和选择排序相关的知识,希望对你有一定的参考价值。
——几种时间复杂度为O(n)的排序算法
内容目录
1. 如何分析一个排序算法2. 冒泡排序2.1 冒泡排序2.2冒泡排序的平均时间复杂度3.插入排序3.1 插入排序4. 选择排序4.1 选择排序5. 冒泡排序和插入排序
1. 如何分析一个排序算法
一般来说,分析一个算法我们从执行效率上来分析。常见的有复杂度分析法,从时间复杂度和空间复杂度两个方面来看。
时间复杂度分为最好时间复杂度、最坏时间复杂度和平均时间复杂度。
实际的开发过程中,数据规模可能不是很大,所以,在分析时间复杂度的时候,系数、低阶和常量有时候也不能忽视。
在排序过程中,涉及到的操作有两个,比较和移动,所以要把这两个操作的复杂度都要考虑进去。
空间复杂度用内存消耗来度量。空间复杂度为O(1)的排序算法,称为原地排序算法。
排序的数据中,如果相等的数据在排序前后位置不发生改变,能减少移动次数。这种不改变相同数据前后位置的特性,称之为稳定性。算法是否具有稳定性,也是一个分析指标。
2. 冒泡排序
2.1 冒泡排序
每次比较相邻两个数据,如果不满足大小关系就进行交换操作。每一次冒泡,都能保证一个数据移动到它对应的位置。比如5个数据排序,第一次冒泡操作,依次比较1和2,2和3,3和4,4和5,5和6位置的数据,如果不满足大小关系就交换两个元素。
如果某次冒泡无需移动元素了,说明已经达到有序了,无需继续操作。
public int[] bubbleSort(int[] array) {
if (array.length <= 0) {
return null;
}
for (int i = 0; i < array.length; i++) {
boolean flag = false;
for (int j = 0; j < array.length - j - 1; j++) {
int temp;
if (array[j] > array[j + 1]) {
temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
flag = true;
}
}
if (!flag) {//没有数据交换则表示排序完成
break;
}
}
return array;
}
冒泡排序是原地排序算法:使用常量级别的空间,空间复杂度为O(1),是原地排序算法
冒泡排序是稳定算法:相邻元素大小相等时不交换元素位置。
冒泡排序的最好最坏时间复杂度:
1)最好时间复杂度:完全有序情况,需要进行一次冒泡操作,比较n次即可,最好时间复杂度为O(n);
2)最坏时间复杂度:完全无序情况,进行n次冒泡操作,最坏时间复杂度为O(n2)。
2.2冒泡排序的平均时间复杂度
有序度:数组中具有有序关系的元素对的个数
逆序度:数组中具有与给定关系相反的元素对的个数
满有序度:完全有序的数组中有序度元素对的个数
逆序度 = 满有序度 - 有序度
冒泡操作每做一次,有序度加一
先来分析交换次数。最好情况下,需要0次交换;最坏情况下需要n(n-1)/2次交换,取平均值n(n-1)/4。
移动次数肯定比交换次数多,同时平均时间复杂度不可能比最坏情况复杂度O(n2)大,所以平均时间复杂度为O(n2)。
3.插入排序
3.1 插入排序
将数据分为已排序区和未排序区。每次从未排序区取一个数字在已排序区找到合适的位置插入(插入位置开始往后的元素要依次向后移动一位)。
对于一个给定的序列,元素的移动次数是有限的,就等于逆序度。
可以这么理解:假设给定长度为n的完全有序的序列,要将第n+1个元素插入到此序列中,需要将前n个元素中比第n+1个元素大的元素后移一位。比如给定序列[1,2,3,4,6,7,8],要将5插入到[1,2,3,4,6,7,8]中。实际上就是要对[1,2,3,4,6,7,8,5]进行插入排序,此序列逆序度为3。插入排序时,需要将[6,7,8]向后移动一位,而[6,7,8]和元素5正好是所有的逆序对。与要插入的元素构成逆序对的每一个元素需要向后移动一位,所以移动次数就等于逆序度。
public int[] insertionSort(int[] array) {
if (array.length <= 0) {
return null;
}
for (int i = 1; i < array.length; i++) {
int toSort = array[i];
int j = i - 1;
// 和已排序区所有元素比较
// 从后向前比较,如果已排序区元素a大,就将a后移一位
for (; j >= 0; j--) {
if (array[j] > toSort) {
array[j + 1] = array[j];
}
else {
break;// 不需移动则说明已排序区有序
}
}
array[j + 1] = toSort;
}
return array;
}
插入排序是原地排序算法:使用常量级别的空间,空间复杂度为O(1),是原地排序算法
插入排序是稳定算法:我们可以将后出现的元素插入到先出现元素的后面,所以是稳定算法。
插入排序的最好最坏时间复杂度:
1)最好时间复杂度:完全有序情况,需要进行一次冒泡操作,比较n次即可,最好时间复杂度为O(n)。
2)最坏时间复杂度:完全无序情况,每次需要将新元素插入到第一个位置,最坏时间复杂度为O(n2)。插入排序的平均时间复杂度:在数组某个位置插入一个数据,需要将该位置后面的数据全部向后移动一位。所以,插入位置从头开始的话,所需时间为n,n-1,n-2,n-3,……3,2,1。概率都为1/n,故平均时间复杂度为(1+2+3+……+n)/n=O(n)。插入排序需要进行n次插入操作,所以平均时间复杂度为O(n2)。
4. 选择排序
4.1 选择排序
将数据分为已排序区和未排序区。每次在未排序区选择一个最小的元素,放到已排序区末尾。
public int[] chooseSort(int[] array) {
if (array.length <= 0) {
return null;
}
for (int i = 0; i < array.length; i++) {
int min = array[i];// 初始化假设最小值为未排序区第一个元素
int minIndex = i;// 初始化最小值的下标为未排序区第一个元素的下标
for (int j = i + 1; j < array.length; j++) {
if (array[j] < min) {
min = array[j];
minIndex = j;// 找到最小值下标
}
}
// 如果未排序区第一个数是最小值,则此轮不需要移动元素
if (minIndex == i) {
continue;
}
// 将未排序区中最小值前面的数字从后往前遍历,向后移
for (int k = minIndex; k > i; k--) {
array[k] = array[k - 1];
}
array[i] = min;
}
return array;
}
选择排序是原地排序算法:使用常量级别的空间,空间复杂度为O(1),是原地排序算法。
选择排序是不是稳定性算法:当第一个元素a不是最小值时,假设最小值为元素b,而a和b之间还有元素c=a,即a、b、c在原数组中顺序为a、c、b(其中a=c,a在c前面)。第一次排序时a和b交换位置,变成b、c、a(a在c后面)。
最好时间复杂度、最坏时间复杂度和平均时间复杂度都为O(n2)。
5. 冒泡排序和插入排序效率比较
冒泡排序和插入排序的时间复杂度都是O(n2)。
冒泡排序中每次进行数据交换需要3个赋值操作,插入排序每次移动只需要1个赋值操作,所以插入排序效率更高。
以上是关于最熟悉的几种排序——冒泡排序插入排序和选择排序的主要内容,如果未能解决你的问题,请参考以下文章
Java中的几种排序算法:冒泡排序,插入排序,二分法排序,简单排序,快速排序
学习Java绝对要懂的,Java编程中最常用的几种排序算法!