数据结构和算法 <字符串>(三、字符串排序 下)

我们按照习惯都是从左到右对比各个字符,然后进行排序,上一节叫的低位优先排序,虽然也能理解,但是需要字符串等长,在实际生活中,字符串一般都不会等长的,所以这次我们来学习一下不等长的,高位优先字符串排序。

3.1 高位优先的字符串排序

3.1.1 对字符串末尾的约定

在高位优先的字符串排序算法中,要特别注意到达字符串末尾的情况。(因为现在的字符串不是等长的了)

我们在之前就使用at的方法来把字符串索引转换成数组索引,当指定的位置超过了字符串的末尾,这个方式返回-1。

接下来只要我们在所有返回值加1,这样就可以得到一个非负的int值并用它作为count[]索引。(是不是感觉有回到上一节了)。这样的结果会产生:0表示字符串结尾,1:表示字母表中的第一个字符,2:表示字母表的第二个字符。所以我们在申请记录索引的数组也需要增大 int *count= new int[R+2]

3.1.2 代码

本来不想上代码的,奈何递归太多,还是按照代码分析分析,理解清楚思维把。

// MSD.h
class MSD
{
    public:
        MSD();
        ~MSD();

        void sort(string *a, int a_len);
        void sort(string *a, int lo, int hi, int d);
        int charAt(string s, int d);

    private:
        static const int R = 256;   // 基数
        static const int M = 0;    // 小数组的切换阈值
        string *aux;                   // 数据分类的辅助数组

    protected:

};
// MSD.cpp
/**
    * @brief  MSD构造函数
    * @param  
    * @retval 
    */ 
MSD::MSD()
{

}    

/**
    * @brief  MSD析构函数
    * @param  
    * @retval 
    */ 
MSD::~MSD()
{

}

int MSD::charAt(string s, int d)
{
    if(d < s.length()) return s.at(d);  else return -1;
}

void MSD::sort(string *a, int a_len)
{
    int N = a_len;
    aux = new string[N];
    sort(a, 0, N-1, 0);
}

// a 0 13 0
void MSD::sort(string *a, int lo, int hi, int d)
{   
    // 以第d个字符为键将a[lo] 至 a[hi]排序
    if (hi <= lo + M)
    {
        // 插入排序是退出的
        return ;
    }

    int *count = new int[R+2];     
    memset(count, 0, (R+2)*4);
    for (int i=lo; i<=hi; i++)      // 计算频率
    {
        count[charAt(a[i], d) + 2]++;
    }

    for (int i=0; i<257; i++)
    {
        if (count[i] != 0)
            printf("%d %d\n", i, count[i]);
    }

    for (int r=0; r<R+1; r++)       // 将频率转换为索引       
    {
        count[r+1] += count[r];
    }

    for (int i=lo; i<=hi; i++)      // 数据分类
    {
        aux[count[charAt(a[i], d)+1]++] = a[i];
    }

    for (int i=lo; i<=hi; i++)      // 回写
    {
        a[i] = aux[i - lo];
    }

    for (int i=0; i<14; i++)
    {
        printf("%d %s\n", i, a[i].c_str());
    }

    // 递归的以每个字符为键进行排序
    for (int r=0; r<R; r++)
    {
        printf("sort = %d %d %d\n", lo+count[r], lo+count[r+1] - 1, d+1);
        sort(a, lo+count[r], lo+count[r+1] - 1, d+1);
    }

    printf("\n\n\n\n");
    delete[] count;    //我也不知道释放完内存了没,太多
}

接下来分析一下代码:刚开始第一轮的话,是排序第0个字母

在这里插入图片描述

然后开始 执行for中递归的以每个字符为键进行排序

a-s已经排好序了,所以就不用递归了,等到s的时候就需要再次排序了

在这里插入图片描述

接着递归

算了,接下来的图就不搞了,意思就这样。不断的递归排序,感觉这个递归很多次了。

3.1.3 小型子数组

之前没有写代码分析之前,就一直不明白这个小型子数组是啥,写完代码并且分析了一遍之后,就都明白了。

我们从上面就递归过程也明白了,3和4明明字符串相同的很多,然后还要一遍一遍的递归处理,这不是折磨人么?所以我们需要在一定的范围后选择进入小数组的插入排序。为了避免重复检查已知相同的字符所带来的成本,我们就以d以后的字符串数组进行插入排序。

和快速排序以及归并排序一样,一个较小的转换阈值就能将性能提高很多。

public static void sort(String[] a, int lo, int hi, int d)
{
	//从第d个字符开始对a[lo]到a[hi]排序
    for(int i=lo; i<=hi; i++)
    {
        for(int j=i; j > lo && less(a[j], a[j-1], j--))
        {
            exch(a, j, j-1);		// 感觉是字符串数组中,交换两个字符串
        }
    }
}

private static boolean less(String v, String w, int d)   // 判断两个字符是否需要交换
{ return v.substring(d).compareTo(w.substring(d)) < 0; }

3.1.4 等值键

高位优先的字符串排序中的第二个陷阱是,对于含有大量等值键的子数组的排序会较慢。

排序算法并不知道这些字符是相同的,所以都需要递归调用。

3.1.5 额外空间

在写代码就发现了,我们在递归里面也申请了数组 ,并且每次递归都申请,所以这个需要额外的空间。

int *count = new int[R+2];  

3.2 三向字符串快速排序

这个三向字符串快速排序就简单了,看着感觉就像是三向快速排序一样,不需要统计什么字符频率,然后转换成下标,然后进行回写,这个三向字符串排序就是需要把字符串数组中的字符串都和一个v比较,然后分为3个数组,然后3个数组又开始递归,依次循环。

public class Quick3string
{
    private static int charAt(String s, int d)
    { if(d < s.length()) return s.chatAt(d); else return -1; }
    
    public static void sort(String[] a)
    { sort(a, 0, a.length -1, 0); }
    
    private static void sort(String[] a, int lo, int hi, int d)
    {
        if(hi <= lo) return ;
        int lt = lo, gt = hi;
        int v = charAt(a[lo], d);
        int i = lo + i;
        while(i <= gt)		// 把所有的字符串,跟v判断,然后进行移动,形成三分天下
        {
            int t = charAt(a[i], d);
            if (t < v) exch(a, lt++, i++);
            else if(t > v) exch(a, i, gt--)
            else i++;
        }
        
        // a[lo .. lt-1] < v = a[lt .. gt] < a[gt+1 .. hi]
        // 下面就是三分天下之后,再次递归,然后再次分,然后。。。。。
        sort(a, lo, lt-1, d);
        if(v >= 0) sort(a, lt, gt, d+1);
        sort(a, gt+1, hi, d);
    }
}

这个就比较简单了,就不描述了,都学到了字符串,应该对快排有所了解了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值