排序算法有很多,在我们学习数组那一章节的时候,就给大家列过许多的排序代码。本节我们讲两个比较简单的排序算法:冒泡排序与选择排序
冒泡排序
排序思想
相邻两个元素进行比较,将较大者(或者较小者)作为泡给冒出去,也就是交换,当数组所有相邻元素都进行比较过后,最后一个值一定是最大的(或者是最小的)。然后对这样的一个操作进行循环操作,来使所有的最大值(最小值)依次排到数组后面。
排序图解
第一步:从左向右,依次两两比较,将最大的元素放到待排元素区的最后位置。(即下标n-1位置)
第二步: 采用相同的方法,再次遍历 ,将第二大的数,放在数组倒数第二个位置(即n-2的位置),以此类推,直至数组有序。
代码实现
void BubbleSort(int* arr, int size)
{
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr[j], arr[j + 1]);
}
}
}
}
时间复杂度分析
外层循环代表排序的趟数,内层循环代表比较的次数。随着趟数的变化,内层的比较也会依次减少。即总共执行次数为:n-1 + n-2 + n-3 +...+3+2+1=n(n-1)/2,时间复杂度为O(n²)。稳定性:稳定排序。
优化方向
如果有一趟没有进行交换,剩下的趟数必然无法发生交换,后续的比较均是拉低效率的执行。所以设置一个标志变量,来判断是否排序已经完成。
//冒泡排序:时间复杂度 O(n^2),稳定
void BubbleSort(int* arr, int size)
{
for (int i = 0; i < size - 1; i++) {
bool flag = true;//flag代表排序是否完成
for (int j = 0; j < size - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr[j], arr[j + 1]);
flag = false;//交换说明还不知道排序完成了没有
}
}
if (flag)return;//如果完成,就不用接着排了,直接跳出去吧
}
}
选择排序
排序思想
选择排序是将未排序的部分中元素的最大值(最小值)找出来。与未排序区的最后一个元素进行交换。找最值的方式是通过记录数组的下标(索引/位置)实现的。循环进行上述操作,直至排序完成。
排序图解
我们想要将这个列表中的数据按照播放次数按升序排列。
第一步:找出列表中播放次数最多的元素,将它与列表最后一个位置的元素进行交换
第二步:再次这样做,找到播放次数第二多的歌曲
继续这样做,最后将得到一个有序列表:
代码实现
void SelectSort(int* arr, int size)
{
for (int i = 0; i < size; i++) {
int index = i;
for (int j = i + 1; j < size - 1; j++) {
if (arr[j] < arr[index])
index = j;
}
swap(arr[i], arr[index]);
}
}
时间复杂度分析
外层循环是排序的趟数,从零索引位到size-1索引位都需要进行比较。内层循环是比较的次数,将无序区的最大值的索引坐标index找到然后交换。随着趟数的增加,有序区的元素逐渐增多,无需区的元素逐渐减少,所以,该事件复杂度分析方法同冒泡类似,最终结果一样,为O(n²)。
优化方向
思考,进行一次比较快还是执行一次交换操作快?显然判断的效率大于交换的效率。
那么假如排序提前完成,后续的交换都是最后一个元素本身在进行交换,不断地执行交换操作。
//选择排序:时间复杂度 O(n^2),不稳定
void SelectSort(int* arr, int size)
{
for (int i = 0; i < size; i++) {
int index = i;
for (int j = i + 1; j < size - 1; j++) {
if (arr[j] < arr[index])
index = j;
}
if (index != size - 1)//加if只是为了提升一点点效率,可不要判断,直接交换
swap(arr[i], arr[index]);
}
}
其实差别就在于if判断条件这一句代码。但优化的力度非常的小,不写也行,区别不大,而且如果每次都进行判断,执行n次,就会无辜多出n次判断操作,但假如一开始为有序数组,判断的时候就会用n次的判断操作替代执行n次的交换操作,交换操作一般为临时变量法实现,最少具有3句指令。所以,这个优化纯属看心情。
感谢大家!