排序算法分析

根据复杂度一般可以分为三种

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)时间内完成,要好于基于比较进行排序的算法,但是由于每一遍处理所需时间要长的多,所以,不见得比比较排序要好。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值