冒泡排序算法的思想
上图是无序的数据,要进行排序。
冒泡排序就是:
每一趟都是从开始的这个元素,两两进行比较,把大的元素往下交换(沉淀到底),把小的元素往上交换(冒泡),每一趟操作的数据都是除去前面的每一趟沉淀到底的数据之外的数据
每一趟处理的方式是一样的,只是数据量是不同的。
第一趟:从开始的这个元素,两两进行比较,把大的元素往下交换(沉),把小的元素往上交换(冒泡)
第一趟下来,整体没排序,但是把整个序列的最大值94沉到底了,
第二趟:不用处理94了,我们的目的是每一趟把当前处理的序列的最大值沉到底。所以,我们第二趟处理除94外的其他数据就可以了。
第三趟:处理除94和91外的其他数据
以此类推下去。
冒泡排序的优化
是在第8趟,把9沉到底了,但是此时的整个序列已经是有序的了,不用做下一趟了。
我们都是在原来的数组上操作的,进行数据的交换的,并没有额外开辟空间去操作。
最后,操作完的数组的下标如下图所示:
每一趟把当前所处理序列的元素的最大值找出来,沉到当前处理的数据序列的最后一个位置,下一趟在处理的时候总是比上一趟少1个元素
每一趟都是少处理1个元素。
所以走size-1个趟数,因为最后一趟就剩下1个元素,所以就不用走最后一趟
冒泡排序的非优化代码实现
//冒泡排序算法
void BubbleSort(int arr[], int size)
{
for (int i = 0; i < size-1; i++)//趟数 O(n) * O(n) = O(n^2)
{
//一趟的处理
for (int j = 0; j < size - 1 - i; j++)//O(n)//size-1,是因为是j和j+1比较
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
冒泡排序的优化代码实现
如果我们在某一趟中,发现两两比较,整个序列比较完之后,并没有进行任何的数据交换,这说明这一趟检测出来,数据已经是从小到大排序好的了,例如上图中的第8趟,这时候根本没必要进行后面的趟了
//冒泡排序算法
void BubbleSort(int arr[], int size)
{
for (int i = 0; i < size-1; i++)//趟数 O(n) * O(n) = O(n^2)
{
bool flag = false;
//一趟的处理
for (int j = 0; j < size - 1 - i; j++)//O(n)//size-1,是因为是j和j+1比较
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = true;
}
}
if (!flag)//冒泡排序算法的优化之处
{
//如果没有做任何的数据交换,那么说明数据已经有序了,就不用继续走后面的趟了
return;
}
}
}
冒泡排序的性能分析
因为冒泡排序都是在原数组上进行操作的,原地排序的,并没有额外开辟空间,所以空间复杂度是O(1)
因为冒泡实现有2个for循环,是嵌套的
所以冒泡排序的平均和最坏的时间复杂度是O(n^2)
最好的时间复杂度是O(n),因为最好的情况下,数据本身就是有序的,经过第一趟处理,没有做任何的交换,任何return掉,只做了内部的一趟的for循环操作。
稳定性:
稳定性就是:
有2个元素,元素1和元素2的值相同,在序列中,元素1排在元素2的前面,然后经过排序完成后,元素1还是在元素2的前面。
这个稳定性主要应用在map表中,或者有映射关联关系的集合中:
我们可以把左边的称为key,右边的称为value
当我们在最开始的原始位置中,放置数据的时候,我们按照键相同的时候,我们按照它们的字典顺序排序的,aaa在前面,bbb在后面
我们到时一筛选5,一筛选k,对应的value都是按照字典序排好的,aaa,bbb,ccc,ddd
如果我们现在有很多key,不仅有5,还有6,7,而且5有多个5,如果是稳定的排序的话,最后排好序后,5对应的aaa还是在5对应的bbb之前。
如果是不稳定的排序的话,会导致,原来上面的key,现在排序了以后,5对应的bbb跑到了aaa前面了。导致,当我们再去看key是5对应的value,这个value不是按照之前的字典顺序排列了,是乱的了。
冒泡排序是稳定的,因为只有上边元素大于其下边元素,才会交换,如果上边元素小于等于其下边元素,是不会进行交换操作的。