根据复杂度一般可以分为三种
O(n2):插入排序、冒泡排序;
O(nlgn):归并排序、快速排序、堆排序等;
O(n):计数排序、基数排序、桶排序;
我们主要对常用算法进行分析。
归并排序
归并排序主要思路是:
当一个数组左边有序,右边也有序,那合并这两个有序数组就完成了排序。如何让左右两边有序了?用递归!这样递归下去,合并上来就是归并排序。
代码如下:#include <stdio.h>
#define NUM 10
void print_result(int array[], int begin, int end){
for(int i=begin; i<=end; i++){
printf("%d ", array[i]);
}
printf("\n");
}
/*
middle belong to the first array
*/
void merge(int array[], int begin, int middle, int end){
int head1 = begin;
int head2 = middle + 1;
int head = begin;
int tmp[NUM];
while(head1 <= middle && head2 <= end){
if(array[head1] < array[head2]){
tmp[head] = array[head1];
head1++;
}else{
tmp[head] = array[head2];
head2++;
}
head++;
}
while(head1<=middle){
tmp[head] = array[head1];
head1++;
head++;
}
while(head2<=end){
tmp[head] = array[head2];
head2++;
head++;
}
head--;
while(head>=begin){
array[head] = tmp[head];
head--;
}
}
void merge_sort(int array[], int begin, int end){
int middle = ((begin + end)>>1);
if((end - begin) < 1)
return;
merge_sort(array, begin, middle);
merge_sort(array, middle + 1, end);
merge(array, begin, middle, end);
}
int main()
{
int array[NUM] = {8, 2, 5, 10, 9, 4, 6, 3, 7, 1};
print_result(array, 0, NUM-1);
merge_sort(array, 0, NUM-1);
print_result(array, 0, NUM-1);
}
快速排序
快速排序的思路是,首先选择一个值,然后将剩下的数据分为两部分,一部分都比这个值大,一部分都比这个值小;然后分别对两部分进行递归操作。很明显这是一个分治思想算法。
快速排序的关键是1. 如何选择这个参照值;2.如何才能将数据分为两部分。
如何选择这个参照值
这个值如果不慎重选择,算法复杂度可能会达到O(n2),如每次取得这个参考值后,其余数据都比这个值大或者都比这个值小。
一个比较好的方法是将一个数组第一位、最后一位和中间一位找出中间大小的作为新的参考值,如下面代码的GetPivot函数。
如何将数据分为两部分
我们将获得参考值和最后一个值交换,也就是我们以最后一个元素为参考值。
有两个标示,next_index表示要比较的下一个元素,bigger_index表示比参考值大的元素集合的起始位置。
当所有的元素都处理后,将参考值和较大元素集合开始位的元素交换,这样参考值左边的值都比参考值小,右边的值都比参考值大。
请参考代码部分的Partition函数。
代码
int a[] = {3,8,1,5,4,9,2,6,7};
void print_array(){
for(int i=0; i<9; i++){
printf("a[%d] %d\n", i, a[i]);
}
}
inline int GetPivot(int begin, int end){
int middle = (begin+end)>>1;
if(begin >= end-1)
return end;
if(a[begin]>a[middle] && a[middle]>a[end]){
return middle;
}else if(a[end]>a[middle] && a[middle]>a[begin]){
return middle;
}else if(a[middle]>a[begin] && a[begin]>a[end]){
return begin;
}else if(a[end]>a[begin] && a[begin]>a[middle]){
return begin;
}else if(a[begin]>a[end] && a[end]>a[middle]){
return end;
}else if(a[middle]>a[end] && a[end]>a[begin]){
return end;
}
}
void Swap(int* first, int* second){
int tmp = *first;
if(first == second)
return;
*first = *second;
*second = tmp;
}
int Partition(int begin, int end){
int pivot = GetPivot(begin, end);
int q = begin;
int bigger_index = begin;
int next_index = begin;
/*set the end as the pivot*/
Swap(a+pivot, a+end);
while(next_index<end){
if(a[next_index]<a[end]){
Swap(a+next_index, a+bigger_index);
bigger_index++;
}
next_index++;
}
Swap(a+bigger_index, a+end);
return bigger_index;
}
void QuickSort(int begin, int end){
if(begin >= end)
return;
int pivot = Partition(begin, end);
QuickSort(begin, pivot-1);
QuickSort(pivot+1, end);
}
int _tmain(int argc, _TCHAR* argv[])
{
QuickSort(0, 8);
print_array();
while(1){}
return 0;
}
堆排序
请参考http://blog.csdn.net/gykimo/article/details/8594203 二叉堆。
计数排序
它是以增大空间上的开销来减少时间上的开销。主要思路是:
我们要排序的一堆整数,其中最大值是M,那么就分配一个M大小的tmp数组,tmp数组第K个元素的值就表示大小是K的整数的个数。代码L18-L20将待排序整数映射到tmp数组中。其实这个时候顺序已经排列出来了,对tmp数组从第一个元素开始处理,如果元素值是0,说明没有大小是该索引值的整数,不为0,说明共有相应值个该索引值的整数,逐个将元素的值添加到最后的result数组中即可。但是,这样计算有个问题,比如待排序数据A和B大小相等并且A在B前面,那么A先映射到tmp数组,B后映射到tmp数组,那么,按照上面的处理方式,B先被排序,A后被排序,所以输入和输出是逆序。
所以,如果按照L22-L24处理后,tmp表示整数大小是对应索引值的整数在result的位置最多就是tmp对应位置的值,然后,我们再按照L26-L29从最后一个元素开始处理,通过tmp数组找到应该在result数组的位置,这样就达到了输入和输出是一样的顺序。这个就叫做排序稳定性。
但是,如果M远远大于待排序数据的个数,那么空间开销将会很大,所以计数排序一般适用于M和数据个数成线性关系的情况。
关键词:
1. 整数值设置为索引值
2. 重复整数的个数转换为排序后的位置
3. 如何才能输入输出相对次序一致
#include <stdio.h>
#define MAX_KEY 5
#define NODE_NUM 8
int array[NODE_NUM] = {2, 5, 3, 0, 2, 3, 0, 3};
int result[NODE_NUM];
int tmp[MAX_KEY] = {0};
void print_result(){
for(int i=0; i<NODE_NUM; i++){
printf("%d", result[i]);
}
printf("\n");
}
void counting_sort(){
for(int i=0; i<NODE_NUM; i++){
tmp[array[i]]++;
}
for(int i=1; i<=MAX_KEY; i++){
tmp[i] = tmp[i] + tmp[i-1];
}
for(int i=NODE_NUM-1; i>=0; i--){
result[tmp[array[i]]-1] = array[i];
tmp[array[i]]--;
}
}
int main()
{
counting_sort();
print_result();
}
基数排序
基数排序正好弥补了计数排序的缺点,如比较
325
289
541
如果我们从最低位开始比较那么排序过程为
541
325
289
325
541
289
289
325
541
这样就将三个数排列好了。
另外对每位的比较,可以采用计数排序,因为他们最大是9。
看上去,基数排序可以在O(n)时间内完成,要好于基于比较进行排序的算法,但是由于每一遍处理所需时间要长的多,所以,不见得比比较排序要好。