冒泡排序(Bubble Sort)
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
C++代码实现
从后向前相邻元素两两比较版本
从后向前两两比较,确保前面序列有序
void bubbleSort(vector<int>& v, int begin, int end)
{
int tmp = 0;
for (int i = begin, i < end - 1; i++)
{
for (int j = end - 1; j > i; j--)
{//从后向前两两比较
if (v[j - 1] < v[j])
{
tmp = v[j];
v[j] = v[j - 1];
v[j - 1] = tmp;
}
}
}
}
过程模拟
以数组[2, 2, 5, 1, 3]为例。注意加粗的2位置,用来判断算法的稳定性。
第一趟排序(i = 0):
j = 4, j > i, v[j] = 3 > v[j - 1] = 1, 无需交换
j = 3, j > i, v[j] = 1 < v[j - 1] = 5, 交换,交换后数组为[2, 2, 1, 5, 3]
j = 2, j > i, v[j] = 1 < v[j - 1] = 2, 交换,交换后数组为[2, 1, 2, 5, 3]
j = 1, j > i, v[j] = 1 < v[j - 1] = 2, 交换,交换后数组为[1, 2, 2, 5, 3]
第一趟排序结束,比较次数为n - 1次,时间复杂度为O(n)。
第一趟排序后,有序序列为[1], 无序序列为[2, 2, 5, 3]
第二趟排序(i = 1):
j = 4, j > i, v[j] = 3 < v[j - 1] = 5,交换,交换后数组为[1, 2, 2, 3, 5]
j = 3, j > i, v[j] = 3 > v[j - 1] = 2, 无需交换
j = 2, j > i, v[j] = 2 == v[j - 1] = 2,无需交换
第二趟排序结束,比较次数为n - 2次,时间复杂度为O(n)。
第二趟排序后,有序序列为[1, 2],无序序列为[2, 3, 5],虽然此时已经有序,但还是会继续下一趟排序比较。
第三趟排序(i = 2):
j = 4, j > i, v[j] = 5 > v[j - 1] = 3, 无需交换
j = 3, j > i, v[j] = 3 > v[j - 1] = 2, 无需交换
第二趟排序结束,比较次数为n - 3,时间复杂度为O(n)。
第三趟排序后,有序序列为[1, 2, 2], 无序序列为[3, 5]。
第四趟排序(i = 3):
j = 4, j > i, v[j] = 5 > v[j - 1] = 3, 无需交换
第四趟排序结束,比较次数为n - 4,时间复杂度为O(n)。
第四趟排序后,有序序列为[1, 2, 2, 3 ,5], 无序序列为[5], 但此时无序序列只有一个元素了,所以无需进行下一趟排序,因为没有相邻元素和它比较了。
从前向后相邻元素两两比较版本
void bubbleSort(vector<int>& v, int begin, int end)
{
int tmp = 0;
for (int i = begin, i < end - 1; i++)
{
for (int j = 0; j < end - i - 1; j++)
{
if (v[j] > v[j + 1])
{
tmp = v[j];
v[j] = v[j + 1];
v[j + 1] = tmp;
}
}
}
}
过程模拟
核心思路依旧是相邻元素两两比较,比较次数没有改变,只是每轮比较后的有序序列位置不同。
依旧以数组[2, 2, 5, 1, 3]为例。
第一趟排序(i = 0):
j = 0, j < end - i - 1 = 4, v[j] = 2 == v[j + 1] = 2, 无需交换
j = 1, j < end - i - 1 = 4, v[j] = 2 < v[j + 1] = 5, 无需交换
j = 2, j < end - i - 1 = 4, v[j] = 5 > v[j + 1], 交换,交换后数组为[2, 2, 1, 5, 3]
j = 3, j < end - i - 1 = 4, v[j] = 5 > v[j + 1] = 3, 交换, 交换后数组为[2, 2, 1, 3, 5]
第一趟排序结束,比较次数为n - 1,时间复杂度为O(n)。
第一趟排序后,有序序列为[5], 无序序列为[2, 2, 1, 3]。
第二趟排序(i = 1):
j = 0, j < end - i - 1 = 3, v[j] = 2 == v[j + 1] = 2, 无需交换
j = 1, j < end - i - 1 = 3, v[j] = 2 > v[j + 1] = 1, 交换, 交换后数组为[2, 1, 2, 3]
j = 2, j < end - i - 1 = 3, v[j] = 2 < v[j + 1], 无需交换
第二趟排序结束,比较次数为n - 2,时间复杂度为O(n)。
第二趟排序结束后有序序列为[3, 5], 无序序列为[2, 1, 2]
第三趟排序(i = 2):
j = 0, j < end - i - 1 = 2, v[j] = 2 < v[j + 1] = 1, 交换, 交换后数组为[1, 2, 2]
j = 1, j < end - i - 1 = 2, v[j] = 2 == v[j + 1] = 2, 无需交换
第三趟排序结束,比较次数为n - 3, 时间复杂度为O(n)。
第三趟排序后有序序列为[2, 3, 5], 无需序列为[1, 2]
第四趟排序(i = 3):
j = 0, j < end - i - 1 = 1, v[j] = 1 < v[j + 1] = 2, 无需交换
第四趟排序结束,比较次数为n - 4, 时间复杂度为O(n)。
第四趟排序后有序序列为[2, 2, 3, 5], 无序序列为[1], 但此时无序序列只有一个元素,无需进行下一次排序。
至此排序完成。
时间复杂度
最好情况: 给定序列是正序的
这种情况下,一趟排序即可完成排序。
未优化的比较次数为(n - 1) + (n - 2) + … + 2 + 1 = n*(n-1)/2, 优化后的比较次数为n - 1。优化代码在后面。
因为给定序列已经有序,所以交换次数为0次。
那么最好情况下的冒泡排序的整体时间复杂度为O(n) + O(0) = O(n),即最好时间复杂度为O(n)。
最坏情况: 给定序列是逆序的
若给定序列是反序的,需要进行n - 1趟排序。在这种情况下,比较和移动次数均达到最大值。
每趟排序要进行 n - i 次的比较(1≤i≤n-1),且每次比较都必须进行三次交换操作来达到交换记录位置。
优化前后比较次数均为(n - 1) + (n - 2) + … + 2 + 1 = n*(n-1)/2。
每次比较都会进行三次交换操作,以此交换次数 = 3 x 比较次数,即 3n*(n-1)/2。
所以最坏情况下冒泡排序的整体时间复杂度为O(n*(n-1)/2) + O(3n*(n-1)/2) = O(n2),即最坏时间复杂度为O(n2)。
综上,冒泡排序总的平均时间复杂度为O(2)。
空间复杂度
在整个排序中,我们只用到一个临时变量tmp的内存空间,所以空间复杂度为O(1)。
冒泡算法的稳定性
排序算法的稳定性大家应该都知道,通俗地讲就是能保证排序前两个相等的数据其在序列中的先后位置顺序与排序后它们两个先后位置顺序相同。即:如,如果A i == A j,Ai 原来在 Aj 位置前,排序后 Ai 仍然是在 Aj 位置前。
观察上面过程模拟中加粗的元素2,无论排序前还是排序后加粗的元素2都在未加粗的元素2之前,所以冒泡排序是稳定的排序算法。
总结
每趟比较都是在上一趟比较得到的无序序列中进行的,减少了每趟排序的比较次数。
扩展
但依旧会进行多余比较,例当前这趟排序过后的无序序列已经有序了,还是会进行下一趟排序。
像上面第一个版本中,第二趟排序过后有序序列为[1, 2],无序序列为[2, 3, 5],此时数组整体已经有序了,但还是会对“无序”进行后面的第三、四趟排序。
解决方法:
设置标志位flag,如果当前这趟排序发生了交换,设置flag = true, 否则设置flag = false。
进入下一趟排序前先判断上一趟是否发生了交换,若没有发生交换,即flag = false,则说明上一趟过后数组已经有序,没有必要进行后面几趟排序了。
优化代码如下:
void bubbleSort(vector<int>& v, int begin, int end)
{
int tmp = 0;
bool flag = false;
for (int i = begin, i < end - 1; i++)
{
//每趟排序后需初始化flag
flag = false;
for (int j = end - 1; j > i; j--)
{//从后向前两两比较
if (v[j - 1] < v[j])
{
tmp = v[j];
v[j] = v[j - 1];
v[j - 1] = tmp;
//发生了交换,设置flag为true
flag = true;
}
}
//判断这趟排序是否发生交换
if (!flag)
{
//flag == false说明这趟排序没有发生交换,则无需进行下一趟排序了,之间break退出外层循环
break;
}
}
}
这里给出测试代码:
vector<int> v{ 2, 2, 5, 1, 3 };
cout << "排序前:" << endl;
for (int x : v)
{
cout << x << " ";
}
cout << endl;
cout << "-------------------------------------------\n";
bubbleSort(v, 0, 5);
cout << "排序后:" << endl;
for (int x : v)
{
cout << x << " ";
}
cout << endl;
cout << "-------------------------------------------\n";
测试结果:
优化前:
优化后: