1.常见算法分类
十种常见排序算法一般分为以下几种:
(1)非线性时间比较类排序:交换类排序(快速排序和冒泡排序)、插入类排序(简单插入排序和希尔排序)、选择类排序(简单选择排序和堆排序)、归并排序(二路归并排序和多路归并排序);
(2)线性时间非比较类排序:计数排序、基数排序和桶排序。
总结:
(1)在比较类排序中,归并排序号称最快,其次是快速排序和堆排序,两者不相伯仲,但是有一点需要注意,数据初始排序状态对堆排序不会产生太大的影响,而快速排序却恰恰相反。
(2)线性时间非比较类排序一般要优于非线性时间比较类排序,但前者对待排序元素的要求较为严格,比如计数排序要求待排序数的最大值不能太大,桶排序要求元素按照hash分桶后桶内元素的数量要均匀。线性时间非比较类排序的典型特点是以空间换时间。
注:本博文的示例代码均已递增排序为目的。
2.算法描述与实现
2.1交换类排序
交换排序的基本方法是:两两比较待排序记录的排序码,交换不满足顺序要求的偶对,直到全部满足位置。常见的冒泡排序和快速排序就属于交换类排序。
2.1.1冒泡排序
算法思想:
从数组中第一个数开始,依次遍历数组中的每一个数,通过相邻比较交换,每一轮循环下来找出剩余未排序数的中的最大数并”冒泡”至数列的顶端。
算法步骤:
(1)从数组中第一个数开始,依次与下一个数比较并次交换比自己小的数,直到最后一个数。如果发生交换,则继续下面的步骤,如果未发生交换,则数组有序,排序结束,此时时间复杂度为O(n);
(2)每一轮”冒泡”结束后,最大的数将出现在乱序数列的最后一位。重复步骤(1)。
稳定性:稳定排序。
时间复杂度: O(n)至O(n2),平均时间复杂度为O(n2)。
最好的情况:如果待排序数据序列为正序,则一趟冒泡就可完成排序,排序码的比较次数为n-1次,且没有移动,时间复杂度为O(n)。
最坏的情况:如果待排序数据序列为逆序,则冒泡排序需要n-1次趟起泡,每趟进行n-i次排序码的比较和移动,即比较和移动次数均达到最大值:
比较次数:Cmax=∑i=1n−1(n−i)=n(n−1)/2=O(n2)
移动次数等于比较次数,因此最坏时间复杂度为O(n2)。
示例代码:
void bubbleSort(int array[],int len){
//循环的次数为数组长度减一,剩下的一个数不需要排序
for(int i=0;i
define MAXNUM 20 //待排序数的最大个数
define MAX 100 //待排序数的最大值
int sorted_arr[MAXNUM]={0};
//计算排序
//arr:待排序数组,sorted_arr:排好序的数组,n:待排序数组长度
void countSort(int *arr, int *sorted_arr, int n)
{
int i;
int count_arr = (int )malloc(sizeof(int) * (MAX+1));
//初始化计数数组
memset(count_arr,0,sizeof(int) * (MAX+1));
//统计i的次数
for(i = 0;i<n;i++)
count_arr[arr[i]]++;
//对所有的计数累加,作用是统计arr数组值和小于小于arr数组值出现的个数
for(i = 1; i<=MAX; i++)
count_arr[i] += count_arr[i-1];
//逆向遍历源数组(保证稳定性),根据计数数组中对应的值填充到新的数组中
for(i = n-1; i>=0; i--)
{
//count_arr[arr[i]]表示arr数组中包括arr[i]和小于arr[i]的总数
sorted_arr[count_arr[arr[i]]-1] = arr[i];
//如果arr数组中有相同的数,arr[i]的下标减一
count_arr[arr[i]]--;
}
free(count_arr);
}
注意:计数排序是典型的以空间换时间的排序算法,对待排序的数据有严格的要求,比如待排序的数值中包含负数,最大值都有限制,请谨慎使用。
2.5.2基数排序
基数排序属于“分配式排序”(distribution sort),是非比较类线性时间排序的一种,又称“桶子法”(bucket sort)。顾名思义,它是透过键值的部分信息,将要排序的元素分配至某些“桶”中,藉以达到排序的作用。
具体描述即代码示例见本人另一篇blog:基数排序简介及其并行化。
2.5.3桶排序
桶排序也是分配排序的一种,但其是基于比较排序的,这也是与基数排序最大的区别所在。
思想:桶排序算法想法类似于散列表。首先要假设待排序的元素输入符合某种均匀分布,例如数据均匀分布在[ 0,1)区间上,则可将此区间划分为10个小区间,称为桶,对散布到同一个桶中的元素再排序。
要求:待排序数长度一致。
排序过程:
(1)设置一个定量的数组当作空桶子;
(2)寻访序列,并且把记录一个一个放到对应的桶子去;
(3)对每个不是空的桶子进行排序。
(4)从不是空的桶子里把项目再放回原来的序列中。
例如待排序列K= {49、 38 、 35、 97 、 76、 73 、 27、 49 }。这些数据全部在1—100之间。因此我们定制10个桶,然后确定映射函数f(k)=k/10。则第一个关键字49将定位到第4个桶中(49/10=4)。依次将所有关键字全部堆入桶中,并在每个非空的桶中进行快速排序。
时间复杂度:
对N个关键字进行桶排序的时间复杂度分为两个部分:
(1) 循环计算每个关键字的桶映射函数,这个时间复杂度是O(N)。
(2) 利用先进的比较排序算法对每个桶内的所有数据进行排序,对于N个待排数据,M个桶,平均每个桶[N/M]个数据,则桶内排序的时间复杂度为 ∑i=1MO(Ni∗logNi)=O(N∗logNM) 。其中Ni 为第i个桶的数据量。
因此,平均时间复杂度为线性的O(N+C),C为桶内排序所花费的时间。当每个桶只有一个数,则最好的时间复杂度为:O(N)。
示例代码:
typedef struct node
{
int keyNum;//桶中数的数量
int key; //存储的元素
struct node * next;
}KeyNode;
//keys待排序数组,size数组长度,bucket_size桶的数量
void inc_sort(int keys[],int size,int bucket_size)
{
KeyNode* k=(KeyNode *)malloc(sizeof(KeyNode)); //用于控制打印
int i,j,b;
KeyNode bucket_table=(KeyNode )malloc(bucket_size*sizeof(KeyNode *));
for(i=0;i