2.1 插入排序:
接口定义:
int insert_sort(void* data, int size, int esize,
int (*compare)(const void* key1, const void* key2));
返回值:成功 0;失败 -1。
算法描述:
每次从待排序数据集中取出第一个元素,将其插入到有序数据集中,两个数据集共用一块存储空间。两个数据集都存放在data中,data包含size个无序元素,每个元素的大小为esize,函数指针compare指向一个比较元素大小的用户自定义函数。最初,data指向size个无序元素,但是在执行insert_sort时,data所指的空间逐渐被有序元素所取代。
算法分析:
插入排序使用嵌套循环来实现,外循环用于控制循环次数,同时也代表了当前待排序的元素;内循环用于找出当前元素的正确插入位置。插入排序从第二个元素开始插入,因此,外循环需要执行n-1次。在寻找插入位置时,考虑最坏的情况(即逆序数据集中:每次插入的元素刚好是有序数据集中的最小值),则在第num次循环中,需要num次比较操作,如此,整个嵌套循环的执行时间:T(n) = [1 + 2 + 3 + ... + (n - 1)] / 2 = n(n - 1) / 2 ---> O(n2)。对于正序数据集而言,其时间复杂度为O(n)。插入算法不需要额外的存储空间。
算法实现:
1 /* 2 insert_sort(): 3 返回值:成功0;失败 -1 4 */ 5 int insert_sort(void* data, int size, int esize, 6 int (*compare)(const void* key1, const void* key2)) 7 { 8 char* pd = (char*)data; 9 void* pcur; 10 int num, ipos; 11 12 /* 分配一块元素大小的内存 */ 13 if((pcur = (char*)malloc(esize)) == NULL) 14 return -1; 15 16 /* 在有序数据集中循环插入待排元素 */ 17 for(num = 1; num < size; num++) 18 { 19 /* 将待排元素复制到pcur中,第一个元素默认有序,从第二个元素开始 */ 20 memcpy(pcur, &pd[num * esize], esize); 21 22 /* 寻找插入位置ipos */ 23 ipos = num - 1; 24 while((ipos >=0) && (compare(&pd[ipos * esize], pcur) > 0)) 25 { 26 /* 如果*pcur < pd[ipos],则pd[ipos]后移 */ 27 memcpy(&pd[(ipos + 1) * esize], &pd[ipos * esize], esize); 28 ipos--; 29 } 30 memcpy(&pd[(ipos + 1) * esize], pcur, esize); 31 32 } 33 /* 释放内存 */ 34 free(pcur); 35 36 return 0;
2.2 快速排序:
接口定义:
int partition(void* data, int esize, int lpos, int rpos,
int (*compare)(const void* key1, const void* key2));
返回值:成功 ppos;失败 -1
int quick_sort(void* data, int size, int esize,
int lpos, int rpos, int (*compare)(const void* key1, const void* key2));
返回值:成功 0;失败 -1
算法描述:
快速排序利用分治法的思想,首先设定一个分割值pval将数据集分为左右两个分区,然后分别对左右两个分区使用快速排序,如此递归直至区间中只有一个元素为止。数据集data包含size个无序元素,每个元素的大小为esize,函数指针compare指向一个比较元素大小的用户自定义函数。lpos和rpos分别为与当前分割值pval进行比较的位置。首次调用quick_sort()时,lpos设为0,rpos设为size – 1,相当于对全部元素进行排序(也可以自己设定感兴趣区间进行排序)。以partition()函数返回的ppos作为分割界限将数据集分为左右两个区间。分别对两个分区递归地进行快排,直至传入quick_sort()的分区只包含一个元素为止,此时排序完成。
算法分析:
影响快速排序算法性能的关键是分割值的选取,也就是partition()函数如何分割数据集。分割值一般不能太极端,这样会使得大部分数据被分到一个分区中,此时,快排的性能非常差,为O(n2)。一个比较好的方法是在区间中随机选择三个元素,然后选择三个元素中的中间值,就是通常的中位数法,实验表明该方法可以使得快排接近平均性能O(nlgn)。另外快速排序不需要额外的存储空间。
算法实现:
/* partition(): return: success ppos; fail -1 */ int partition(void* data, int esize, int lpos, int rpos, int (*compare)(const void* key1, const void* key2)) { char* pd = (char*)data; void* pval; /* partition value */ void* ptemp; int r[3]; /* 为partition value和swapping分配空间 */ if((pval = malloc(esize)) == NULL) return -1; if((ptemp = malloc(esize)) == NULL) { free(pval); return -1; } /* 计算partition value */ r[0] = (rand() % (rpos - lpos + 1)) + lpos; r[1] = (rand() % (rpos - lpos + 1)) + lpos; r[2] = (rand() % (rpos - lpos + 1)) + lpos; insert_sort(r, 3, sizeof(int), compare_int); memcpy(pval, &pd[r[1] * esize], esize); /* 根据pval进行partition */ lpos--; rpos++; while(1) { /* 如果pd[rpos * esize] > *pval, 将rpos左移 */ do{ rpos--; }while(compare(&pd[rpos * esize], pval) > 0); /* 如果pd[lpos * esize] < *pval, 将lpos右移 */ do{ lpos++; }while(compare(&pd[lpos * esize], pval) < 0); if(lpos >= rpos) break; /* 停止partition */ else { /* 交换lpos和rpos处的元素 */ memcpy(ptemp, &pd[lpos * esize], esize); memcpy(&pd[lpos * esize], &pd[rpos * esize], esize); memcpy(&pd[rpos * esize], ptemp, esize); } } /* 释放空间 */ free(ptemp); free(pval); return rpos; }
/* quick_sort(): return: success 0; */ int quick_sort(void* data, int size, int esize, int lpos, int rpos, int (*compare)(const void* key1, const void* key2)) { int ppos; if(lpos < rpos) { if((ppos = partition(data, esize, lpos, rpos, compare)) < 0) return -1; /* 递归地对左分区进行排序 */ if(quick_sort(data, size, esize, lpos, ppos, compare) < 0) return -1; /* 递归地对右分区进行排序 */ if(quick_sort(data, size, esize, ppos + 1, rpos, compare) < 0) return -1; } return 0; }