其他排序
堆排序
归并排序
插入排序和希尔排序
快速排序
基数排序
对数arr[] = {3,7,6,8,0,4,56,78,45}这个数组进行从小到大排序,设数组的长度为nLen(这里nLen = 9)
冒泡排序
核心思想
:相邻两个元素之间相比,如果前者比后者大,则交换两个值(基于交换
)。
排序过程
:
初始时:3与7相比—>3小于7—>不做交换,继续进行下一次比较—>7与6相比—>7大于6—>交换7与6的值···—>最终,第一次循环找到最大值78放到最后,此时第一次循环结束。
显然,我们可以看出每一次循环我们都能找出当前数组里的最大值然后将其放到数组的后面,由于数组是有长度为nLen个元素,要想找到数组的最大值我们必须得遍历一遍数组,由于每一次循环我们都能找到一个最大值,因此对于下一次循环时遍历时对于之前已经找到的最大值可不做考虑。另外,由于我们每遍历一次数组只能找到一个最大值,因此我们要想对数组排好序就需要进行nLen-1次遍历。
上面这样循环结束之后确实可以把数组排好序,但是我们还能够再进行优化吗?
答案是肯定的。我们考虑一种极其特殊的情况,如对数组{1,2,3,4,5,6,7,8,9}进行排序,这是一种极其特殊的情况,数组来到就是有序的,但是如果我们仍然按照上面那种遍历方法,会使得冒泡排序的效率极其低。我们观察数组,思考一下便可以知道,对于已经有序的数组,是不会发生任何交换的!因此,我们可以设置一个标志,如果数组不发生交换,则直接结束排序,此时对于有序的数组仅需遍历一次。
那么还能不能继续优化呢?
其实是还可以优化,上面是针对的完全有序的数组,对于一个局部有序的数组来讲,上边那种方式并没有提升多少效率,如对数组{5,3,7,1,8,9,10,23},我们可以看出数组最后是有序的,因此对于后半部分已经有序的数组我们可以想办法不进行遍历,而方法就是我们需要标记数组最后一次交换的位置,在下一次循环时只需遍历到该位置。对于数组{5,3,7,1,8,9,10,23}初始时5与3交换记录3的位置下标1,而后3与7不进行交换,再之后7与1进行交换,重新更新先前标记的位置下标为交换前的1处的位置下标,而后都不进行交换,下一次在进行遍历时只需要遍历到标记的位置即可。
以上就是冒泡排序的全部优化过程。
最好时间复杂度
:O(n) 对于冒泡排序的最好时间复杂度来说,就是来到直接是有序的,此时只需要遍历一次,因此最好时间复杂度为O(n)
最坏时间复杂度
:O(n²)对于冒泡排序的最坏时间复杂度来说,就是倒序时,每一个元素都需要进行交换,此时时间复杂度为O(n²)
空间复杂度
:由于没有新开辟空间,也没有递归,因此空间复杂度为O(1)
稳定性:由于冒泡排序不会改变两个相同值得相对顺序,所以是稳定的
使用场合
:从上面来看,对于完全有序或局部有序的数组,冒泡排序的效率越高,因此冒泡排序适用于局部有序或者完全有序的场合。
思维导图
:
排序次数控制外层循环,内层的一次循环只能将一个最大值排到最后位置,因此对于长度为nLen的数组来说需要进行nLen-1次排序才能完全排好序。
代码如下
:
#include <stdio.h>
void Swap(int *a,int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void BubbleSort(int arr[],int nLen)
{
//参数检验
if(arr == NULL || nLen <= 0 )return;
int nFlag;
int nCount = 0;
//循环次数:要排序的数的个数
for(int i = 0;i < nLen - 1;i++)
{
nFlag = 0;
//两个数相比较,冒泡的次数
for(int j = 0;j < nLen -1 - i;j++)
{
if(arr[j] > arr[j+1])
{
nFlag = j+1;
Swap(&arr[j],&arr[j+1]);
}
nCount++;
}
if(nFlag == 0)
break;
i = nLen - nFlag -1;//局部有序时计算的循环次数
//这里可以看成是如果标记的数是nFlag,则说明有nLen-nFlag个局部有序的数,那么就还剩nFlag个数是无序的,接下来只需要我们只需要遍历nLen-nFlag次就行
//了,但是由于本身i有i++的效果,所以我们需要消除这个效果,需要再进行减1处理
}
printf("nCount = %d\n",nCount);
}
void Print(int arr[],int nLen)
{
if(arr == NULL || nLen <= 0) return;
for(int i = 0;i < nLen;i++)
{
printf("%d\t",arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {8,5,3,91,123,12345,23,5672,12367,12,56,78,694,12345,567,10,11};
BubbleSort(arr,sizeof(arr)/sizeof(arr[0]));
Print(arr,sizeof(arr)/sizeof(arr[0]));
return 0;
}
选择排序
核心思想
:在每次循环时标记最大值的位置,循环时不进行交换,在每一次循环结束后再进行交换。
实现方式
:
初始时将下标为0处的值作为最大值,而后从下标0处之后与下标为0处所对应的值相比,如果后者大于前者最大值,则更新最大值的下标。
如上,3不大于8,因此不更新最大值下标,而后9大于8,更新最大值下标为MaxIndex = 2,
而后继续与接下来之后的值比较,这一次循环结束后,最大值下标最终指向最大值29处,即MidIndex = 4;
此时找到了最大值29,因此将29与数组的最后一个值6交换,交换后的结果如上所示,此时经过第一轮比较,已经找到了一个最大值,那么接下来的排序中已经排好后的数可以不进行考虑。同样取MaxIdex = 0;
这一次循环结束后找到了最大值20,因为最大值下标已经与“最后一个”要交换的值下标一样,因此不需要进行交换,继续下一次循环…。
思维导图
代码
#include <stdio.h>
void Swap(int *a,int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void SelectSort(int arr[],int nLen)
{
if(arr == NULL || nLen <= 0) return;
int Max;
for(int j = 0;j < nLen-1;j++)
{
Max = 0;
//找出最大值
for(int i = 0;i < nLen-j;i++)
{
if(arr[i] > arr[Max])
{
Max = i;
}
}
//标记下一个最大位置
Swap(&arr[Max],&arr[nLen-1-j]);
}
}
void Print(int arr[],int nLen)
{
if(arr == NULL || nLen <= 0) return;
for(int i = 0;i < nLen;i++)
{
printf("%d\t",arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {8,5,3,91,123,12345,23,5672,12367,12,56,78,694,12345,567,10,11};
//BubbleSort(arr,sizeof(arr)/sizeof(arr[0]));
SelectSort(arr,sizeof(arr)/sizeof(arr[0]));
Print(arr,sizeof(arr)/sizeof(arr[0]));
return 0;
}
最好时间复杂度
:每一次都需要遍历找到最大值,因此为O(n²)
最坏时间复杂度
:同为O(n²)
平均时间复杂度
:O(n²)
空间复杂度
:O(1)
稳定性
:不稳定
适用场合
:数组无序且数据量小时
排序名称 | 最好时间复杂度 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 | 适用场合 |
---|---|---|---|---|---|---|
BubbleSort | O(n) | O(n2) | O(n2) | O(1) | 稳定 | |
SelectSort | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 | |
InsertSort | O(n) | O(n2) | O(n2) | O(1) | 稳定 | |
ShellSort | O(n) | O(n1.3) | O(n2) | O((log2n) | 不稳定 | |
QuickSort | O(nlogn) | O(nlogn) | O(n2) | O(logn)~O(n) | 不稳定 | |
MergeSort | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 | |
HeapSort | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 稳定 | |
RadixSort | 稳定 |