介绍
冒泡排序是一种简单的排序算法,它也是一种稳定排序算法。其实现原理是重复扫描待排序序列,并比较每一对相邻的元素,当该对元素顺序不正确时进行交换。一直重复这个过程,直到没有任何两个相邻元素可以交换,就表明完成了排序。
用法与要点
冒泡排序的重点就是把相邻的两个元素进行比较,然后满足组条件就进行交换。
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后数组排序会呈升序。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
代码实例
最简单容易理解的版本,也是我自己个人写的版本
用到双循环的遍历的是因为,只遍历一次,就只造成相邻元素之间排序是正常的,但是从整体来说,排序并不正常,因为你不知道要排序的数组里面的元素位置是不是有序的。
public static int[] arr = new int[] { 21, 60, 19, 89, 25, 49, 11, 84, 40, 1 };
public static void InsertSort()
{
// 这一层遍历是为了能让整体排序正常,也就是多执行几次相邻元素的排序调换
// 也是将遍历数组所有的元素
for (int i = 0; i < arr.Length; i++)
{
// 这一层遍历是为了让相邻的元素排序正常
// 这一层的遍历可以从0开始,也可以从1开始,具体按照各自的写法,这里作者是`j-1`
for (int j = 1; j < arr.Length; j++)
{
// 默认是进行升序 如果上一个元素比当前元素大,就进行交换
if (arr[j] < arr[j-1])
{
int temp = arr[j-1];
arr[j - 1] = arr[j];
arr[j] = temp;
}
}
}
}
改良优化版本
1、以上写法2次遍历都是需要遍历所有的数组元素的,这个可以其实优化的。我们先将每一次遍历后的结果打印出来,如下。大家可以发现每一次遍历,冒泡排序都会讲最大的数移动到最后面,也就是说,其实每一次遍历后数组里面有一部分元素是已经排序好的,那么这部分其实就没有必要再去遍历了。具体规则就是i=0
时,是需要全部遍历的,而当i=1
时,也就是第二次遍历,这时候最后一个数其实不用遍历到的。也就是说for(j = 1; j < arr.Length - i; j++)
才对
第1次: 21, 19, 60, 25, 49, 11, 84, 40, 1, 89,
第2次: 19, 21, 25, 49, 11, 60, 40, 1, 84, 89,
第3次: 19, 21, 25, 11, 49, 40, 1, 60, 84, 89,
第4次: 19, 21, 11, 25, 40, 1, 49, 60, 84, 89,
第5次: 19, 11, 21, 25, 1, 40, 49, 60, 84, 89,
第6次: 11, 19, 21, 1, 25, 40, 49, 60, 84, 89,
第7次: 11, 19, 1, 21, 25, 40, 49, 60, 84, 89,
第8次: 11, 1, 19, 21, 25, 40, 49, 60, 84, 89,
第9次: 1, 11, 19, 21, 25, 40, 49, 60, 84, 89,
第10次:1, 11, 19, 21, 25, 40, 49, 60, 84, 89,
具体代码
// 这一层遍历是为了能让整体排序正常,也就是多执行几次相邻元素的排序调换
// 也是将遍历数组所有的元素
for (int i = 0; i < arr.Length; i++)
{
// 这一层遍历是为了让相邻的元素排序正常
// 这一层的遍历可以从0开始,也可以从1开始,具体按照各自的写法,这里作者是`j-1`
// 因为每一次遍历都会把最大的数放到后方,所以后面就不要执行后面已经排序的好的元素了
for (int j = 1; j < arr.Length - i; j++)
{
// 默认是进行升序 如果上一个元素比当前元素大,就进行交换
if (arr[j] < arr[j - 1])
{
int temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
}
}
Print(i);
}
2、有时候会出现一种情况,原数组是1, 11, 19, 21, 60, 89, 25, 49, 84, 40
。执行结果如下所示:
第1次: 1, 11, 19, 21, 60, 25, 49, 84, 40, 89,
第2次: 1, 11, 19, 21, 25, 49, 60, 40, 84, 89,
第3次: 1, 11, 19, 21, 25, 49, 40, 60, 84, 89,
第4次: 1, 11, 19, 21, 25, 40, 49, 60, 84, 89,
第5次: 1, 11, 19, 21, 25, 40, 49, 60, 84, 89,
第6次: 1, 11, 19, 21, 25, 40, 49, 60, 84, 89,
第7次: 1, 11, 19, 21, 25, 40, 49, 60, 84, 89,
第8次: 1, 11, 19, 21, 25, 40, 49, 60, 84, 89,
第9次: 1, 11, 19, 21, 25, 40, 49, 60, 84, 89,
第10次:1, 11, 19, 21, 25, 40, 49, 60, 84, 89,
如上面结果所示,是当i=3
的时候,所有的数组都已经排序好了,也就是说,需要排序的数组里面有一部分是有序的,既然已经排序好了,但是还要多运行6次遍历,这样非常不合理.所以需要进行改良,具体如下:
public static int[] arr = new int[] { 1, 11, 19, 21, 60, 89, 25, 49, 84, 40 };
public static void InsertSort()
{
// 增加标记,用来标记是不是无元素可互换
bool flag = true;
// 这一层遍历是为了能让整体排序正常,也就是多执行几次相邻元素的排序调换
// 也是将遍历数组所有的元素
for (int i = 0; i < arr.Length; i++)
{
flag = false;
// 这一层遍历是为了让相邻的元素排序正常
// 这一层的遍历可以从0开始,也可以从1开始,具体按照各自的写法,这里作者是`j-1`
// 因为每一次遍历都会把最大的数放到后方,所以后面就不要执行后面已经排序的好的元素了
for (int j = 1; j < arr.Length - i; j++)
{
// 默认是进行升序 如果上一个元素比当前元素大,就进行交换
if (arr[j] < arr[j - 1])
{
int temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
if (!flag)
{
flag = true;
}
}
}
Print(i);
// 如果没有元素可以互换,就表明已经全部排序好了,因为只会循环1~arr.Length - i,
// arr.Length - i后面的都是排序好的,那么前面的元素既然没有可以互换的,那表明就是已经排序完成
if (!flag)
{
return;
}
}
}
优化之后,执行如下:
第1次:1, 11, 19, 21, 60, 25, 49, 84, 40, 89,
第2次:1, 11, 19, 21, 25, 49, 60, 40, 84, 89,
第3次:1, 11, 19, 21, 25, 49, 40, 60, 84, 89,
第4次:1, 11, 19, 21, 25, 40, 49, 60, 84, 89,
第5次:1, 11, 19, 21, 25, 40, 49, 60, 84, 89,
只多运行了一次,明显减少了无意义的遍历行为。
复杂度分析
时间复杂度:最坏O(n²),最好O(n)
空间复杂度:O(1)