LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 11276|回复: 5

[算法]有兴趣研究排序算法的进来看看~

[复制链接]
发表于 2005-11-7 11:00:50 | 显示全部楼层 |阅读模式
这是一个字符串数组的内排序函数,我使用的是在qsort基础上针对字符串的改进版本,
另外还有个特殊之处在于事先已经知道Strs[0]是最小值,Strs[num-1]是最大值

  1. __forceinline void StrSort(char** Strs, size_t num)
  2. {//注意:Strs[0]必须是最小值,Strs[num-1]必须是最大值;num>0
  3.         size_t lo=0, mid_lo, mid_hi, hi=num-1; //当前子表的 低/中低/中高/高 指针
  4.         size_t loguy, higuy; //用于划分步骤的低/高滑动指针
  5.         size_t size=num; //当前子表大小
  6.         unsigned short preLen=0; //当前子表的公共前缀长度
  7.         unsigned char *src, *dst; //用于比较两个字符串(防止破坏原字符串)[必须强制为unsigned char*,由于'\0'结尾的原因]
  8.         short comp=0; //上述两字符串的比较结果:负 src<dst,零 src=dst,正 src>dst
  9.         size_t p, max; //小子表冒泡排序时的滑动索引和指向局部最大值的索引
  10.         char* tmp; //临时字符串
  11.         size_t lostk[32]={0}, histk[32]={0}; //保存子表低/高指针的FILO栈[因为小表优先处理,所以32已经足够大了]
  12.         char stkptr=0; //上述栈的深度
  13.         if(NULL==Strs||num<2) return; //无事可做

  14. PseudoRecursion://更新lo,hi后,跳转到此处模拟递归
  15.         preLen=0; src=Strs[lo]; dst=Strs[hi]; while(*src==*dst && *dst) src++,dst++,preLen++; //计算当前子表的公共前缀长度
  16.         if((size=hi-lo+1)<11)
  17.         {//如果子表中乱序元素个数<=8,则时间复杂度为O(n^2)的冒泡算法效果最好[前人的经验,没有理由]
  18.                 hi--; lo++;//因为首尾已经分别是最小/大值了
  19.                 while(hi>lo)
  20.                 {
  21.                         max=lo;//假定索引lo指向局部最大值
  22.                         for(p=lo+1; p<=hi; p++)
  23.                         {//寻找指向数组中局部最大值的真实索引max
  24.                                 comp=0; src=Strs[p]+preLen; dst=Strs[max]+preLen; while(!(comp=*src-*dst) && *dst) src++,dst++; //比较两个字符串
  25.                                 if(comp>0) max=p;//如果p指向的值更大就更新max
  26.                         }
  27.                         tmp=Strs[max]; Strs[max]=Strs[hi]; Strs[hi]=tmp; //交换,使得hi处为局部最大值
  28.                         hi--;
  29.                 }
  30.         }
  31.         else
  32.         {//子表较大(>=11),继续分割
  33.                 mid_lo=mid_hi=lo+size/2;//用于划分子表的中间索引[不能用(lo+hi)/2,可能会溢出]
  34.                 loguy=lo+1; higuy=hi-1;//低/高端滑动指针[因为首尾已经分别是最小/大值了]
  35.                 while(1)
  36.                 {//下面采取的策略是分割成3子表而不是传统的2子表,这对于改善有大量相同元素的表的效率有极大的提高!
  37.                         while(loguy<mid_lo)
  38.                         {//将loguy向高端滑动
  39.                                 comp=0; src=Strs[loguy]+preLen; dst=Strs[mid_lo]+preLen; while(!(comp=*src-*dst)&&*dst)src++,dst++; //比较两个字符串
  40.                                 if(comp<0) loguy++; //低端较小,符合顺序(继续下一个)
  41.                                 else if(comp>0) break; //低端较大,需要交换(暂停移动)
  42.                                 else { mid_lo--; tmp=Strs[loguy]; Strs[loguy]=Strs[mid_lo]; Strs[mid_lo]=tmp; }//相等,减小mid_lo并交换
  43.                         }
  44.                         while(mid_hi<higuy)
  45.                         {//将higuy向低端滑动
  46.                                 comp=0; src=Strs[mid_hi]+preLen; dst=Strs[higuy]+preLen; while(!(comp=*src-*dst)&&*dst)src++,dst++; //比较两个字符串
  47.                                 if(comp<0) higuy--; //低端较小,符合顺序(继续下一个)
  48.                                 else if(comp>0) break; //低端较大,需要交换(暂停移动)
  49.                                 else { mid_hi++; tmp=Strs[higuy]; Strs[higuy]=Strs[mid_hi]; Strs[mid_hi]=tmp; }//相等,增大mid_hi并交换
  50.                         }
  51.                         //根据不同的情况采取不同的措施
  52.                         if(loguy==mid_lo&&mid_hi==higuy) break;
  53.                         tmp=Strs[loguy]; Strs[loguy]=Strs[higuy]; Strs[higuy]=tmp;
  54.                         if(loguy<mid_lo&&mid_hi<higuy){loguy++; higuy--;}
  55.                         else if(loguy==mid_lo&&mid_hi<higuy){mid_lo++; mid_hi++; tmp=Strs[mid_hi]; Strs[mid_hi]=Strs[higuy]; Strs[higuy]=tmp;}
  56.                         else {mid_lo--; mid_hi--; tmp=Strs[loguy]; Strs[loguy]=Strs[mid_lo]; Strs[mid_lo]=tmp;}//if(loguy<mid_lo&&mid_hi==higuy)
  57.                 }
  58.                 //分割完毕;下面优先处理较小的子表,以避免栈溢出
  59.         if(mid_lo-lo<hi-mid_hi)
  60.                 {//低端子表较小
  61.                         if(hi-mid_hi>2){lostk[stkptr]=mid_hi; histk[stkptr]=hi; ++stkptr;} //将高端子表压栈//仅压3元素以上的子表
  62.                         if(mid_lo-lo>2){hi=mid_lo; goto PseudoRecursion;}//仅处理3元素以上的子表
  63.         }
  64.         else
  65.                 {//高端子表较小
  66.                         if(mid_lo-lo>2){lostk[stkptr]=lo; histk[stkptr]=mid_lo; ++stkptr;} //将低端子表压栈//仅压3元素以上的子表
  67.                         if(hi-mid_hi>2){lo=mid_hi; goto PseudoRecursion;}//仅处理3元素以上的子表
  68.         }
  69.         }
  70.     if(--stkptr>=0){ lo=lostk[stkptr]; hi=histk[stkptr]; goto PseudoRecursion; }
  71.     else return;
  72. }

