/*issort 插入排序*/
int issort(void *data, int size, int esize, int (*compare)(const void *key1, const void *key2))
{
char *a = data;
void *key;
int i,j;
/*为key元素分配一块空间*/
if((key =(char *)malloc(esize)) == NULL)
return -1;
/*将元素循环插入到已排序的数据集中*/
for(j=1; j < size; j++)
{
/*取无序数据集中的第j个元素,复制到key中*/
memcpy(key, &a[j*esize], esize);
/*设i为j紧邻的前一个元素*/
i = j - 1;
/*从i开始循环查找可以插入key的正确位置*/
/*key和第i个元素对比,如果小于第i个元素就复制i元素到i+1的位置;i递减循环对比*/
while(i >= 0 && compare(&a[i*esize],key)>0)
{
memcpy(&a[(i+1)*esize],&a[i*esize],esize);
i--;
}
/*将key元素的值(也就是要插入的值)复制到while循环后i+1的位置,也就是要插入的位置*/
memcpy(&a[(i+1)*esize],key,esize);
}
/*释放key的空间*/
free(key);
return 0;
}
快速排序是一种分治算法。
广泛地认为它是解决一般问题的最佳算法。同插入排序一样,快速排序也属于比较排序的一种,而且不需要额外的存储空间。在处理中到大型数据集时,快速排序是一个比较好的选择。
我们来看一个人工对一堆作废的支票进行排序的例子,可以将未排序的支票分为两堆。其中一堆专门用来放小于或等于某个编号的支票,而另一堆用来放大于这个编号的支票(假设这个支票大概是所有支票编号的中间值)。当以这种方式得到两堆支票后,又可以以同样的方式将它们分为四堆,不断的重复这个过程直到每个堆中只放有一张支票。这时,所有的支票就已经排好序了。
由于快速排序属于分治算法的一种,我们用分治的思想将排序分为三个步骤:
1、分:设定一个分割值,将数据分为两部分;
2、治:分别在两个部分用递归的方式继续使用快速排序法;
3、合:对分割部分排序直至完成。
快速排序最坏情况下的性能不会比插入排序的最坏情况好。通过一点点修改可以大大改善快速排序最怀情况的效率,使其表现得与其平均情况相当。如何做到这一点,关键在于如何选择分割值。
所选的分割值需要尽可能的将元素平均分开。如果分割值会将大部分的元素放到其中一堆中,那么此时快速排序的性能会非常差。例如:如果用10作为数据值{15,20,18,51,36,10,77,43}的分割值,其结果为{10}和{15,20,18,51,36,77,43},明显不平衡。如果将分割值选为36,其结果为{36,51,77,43}和{15,20,18,10},就比较平衡。
选择分割值的一种有效的方法是通过 随机选择法 来选取。随机选择法能够有效的防止被分割的数据极度不平衡。同时,还可以改进这种随机选择法,方法是:首先随机选择三个元素,然后选择三个元素中的中间值。这就是所谓的中位数方法,可以保证平均情况下的性能。由于这种分割方法依赖随机数的统计特性,从而保证快速排序的整体性能,因此快速排序也是随机算法的一个好例子。
快速排序的接口定义 qksortint qksort(void *data, int size, int esize, int i, int k, int (*compare)(const void *key1, const void *key2);
返回值:如果排序成功,返回0;否则返回-1。
描述: 利用快速排序将数组data中的元素进行排序。数组中的元素个数由size决定。而每个元素的大小由esize决定。参数i和k定义当前进行排序的两个部分,其值分别初始为0和size-1。函数指针compare会指向���个用户定义的函数来比较元素大小,其函数功能与issort中描述的一样。当qksort返回时,data包含已经排好序的元素。
复杂度: O(n lg n),n为要被排序的元素的个数。
快速排序的实现与分析快速排序本质上就是不断地将无序元素集递归分割,直到所有的分区都只包含单个元素。