qsort库函数的源代码中用了recurese + goto的递归结构。
②bug回顾
(1)由于某处看过学c不推荐使用goto;
(2)由于c中不能在一个函数中嵌套定义另一个函数;
所以我把递归的部分抽出来,放进另一个函数体。
写完发现可能自己过两天就看不懂了......于是几乎每一步都加了很多注释。
配合图解其实更方便。
①myqsort()改写实例:
//前置函数,按字节交换指针指向的内容
void myswap(char *p1, char *p2, size_t width) {
// char *p1 = (char*)a;
// char *p2 = (char*)b;
char tmp;
if (p1 != p2)
while (width--) {
tmp = *p1;
*p1++ = *p2;
*p2++ = tmp;
}
return;
}
void sort_recurs(char *lo, char *hi, size_t width, int(*comp)(const void*, const void*), char **lostkptr, char** histkptr, int &stkptr);
#define STKSIZ 64 //合适的上限
void myqsort(void *base, size_t num, size_t width, int(*comp)(const void *, const void *)) {
char *lo, *hi;
int stkptr;
char *lostk[STKSIZ], *histk[STKSIZ];
//initial
stkptr = 0;
lo = (char*)base;
hi = (char*)base + width * (num - 1);
//validation check
if (num <= 2) {
if (comp(lo, hi)>0)
myswap(lo, hi, width);
return;
}
//递归处理较小区域 ,第一次是处理全域
sort_recurs(lo, hi, width, comp, lostk, histk, stkptr); //stkptr需要时时变更
//递归处理栈中留存的较大区域
while (stkptr--) {
lo = lostk[stkptr];
hi = histk[stkptr];
sort_recurs(lo, hi, width, comp, lostk, histk, stkptr); //stkptr需要时时变更
}
//所有栈区数据递归完毕
return;
}
void sort_recurs(char *lo, char *hi, size_t width, int(*comp)(const void*, const void*), char **lostk, char** histk, int &stkptr) {
//-----------start recursion-------三分排序-----
//另写函数,需要 width,comp ,hi,lo, lostk,histk,stkptr
//refresh data
int size; //统计递归时,参与排序的子串长度
size = (hi - lo) / width + 1; //参与排序个数 ,可以有个cutoff分支
if (size == 2) { //递归时,size至少是2开始的。而size==2时无法3分,特殊处理,保证递归收敛。
if (comp(lo, hi)>0)
myswap(lo, hi,width);
return;
}
//divide into three partion
char *mid; //定义中间点
mid = lo + (size / 2)*width;
//对三个分割点排序
if (comp(lo, mid)>0) {
myswap(lo, mid, width);
}
if (comp(lo, hi)>0) {
myswap(lo, hi, width);
}
if (comp(mid, hi)>0) {
myswap(mid, hi, width);
}
//启动游动扫描
char *loguy, *higuy; //定义游动指针
loguy = lo;
higuy = hi;
while (loguy<higuy) { //交叉时退出循环
//扫描数组,找到A[loguy]>中位数
if (loguy<mid) {
do {
loguy += width;
} while (loguy<mid && comp(loguy, mid) <= 0);
}
if (loguy >= mid) {
do {
loguy += width;
} while (loguy <= hi && comp(loguy, mid) <= 0);
}
//扫描至此,保证loguy左侧所有数据<= A[mid]
//loguy 有2种情况,
//1. A[loguy]>A[mid],loguy<=hi,即找到一个比A[mid]大的数
//2. loguy>hi,说明数组内所有数都满足<=A[mid],游动指针loguy超出定义范围
//下面游动higuy,扫描至 A[higuy] <= A[mid],而hiugy位置保证在右半侧
do {
higuy -= width;
} while (higuy>mid && comp(higuy, mid)>0);
//扫描结束时,higuy停留在mid,或者在mid右侧找到 A[higuy]<=A[mid]
if (loguy<higuy) { //说明尚未交叉
//交换数据
//loguy指向的比中位数大的数据,可以交换到右侧的higuy来
//higuy指向的<=中位数的数据,可以交换到loguy所在
myswap(loguy, higuy, width);
//特殊情况下,higuy=mid,故交换后跟踪的原中位数A[mid]位置变去了loguy
//需要调整mid指针,使其重新指向中位数
if (mid == higuy) {
mid = loguy;
}
}
}
//退出循环了,说明loguy与higuy交叉
//由于loguy左侧都是<=A[mid],loguy是第一个>A[mid]的点
//而higuy右侧都是>A[mid],higuy是第一个<=A[mid]的点
//有2种情况
// 1. 两者相差1单位, higuy = loguy - width;
// 2. 全部数据满足<=mid,所以loguy=hi+width,而higuy只运动1次,higuy= hi-width
//分类讨论
higuy += width; //此时higuy与loguy重合,或者higuy抵达hi位置
//loguy左侧都 <= mid
//找寻左侧第一个 < mid的位置,找不到时抵达lo
if (mid < higuy) {
do {
higuy -= width;
} while (higuy > mid && comp(higuy, mid) == 0); //设计上避免了comp(mid,mid)
}
if (mid >= higuy) {
do {
higuy -= width;
} while (higuy>lo && comp(higuy, mid) == 0); //同样不会出现comp(mid,mid)
}
//此时若higuy未抵达lo,则出现3分天下
// lo <= x <= higuy, A[x]<=mid
// higuy< x < loguy, A[x]==mid //从loguy开始-=width
// x>=loguy(假设loguy有定义), A[x]>mid
//[lo~higuy],(higuy~loguy),[loguy~hi]
//优先递归处理较小区域
if (higuy - lo >= hi - loguy) { //巧妙之处在于,当loguy无定义时,也可以正常处理higuy-lo区间
// [loguy,hi]较小
if (lo<higuy) { //储存较大区域,相等时不做处理
lostk[stkptr] = lo;
histk[stkptr] = higuy;
++stkptr; //栈顶指针上移
}
if (loguy<hi) { //相等或无定义时不做处理
lo = loguy;
//递归,三分整理 loguy~hi区间
sort_recurs(lo, hi, width, comp, lostk, histk, stkptr); //stkptr需要时时变更
}
}
else {
// [lo,higuy]较小
if (loguy<hi) { //储存较大区域,相等或无定义时不处理
lostk[stkptr] = loguy;
histk[stkptr] = hi;
stkptr++;
}
if (lo<higuy) {
hi = higuy;
//递归,三分整理 lo~higuy区间
sort_recurs(lo, hi, width, comp, lostk, histk, stkptr); //stkptr需要时时变更
}
}
//--------end recursion---------
return;
}
②bug回顾
编译通过之后,测试IO时发现有bug。
检查了很久,发现是对三分端点的排序代码出错了。
提问:给定3个数,如何设计一个方案,保证3个数从小到大排序?
我一开始写的是
if (comp(lo, mid)>0) {
swap(lo, mid, width);
}
if (comp(mid, hi)>0) {
swap(mid, hi, width);
}
if (comp(lo, hi)>0) {
swap(lo, hi, width);
}
后来发现这个顺序,(a,b) (b,c) (a,c)是不行的。
令数据(a,b,c)=(7,10,1)
1. 比较(a,b) => (7,10,1)
2. 比较(b,c) => (7,1,10)
3. 比较(a,c) => (7,1,10)
产生错误结果。
回头看了一下库源码,人家用的顺序是(a,b)(a,c) (b,c)。
这个低级错误没什么好说的只能背了......
-
第二个bug出现在swap函数里
一不留神写了swap(lo,hi);
原来c++中确实存在名为swap的这个函数,所以编译没有报错。
摘录源码,它是传入left和right的两个引用,然后交换他们的值。
template<class _Ty,
class> inline
void swap(_Ty& _Left, _Ty& _Right)
_NOEXCEPT_COND(is_nothrow_move_constructible_v<_Ty>
&& is_nothrow_move_assignable_v<_Ty>)
{ // exchange values stored at _Left and _Right
_Ty _Tmp = _STD move(_Left);
_Left = _STD move(_Right);
_Right = _STD move(_Tmp);
}
但是传入指针时,交换的是指针地址,对地址上存的数值没有影响。
举例:
对数组a[0]=21,a[1]=3,存在两个指针。
int* 类型的p1= 0x0119c138 ,指向的数字为21
int* 类型的p2= 0x0119c13c ,指向的数字为3
传入swap(p1,p2)
两者交换之后
p1 = 0x0119c13c
p2 = 0x0119c138
只是改变了指针而已......
对于数组而言,
a[0]在0x0119c138地址上存的值还是21......
a[1]在0x0119c13c地址上存的值还是3......
这也太蠢了......
自定义函数命名还是选不常见的好,别跟库函数撞了。
③源码参考
两篇博文内容相近,对照着看收益颇多。
https://blog.csdn.net/u011822516/article/details/17059693
ttps://blog.csdn.net/gen_ye/article/details/52880461