复制代码
发表于 2005-11-7 11:35:19 | 显示全部楼层
so good!
为什么不去学科理论版去讨论呢?那个版太需要人气了
回复 支持 反对

使用道具 举报

发表于 2005-11-7 14:48:59 | 显示全部楼层
呵呵,坚决不使用递归吗?goto让人有点不适应
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-11-7 15:51:18 | 显示全部楼层

关于该算法的几点说明

1,为什么不直接用递归而是使用一个栈来模拟递归?
一方面可以减小函数递归调用带来的开销(其实维护一个栈需要的开销与递归差不多,只稍微少一点点而以)。
另一方面是最主要的原因,使用栈可以优先处理较小的子表,这样即使在最坏的情况下,栈的深度也不会超过30,而递归无法保证优先处理小的子表,从而无法保证栈的深度较小,而在最坏的情况下栈的深度将等于(num-10),这是不可接受的(内存也许会爆掉)!

2,为什么要假定:Strs[0]必须是最小值,Strs[num-1]必须是最大值?
通常仅仅在处理大量数据的时候才会非常关心效率的问题,而此时的数据通常保存在硬盘上,从硬盘调入内存的时候,瓶颈在硬盘,故而可以利用此速度差在读取的同时计算轻易找出最小/大字符串的值,并将其调换到字符串数组的首尾。进而给此排序函数提供额外的信息。

3,为什么要多花几个步骤来维护一个3子表,而不是传统的2子表?
因为传统QuickSort的2子表在数组中没有什么重复元素的情况下工作的非常好,但是一旦表中出现大量的重复元素,效率就会变得不可思议的低!而3子表在这种情况下仍然能工作的非常好,所以多花些步骤避免难堪的最坏结果是值得的。

4,为什么要在处理每个子表前计算公共前缀的长度?
算法中有大量的字符串比较,耗去了主要的时间,因为比较两有10个相同前缀的字符串需要多做10次无效劳动。所以如何减少无效劳动就显得很重要,因此在基于每个子表的最小/大字符串已知的情况下,可以计算出该子表的公共前缀的长度,从而线性的减少无效劳动,节约的时间很可观。

