排序算法
不同的排序算所对应的时间复杂度和空间复杂度不同,按要求选取最合适的排序算法。
相关概念:
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
1.冒泡排序
1.1 基本思想
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
1.2 性能
时间复杂度:O(n²)
稳定性:稳定
1.3 步骤描述
– 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
– 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
– 针对所有的元素重复以上的步骤,除了最后一个;
– 重复步骤1~3,直到排序完成。
1.4 动图演示
1.5 代码实现
int BubbleSort(int a[], int n)
{
int i, j, temp;
for(i = 0; i < n-1; i++)
{
for(j = 0; j < n-i-1; j++)
{
if(a[j] > a[j+1])
{
temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
2.选择排序
2.1 基本思想
第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零
2.2 性能
时间复杂度:O(n²)
稳定性:不稳定
2.3 步骤描述
n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:
– 初始状态:无序区为R[1…n],有序区为空;
– 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
– n-1趟结束,数组有序化了。
2.4 动图演示
2.5 代码实现
void SelectionSort (int num[], int n)
{
int i, j, min, temp;
for(i = 0; i < n-1; i++)
{
min = i; //每次将min设置成无序组起始元素下标
for(j = i; j < n; j++) //遍历无序组,找到最小元素
{
if(num[min] > num[j])
{
min = j;
}
}
if(min != i) //如果最小元素不是无序组起始位置元素, 则与起始元素交换位置
{
temp = num[min];
num[min] = num[i];
num[i] = temp;
}
}
}
3.插入排序
3.1 基本思想
插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序。
通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
3.2 性能
时间复杂度:O(n²)
稳定性:稳定
3.3 步骤描述
– 从第一个元素开始,该元素可以认为已经被排序;
– 取出下一个元素,在已经排序的元素序列中从后向前扫描;
– 如果该元素(已排序)大于新元素,将该元素移到下一位置;
– 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
– 将新元素插入到该位置后;
– 重复步骤2~5。
3.4 动图演示
3.5 代码实现
int InsertSort(int array[], int n)
{
int i, j, temp;
for (i = 1; i < n; i++) {
j = i;
temp = array[i]; //待排序元素赋值给临时变量
while (j > 0 && temp < array[j - 1])
{ //当未达到数组的第一个元素或者待插入元素小于当前元素
array[j] = array[j - 1]; //就将该元素后移
j--; //下标减一,继续比较
}
array[j] = temp; //插入位置已经找到,立即插入
}
}
4.简单桶排序
4.1 基本思想
将待排序的数字放入该数字对应的桶中。
以空间换时间。
4.2 性能
时间复杂度:O(M+N)
循环一共循环了m次(m为桶的个数),第9行的代码循环了n次(n为待排序数的个数)
稳定性:稳定
4.3 步骤描述
– 设置一个定量的数组当作空桶;
– 遍历输入数据,并且把数据一个一个放到对应的桶里去;
– 对每个不是空的桶进行排序;
–从不是空的桶里把排好序的数据拼接起来。
4.4 图片演示
4.5 代码实现
#include <stdio.h>
int main()
{
int i, j, k, n, a[10];
for(i = 0; i < 10; i++)
a[i] = 0; //将每个桶中的数量初始化为0,方便后面出现该数就++
//scanf("%d", &n); //要排序几个数
for(i = 0; i < 10; i++)
{
scanf("%d", &k); //被排序的数字
a[k]++; //如数字5,则将标号为5的桶++,记5出现了几次
}
for(i = 0; i < 10; i++)
{
for(j = 0; j < a[i]; j++) //0→该数出现的个数, 出现几次打印几次
{
printf("%d ", i); //将桶序号输出,没有装数的桶则内层循环不执行, 不输出桶的标号
}
}
return 0;
}
5.快速排序
5.1基本思想
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
5.2 性能
时间复杂度:O (nlogn)
稳定性:不稳定
5.3 步骤描述
通过多次比较和交换来实现排序
– 设定一个分界值,通过该分界值将数组分成左右两部分。
– 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
– 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
– 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
5.4 动图演示
5.5代码实现
void quicksort(int left, int right)
{
int i, j, t, temp;
if(left > right)
return;
temp = a[left]; //temp中存的就是基准数
i = left;
j = right;
while(i != j)
{
//顺序很重要,要先从左往右找
while(a[j] >= temp && i < j)
j--;
//再从左往右找
while(a[i] <= temp && i < j)
i++;
//交换两个数在数组中的位置
if(i < j) //当哨兵i和哨兵j没有相遇时
{
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
//最终将基准数归位
a[left] = a[i];
a[i] = temp;
quicksort(left, i-1); //继续处理左边的,这里是一个递归的过程
quicksort(i+1, right); //继续处理右边的,这里是一个递归的过程
return;
}
注:博客中的动图皆引用自博主:一像素more。