目录
普通的冒泡排序
冒泡排序是一种交换排序。
什么是交换排序呢?
交换排序:两两比较待排序的关键字,并交换不满足次序要求的那对数,直到整个表都满足次序要求为止。
● 冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
● 冒泡排序算法的运作如下:
- 是临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换,
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最大或最小的数字被交换到了最后一位(每将一个数归位我们将其称为“一趟冒泡”)
- 针对所有的元素重复以上的步骤,直到最后一个尚未归位的数,已经归位的数列无需在进行比较,(比如第二趟冒泡,最后一个数就不需要比较了,因为第一次冒泡已经确定了最后一个数是该数列中最大或最小的数,所以就不需要在进行比较了)。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
“冒泡排序”的原理是:
- 每一趟只能确定将一个数归位。即第一趟只能确定将末位上的 数(即第 5 位)归位,第二趟只能将倒数第 2 位上的数(即第 4 位)归位,第三趟只能将倒 数第 3 位上的数(即第 3 位)归位,而现在前面还有两个位置上的数没有归位,因此我们仍 然需要进行“第四趟”。
● 最后我们总结一下:如果有 n 个数进行排序,只需将 n-1 个数归位,也就是说要进行 n-1 趟操作。 而“每一趟”都需要从第 1 位开始进行相邻两个数的比较,将较小的一个数放 在前面,比较完毕后向后挪一位继续比较下面两个相邻数的大小,重复此步骤,直到最后一
个尚未归位的数,已经归位的数则无需再进行比较(已经归位的数你还比较个啥,浪费表情)。
下面看我的示例代码:
#include<iostream>
#include<cassert>
using namespace std;
class SqList
{
public:
SqList(size_t sizeElem);
~SqList();
void printElem();
void swapElem(int &a, int &b);
void BubbleSort();
void create(const size_t length);
private:
int *m_base; //指向数组
int m_length; //记录数组中的个数
};
SqList::SqList(size_t sizeElem)
{
m_base = new int[sizeElem];
assert(m_base != nullptr);
m_length = 0;
}
SqList::~SqList()
{
delete m_base;
m_base = nullptr;
}
void SqList::create(const size_t length)
{
m_length = length;
cout << "请分别输入你想排序的这" << length << "个元素,中间以回车键隔开:\n";
for (size_t i = 0; i != length; ++i)
{
cin >> m_base[i];
}
cout << endl;
}
void SqList::printElem()
{
for (size_t i = 0; i != m_length; ++i)
{
cout << m_base[i] << " ";
}
cout << endl;
}
void SqList::swapElem(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
void SqList::BubbleSort()
{
/*int count = 0; //设置一个临时变量,看几趟,才能排序完成
for (int i = 0; i < m_length -1; ++i)
{
for (int j = m_length -2 ; i <= j ; --j) //这是从数组的后面往前面循环
{
if (m_base[j] > m_base[j+1]) //若前者大于后者,就交换
{
swapElem(m_base[j],m_base[j+1]);
}
}
++count;
}
cout << "普通交换排序花了" << count << "趟,完成的排序!" << endl;*/
//这是从数组的前面往后面循环
int count = 0; //设置一个临时变量,看几趟,才能排序完成
for (int i = 0; i < m_length -1 ; ++i) //如果有 m_length个数进行排序,只需将 m_length-1 个数归位,也就是说要进行 n-1 趟操作。
{
for (int j = 0; j < m_length -i- 1; ++j)
{
if (m_base[j] > m_base[j + 1])
{
swapElem(m_base[j], m_base[j + 1]);
}
}
++count;
}
cout << "普通冒泡排序花了" << count << "趟,完成的排序!" << endl;
}
int main()
{
{
int sizeCapacity(0);
cout << "输入数组的最大容量:";
cin >> sizeCapacity;
SqList mySqList(sizeCapacity);
while (true)
{
{
cout << "\n************************ 欢迎来到来到普通冒泡排序的世界!**********************\n" << endl
<< "输入0,退出程序!" << endl
<< "输入1,进行冒泡排序!" << endl
<< "输入2,清屏!" << endl;
}
cout << "************************* 请输入你想要使用的功能的序号 **********************" << endl;
int select(0);
cout << "请输入你的选择:";
cin >> select;
if (!select)
{
cout << "程序已退出,感谢你的使用!" << endl;
break;
}
switch (select)
{
case 1:
{
cout << "请输入你想排序数组元素的个数:";
int arraySize(0);
cin >> arraySize;
assert(arraySize != 0);
mySqList.create(arraySize);
cout << "先输出排序前的元素:";
mySqList.printElem();
mySqList.BubbleSort();
cout << "再输出排序后的元素:";
mySqList.printElem();
break;
}
case 2:
system("cls");
cout << "程序已清屏!可以重新输入!" << endl;
break;
default:
cout << "输入的序号不正确,请重新输入!" << endl;
}
}
}
system("pause");
return 0;
}
使用标志的冒泡排序
带标志非常有效,全部排序都需要这个,新手这个务必掌握。只要这一趟没有发生交换,那么就会停止循环扫描,减少了扫描的次数。一个很简单的例子,比如一个顺序的一组元素,就拿1,2,3,4,5来说吧,第一趟扫描完,发现没有元素交换,如果没有标志,最外层循环条件为for(int i=0; i <5;++i)
下面看标志冒泡的代码示例:
#include<iostream>
#include<cassert>
using namespace std;
class SqList
{
public:
SqList(size_t sizeElem);
~SqList();
void printElem();
void swapElem(int &a, int &b);
void BubbleSort();
void create(const size_t length);
private:
int *m_base; //指向数组
int m_length; //记录数组中的个数
};
SqList::SqList(size_t sizeElem)
{
m_base = new int[sizeElem];
assert(m_base != nullptr);
m_length = 0;
}
SqList::~SqList()
{
delete m_base;
m_base = nullptr;
}
void SqList::create(const size_t length)
{
m_length = length;
cout << "请分别输入你想排序的这" << length << "个元素,中间以回车键隔开:\n";
for (size_t i = 0; i != length; ++i)
{
cin >> m_base[i];
}
cout << endl;
}
void SqList::printElem()
{
for (size_t i = 0; i != m_length; ++i)
{
cout << m_base[i] << " ";
}
cout << endl;
}
void SqList::swapElem(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
void SqList::BubbleSort()
{
/*int count = 0; //设置一个临时变量,看几趟,才能排序完成
bool flag = true;
for (int i = 0; i < m_length -1 && flag ; ++i)
{
flag = false;
for (int j = m_length -2 ; i <= j ; --j) //这是从数组的后面往前面循环
{
if (m_base[j] > m_base[j+1]) //若前者大于后者,就交换
{
swapElem(m_base[j],m_base[j+1]);
flag = true;
}
}
++count;
}
cout << "普通交换排序花了" << count << "趟,完成的排序!" << endl;*/
//这是从数组的前面往后面循环
int count = 0; //设置一个临时变量,看几趟,才能排序完成
bool flag = true; //设置一个标志flag, 如果某一趟没有交换,那么该序列就是有序的
for (int i = 0; i < m_length -1 && flag; ++i) //如果有 m_length个数进行排序,只需将 m_length-1 个数归位,也就是说要进行 n-1 趟操作。
{
flag = false;
for (int j = 0; j < m_length -i- 1; ++j)
{
if (m_base[j] > m_base[j + 1])
{
swapElem(m_base[j], m_base[j + 1]);
flag = true;
}
}
++count;
}
cout << "普通冒泡排序花了" << count << "趟,完成的排序!" << endl;
}
int main()
{
{
int sizeCapacity(0);
cout << "输入数组的最大容量:";
cin >> sizeCapacity;
SqList mySqList(sizeCapacity);
while (true)
{
{
cout << "\n************************ 欢迎来到来到普通冒泡排序的世界!**********************\n" << endl
<< "输入0,退出程序!" << endl
<< "输入1,进行冒泡排序!" << endl
<< "输入2,清屏!" << endl;
}
cout << "************************* 请输入你想要使用的功能的序号 **********************" << endl;
int select(0);
cout << "请输入你的选择:";
cin >> select;
if (!select)
{
cout << "程序已退出,感谢你的使用!" << endl;
break;
}
switch (select)
{
case 1:
{
cout << "请输入你想排序数组元素的个数:";
int arraySize(0);
cin >> arraySize;
assert(arraySize != 0);
mySqList.create(arraySize);
cout << "先输出排序前的元素:";
mySqList.printElem();
mySqList.BubbleSort();
cout << "再输出排序后的元素:";
mySqList.printElem();
break;
}
case 2:
system("cls");
cout << "程序已清屏!可以重新输入!" << endl;
break;
default:
cout << "输入的序号不正确,请重新输入!" << endl;
}
}
}
system("pause");
return 0;
}
时间复杂度
1.设对象个数为n 个,比较次数和移动次数与初始排序有关
最好的情况 ——若文件的初始状态是正序的(如:1 2 3 4 5),一趟扫描即可完成排序。所需的关键字比较次数和记录移动次数均达到最小值:
比较次数 = N - 1, 移动次数= 0。该序列没有数据交换,所以,冒泡排序最好时间复杂度为O(N)。
最坏的情况——若初始条件是反序的(如:5 4 3 2 1),需要进行 N -1 趟排序 :
比较次数 = N(N-1)/2 = O(N²)
移动次数 = N(N-1)/2 = O(N²)
冒泡排序的最坏时间复杂度为O(N²)。
因此,冒泡排序的平均时间复杂度为O(N²)。
空间复杂度为O(1)
总结起来,其实就是一句话:当数据越接近正序时,冒泡排序性能越好。
算法稳定性
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。
所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。