5,为什么将交换函数和比较函数集成在内部?
当然是为了最大限度减少函数调用的开销喽~虽然这样做的结果是代码看上去更加臃肿。但是为了效率,还是值得的!

6,为什么在子表较小(<11)的时候停止递归,转而使用冒泡排序?
前人的经验表明,在无序数组长度<=8的情况下,时间复杂度为O(n^2)的冒泡算法效果最好。因此它很适合用来处理最终的小子表。


======================
就说这几点,欢迎大伙讨论!!
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-11-7 15:54:47 | 显示全部楼层

有兴趣研究排序算法的进来看看~

关于该算法的几点说明
----------------------------------------
1,为什么不直接用递归而是使用一个栈来模拟递归?
一方面可以减小函数递归调用带来的开销(其实维护一个栈需要的开销与递归差不多,只稍微少一点点而以)。
另一方面是最主要的原因,使用栈可以优先处理较小的子表,这样即使在最坏的情况下,栈的深度也不会超过30,而递归无法保证优先处理小的子表,从而无法保证栈的深度较小,而在最坏的情况下栈的深度将等于(num-10),这是不可接受的(内存也许会爆掉)!

2,为什么要假定:Strs[0]必须是最小值,Strs[num-1]必须是最大值?
通常仅仅在处理大量数据的时候才会非常关心效率的问题,而此时的数据通常保存在硬盘上,从硬盘调入内存的时候,瓶颈在硬盘,故而可以利用此速度差在读取的同时计算轻易找出最小/大字符串的值,并将其调换到字符串数组的首尾。进而给此排序函数提供额外的信息。

3,为什么要多花几个步骤来维护一个3子表,而不是传统的2子表?
因为传统QuickSort的2子表在数组中没有什么重复元素的情况下工作的非常好,但是一旦表中出现大量的重复元素,效率就会变得不可思议的低!而3子表在这种情况下仍然能工作的非常好,所以多花些步骤避免难堪的最坏结果是值得的。

4,为什么要在处理每个子表前计算公共前缀的长度?
算法中有大量的字符串比较,耗去了主要的时间,因为比较两有10个相同前缀的字符串需要多做10次无效劳动。所以如何减少无效劳动就显得很重要,因此在基于每个子表的最小/大字符串已知的情况下,可以计算出该子表的公共前缀的长度,从而线性的减少无效劳动,节约的时间很可观。

5,为什么将交换函数和比较函数集成在内部?
当然是为了最大限度减少函数调用的开销喽~虽然这样做的结果是代码看上去更加臃肿。但是为了效率,还是值得的!

6,为什么在子表较小(<11)的时候停止递归,转而使用冒泡排序?
前人的经验表明,在无序数组长度<=8的情况下,时间复杂度为O(n^2)的冒泡算法效果最好。因此它很适合用来处理最终的小子表。

就说这几点,欢迎大伙讨论!!

