字符串 - 字符串排序

1. 字符串排序

对于许多排序应用来说,决定顺序的键都是字符串。给定一列字符串,需要按一定顺序排列整齐方便后序处理。

2.  键索引计数法

这个方法名字有点拗口,过程有点绕,但是每一步其实很简单。

举个简单的例子:

 

 

 

过程看着有点复杂,但是代码真的非常简单,一步一行就可以。

    public static void string_count(String[] a, int R)
    {
        int N = a.length;
        int[] Count = new int[R+1];
        String[] aux = new String[N];

        for(int i=0;i<N;i++)
            Count[a[i].key()+1]++; //计数
        for(int i=0;i<R;i++)
            Count[i+1] += Count[i]; //Count数组向前累加
        for(int i=0;i<N;i++)
            aux[Count[a[i].key()]++] = a[i];  //将元素分类
        
        for(int i=0;i<N;i++)
            a[i] = aux[i];  //回写
    }

这里a[i].key()表示字符串a[i]的键,我们按照键对数组a进行排序,也就是说键小的在前,键大的在后。

 注意这里一定要理解清楚,键索引计数法是后面两种算法的基础。

而且键索引计数法是稳定的,也就是说,同一个键的话,字符串的相对顺序不会改变。

 

3. 低位优先的字符串排序

位优先的字符串排序方法适合数组中所有字符串的长度相等的情况。

例如 String a[]= {"ao01", "adf5", "erh4","6te5"},字符串数组a中所有的字符串都是4位。

这种情况就可以用低位优先的字符串排序,具体来说就是从最末尾一位开始排序,排完了之后,再按照倒数第二位开始排序,直到第一个字符。一共要经历四轮排序,最后得到最终顺序。

我一开始看的时候也是好奇为什么要从最后一位开始比,然后再依次比到第一位。包括自己也拿程序验证了一下,如果是从第一位先排,再排到最后一位结果确实是不对的。

但是仔细想了一下,这个过程其实就类似于数字的比大小。以四位数为例,先从千位开始比,从小到大排好,再按百位排,就相当于打乱了这一顺序,得出来的排序没有任何意义,因为千位的排序本身是要比百位更重要的。相反,假如从个位开始排,那么个位数大的排在后面,然后再排十位,打乱的话,这个过程是有意义的。因为十位本身就决定了排序,而假如十位相同的话,那么第一轮决定了个位数大的排在后面。这个时候就真正实现了数字的排序。同理也可以应用于字符串的排序。

这个算法的核心在于排序次数线性正比于字符串长度。假如字符串长度均为n,那么就只需要操作n次。并且原地排序不需要占用任何其他内存。这样看这个算法其实是简洁且快速的。

public class LSD
{
    public static void sort(String[] a, int W) //W为字符串的长度
    {
        int N = a.length;
        int R = 256; //字典大小;

        String[] aux = new String[N];

        for(int d=W-1;d>=0;d--)
        {
            int[] Count = new int[R+1]; //注意这一行是在循环里面,因为每一轮循环都有自己独立的count数组
            
            for(int i=0;i<N;i++)
                Count[a[i].charAt(d)+1]++;  //计算次数

            for(int i=0;i<R;i++)
                Count[i+1] += Count[i]; //Count向前累加

            for(int i=0;i<N;i++)
                aux[Count[a[i].charAt(d)]++] = a[i];

            for(int i=0;i<N;i++)
                a[i] = aux[i]; // 回写
        }
    }

    public static void main(String[] args) 
    {
        String[] a = {"4ADC","4BPL","0HPD","0BME"};
        int W = 4;
        sort(a, 4);
        for(int i=0;i<4;i++)
        {
            System.out.println(a[i]);
        }

    }
}

输出结果为 

 

4. 高位优先字符串

高位优先字符串则更加通用一些,可以处理一个字符串数组中,字符长度不定的情况。

具体方法为,先用键索引计数法将所有字符串按照首字母排序,然后递归的将每个首字母所对应的子数组排序。如果遇到["A", "AB"],具有相同前缀,但有些字符串已经结束,另一些没结束,则把已经结束了的放在子字符串数组的前面。

public class MSD {
    private static int R = 256;
    private static String[] aux;


    private static int charAt(String s, int d)
    {
        if(d>=s.length()) return -1;
        else return s.charAt(d);
    }

    public static void sort(String[] a)
    {
        int N = a.length;
        aux = new String[N];
        sort(a, 0, N-1, 0);
    }
    
    //a为带排序数组,lo开始考虑的位置,hi考虑的最后一个字符串的位置,考虑的第d位字符
    public static void sort(String[] a, int lo, int hi, int d)
    {
        if(lo>=hi)
            return;
        int[] Count = new int[R+2];
        for(int i=lo;i<=hi;i++)           //计算频率
            Count[charAt(a[i], d) + 2]++;
        
        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 r=0;r<R;r++)  //递归以每个字符为键进行排序
            sort(a, lo + Count[r], lo + Count[r+1]-1, d+1);
    }

    public static void main(String[] args)
    {
        String[] a = {"4ADC","4BPL","0HPD","0BME"};
        sort(a);
        for(int i=0;i<4;i++)
        {
            System.out.println(a[i]);
        }

    }
}

 

用同样的测试数据,得到了和低位优先排序一样的结果。

 

5. 三向字符串快速排序

三向指的是每次排序都只把数组切分位三部分。这样可以提高每次排序的效率,避免每次都要生成一堆空子数组。

三向字符串快速排序特别适合有特别长的公共前缀的字符串

 

public class Quick3string {
    private static int charAt(String s, int d) {
        if (d >= s.length()) return -1;
        else return s.charAt(d);
    }

    public static void sort(String[] a) {
        sort(a, 0, a.length - 1, 0);
    }

    public static void sort(String[] a, int lo, int hi, int d) {
        if (lo >= hi) return;
        int lt = lo, gt = hi;
        int v = charAt(a[lo], d);
        int i = lo + 1;
        while (i <= gt) {
            int t = charAt(a[i], d);
            if (t < v) exch(a, lt++, i++);
            else if (t > v) exch(a, i, gt--);
            else i++;
        }
        sort(a, lo, lt - 1, d); //先把小于首字母的部分按首字母再继续递归排序
        if (v >= 0) sort(a, lt, gt, d + 1); //把等于首字母的,且还没有遍历完成的就排后一个字母
        sort(a, gt + 1, hi, d); //大于首字母的也是继续按首字母再继续递归排序
    }

}

 

转载于:https://www.cnblogs.com/corineru/p/10799686.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值