[PHP]
__forceinline void StrSort(char** Strs, size_t num)
{//注意:Strs[0]必须是最小值,Strs[num-1]必须是最大值;num>0
        size_t lo=0, mid_lo, mid_hi, hi=num-1; //当前子表的 低/中低/中高/高 指针
        size_t loguy, higuy; //用于划分步骤的低/高滑动指针
        size_t size=num; //当前子表大小
        unsigned short preLen=0; //当前子表的公共前缀长度
        unsigned char *src, *dst; //用于比较两个字符串(防止破坏原字符串)[必须强制为unsigned char*,由于'\0'结尾的原因]
        short comp=0; //上述两字符串的比较结果:负 src<dst,零 src=dst,正 src>dst
        size_t p, max; //小子表冒泡排序时的滑动索引和指向局部最大值的索引
        char* tmp; //临时字符串
        size_t lostk[30]={0}, histk[30]={0}; //保存子表低/高指针的FILO栈[因为小表优先处理,所以30已经足够大了]
        char stkptr=0; //上述栈的深度
        if(NULL==Strs||num<2) return; //无事可做

PseudoRecursion://更新lo,hi后,跳转到此处模拟递归
        preLen=0; src=Strs[lo]; dst=Strs[hi]; while(*src==*dst && *dst) src++,dst++,preLen++; //计算当前子表的公共前缀长度
        if((size=hi-lo+1)<11)
        {//如果子表中乱序元素个数<=8,则时间复杂度为O(n^2)的冒泡算法效果最好[前人的经验,没有理由]
                hi--; lo++;//因为首尾已经分别是最小/大值了
                while(hi>lo)
                {
                        max=lo;//假定索引lo指向局部最大值
                        for(p=lo+1; p<=hi; p++)
                        {//寻找指向数组中局部最大值的真实索引max
                                comp=0; src=Strs[p]+preLen; dst=Strs[max]+preLen; while(!(comp=*src-*dst) && *dst) src++,dst++; //比较两个字符串
                                if(comp>0) max=p;//如果p指向的值更大就更新max
                        }
                        tmp=Strs[max]; Strs[max]=Strs[hi]; Strs[hi]=tmp; //交换,使得hi处为局部最大值
                        hi--;
                }
        }
        else
        {//子表较大(>=11),继续分割
                mid_lo=mid_hi=lo+size/2;//用于划分子表的中间索引[不能用(lo+hi)/2,可能会溢出]
                loguy=lo+1; higuy=hi-1;//低/高端滑动指针[因为首尾已经分别是最小/大值了]
                while(1)
                {//下面采取的策略是分割成3子表而不是传统的2子表,这对于改善有大量相同元素的表的效率有极大的提高!
                        while(loguy<mid_lo)
                        {//将loguy向高端滑动
                                comp=0; src=Strs[loguy]+preLen; dst=Strs[mid_lo]+preLen; while(!(comp=*src-*dst)&&*dst)src++,dst++; //比较两个字符串
                                if(comp<0) loguy++; //低端较小,符合顺序(继续下一个)
                                else if(comp>0) break; //低端较大,需要交换(暂停移动)
                                else { mid_lo--; tmp=Strs[loguy]; Strs[loguy]=Strs[mid_lo]; Strs[mid_lo]=tmp; }//相等,减小mid_lo并交换
                        }
                        while(mid_hi<higuy)
                        {//将higuy向低端滑动
                                comp=0; src=Strs[mid_hi]+preLen; dst=Strs[higuy]+preLen; while(!(comp=*src-*dst)&&*dst)src++,dst++; //比较两个字符串
                                if(comp<0) higuy--; //低端较小,符合顺序(继续下一个)
                                else if(comp>0) break; //低端较大,需要交换(暂停移动)
                                else { mid_hi++; tmp=Strs[higuy]; Strs[higuy]=Strs[mid_hi]; Strs[mid_hi]=tmp; }//相等,增大mid_hi并交换
                        }
                        //根据不同的情况采取不同的措施
                        if(loguy==mid_lo&&mid_hi==higuy) break;
                        tmp=Strs[loguy]; Strs[loguy]=Strs[higuy]; Strs[higuy]=tmp;
                        if(loguy<mid_lo&&mid_hi<higuy){loguy++; higuy--;}
                        else if(loguy==mid_lo&&mid_hi<higuy){mid_lo++; mid_hi++; tmp=Strs[mid_hi]; Strs[mid_hi]=Strs[higuy]; Strs[higuy]=tmp;}
                        else {mid_lo--; mid_hi--; tmp=Strs[loguy]; Strs[loguy]=Strs[mid_lo]; Strs[mid_lo]=tmp;}//if(loguy<mid_lo&&mid_hi==higuy)
                }
                //分割完毕;下面优先处理较小的子表,以避免栈溢出
        if(mid_lo-lo<hi-mid_hi)
                {//低端子表较小
                        if(hi-mid_hi>2){lostk[stkptr]=mid_hi; histk[stkptr]=hi; ++stkptr;} //将高端子表压栈//仅压3元素以上的子表
                        if(mid_lo-lo>2){hi=mid_lo; goto PseudoRecursion;}//仅处理3元素以上的子表
        }
        else
                {//高端子表较小
                        if(mid_lo-lo>2){lostk[stkptr]=lo; histk[stkptr]=mid_lo; ++stkptr;} //将低端子表压栈//仅压3元素以上的子表
                        if(hi-mid_hi>2){lo=mid_hi; goto PseudoRecursion;}//仅处理3元素以上的子表
        }
        }
    if(--stkptr>=0){ lo=lostk[stkptr]; hi=histk[stkptr]; goto PseudoRecursion; }
    else return;
}

[/PHP]
回复 支持 反对

使用道具 举报

发表于 2005-12-1 13:00:53 | 显示全部楼层
我觉得MergeSort更加稳定一些,这个也不错
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表