第五章 字符串(一) - 《算法》读书笔记

第五章 字符串(一)

5.1 字符串排序

  • 低位优先(LSD)的字符串排序:从右到左检查键中的字符
    • 适用于键的长度都相同的字符串
  • 高位优先(MSD)的字符串排序:从左到右检查键中的字符
    • 不一定需要检查所有的输入就能完成排序

5.1.1 键索引计数法

  • 将键值key作为索引,来进行排序

5.1.1.1 频率统计

  • 使用int数组count[]计算每个键出现的频率,如果键为r,则将count[r+1]加1
  • count[0]总是0,如果key从1开始,count[1]也为0
for(int i = 0; i < N; i++)
    count[a[i].key() + 1]++;

5.1.1.2 将频率转换为索引

  • 使用count[]来计算每个键在排序结果中的起始索引位置
  • 任意给定的键的起始索引均为所有较小的键所对应的出现频率之和
for(int r = 0; r < R; r++)
    count[r+1] += count[r];

5.1.1.3 数据分类

  • 在将count[]数组转换为一张索引表之后,将所有元素移动到一个辅助数组aux[]中进行排序
  • 每个元素在aux[]中的位置是由它的键对应的count[]值决定
  • 在移动之后将count[]中对应元素的值加1,以保证count[r]总是下一个键为r的元素在aux[]中的索引位置
for(int i = 0; i < N; i++)
    aux[count[a[i].key()]++] = a[i];
  • 这种实现方式具有稳定性,即键相同的元素排序后会被聚集到一起,但相对顺序没有变化

5.1.1.4 回写

  • 最后一步就是将结果复制回数组中
for(int i = 0; i < N; i++)
    a[i] = aux[i];

键索引计数法排序N个键为0到R-1之间的整数的元素需要访问数组11N+4R+1次。

  • 键索引计数法突破了NlogN的排序算法运行时间下限,因为它不需要比较
  • 只要当R在N的一个常数因子范围之内,它都是一个线性时间级别的排序方法

5.1.2 低位优先的字符串排序

  • 如果字符串的长度均为W,那就从右向左以每个位置的字符作为键,用键索引计数法将字符串排序W遍
public class LSD{
    public static void sort(String[] a, int 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]; //计算出现频率
            for(int i = 0; i < N; i++)
   				count[a[i].charAt(d) + 1]++;
            for(int r = 0; r < R; r++) //将频率转换为索引
    			count[r+1] += count[r];
            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];
        }
    }
}

低位优先的字符串排序算法能够稳定地将定长字符串排序。

  • 如果有两个键,它们中还没有被检查过的字符是完全相同的,那么键的不同之处就仅限于已经被检查过的部分,因为两个键已经被排序过,所以出于稳定性,它们将一直保持有序
  • 如果还没有被检查过的部分是不同的,那么已经被检查过的字符对于两者的最终顺序没有意义,之后的某轮处理会根据更高位字符的不同,修正这对键的顺序

对于基于R个字符的字母表的N个以长为W的字符串为键的元素,低位优先的字符串排序需要访问~7WN+3WR次数组,使用的额外空间与N+R成正比。

5.1.3 高位优先的字符串排序

  • 首先用键索引计数法将所有字符串按照首字母排序,然后再将每个首字母所对应的子数组排序

5.1.3.1 对字符串末尾的约定

  • 将所有字符都已被检查过的字符串所在的子数组,排在所有子数组的前面
  • 具体方法:
    • 使用charAt()方法来讲字符串中的字符索引转化为数组索引,当指定的位置超过了字符串的末位时,返回-1
    • 然后将所有返回值加1,得到一个非负的int值并用它作用count[]的索引
  • 此外,增加一个条件语句以在子数组较小时切换至插入排序
public class MSD{
    private static int R = 256;
    private static final int M = 15; //小数组的切换阈值
    private static String[] aux;
    
    private static int charAt(String s, int d){
        if(d < s.length())
            return s.charAt(d);
        else return -1;
    }
    public static void sort(String[] a){
        int N = a.length;
        aux = new String[N];
        sort(a, 0, N-1, 0);
    }
    private static void sort(String[] a, int lo, int hi, int d){
        if(hi <= lo + M){
            Insertion.sort(a, lo, hi, d);
            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);
    }
}

5.1.3.7 性能

要将基于大小为R的字母表的N个字符串排序,高位优先的字符串排序算法平均需要检查NlogRN个字符;访问数组的次数在8N+3R到~7wN+3wR之间,其中w是字符串的平均长度;最坏情况下所需的空间与R乘以最长的字符串的长度之积成正比。

5.1.4 三向字符串快速排序

  • 根据高位优先的字符串排序,改进快速排序
  • 根据键的首字母进行三向切分,仅在中间子数组中的下一个字符继续递归排序
public class Quick3string{
    private static int charAt(String s, int d){
        if(d < s.length())
            return s.charAt(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 + 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++;
        }
        //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);
    }
}

5.1.4.4 性能

要将含有N个随机字符串的数组排序,三向字符串快速排序平均需要比较字符~2NlnN次。

5.1.5 字符串排序算法的选择

  • 将基于大小为R的字母表的N个字符串排序,平均长度为w,最大长度为W
算法 是否稳定 原地排序 运行时间 额外空间 优势领域
字符串的插入排序 N到N2之间 1 小数组或是已经有序的数组
快速排序 Nlog2N logN 通用排序算法,特别适合用于空间不足的情况
归并排序 Nlog2N N 稳定的通用排序算法
三向快速排序 N到NlogN之间 logN 大量重复键
低位优先的字符串排序 NW N 较短的定长字符串
高位优先的字符串排序 N到Nw之间 N+WR 随机字符串
三向字符串快速排序 N到Nw之间 W+logN 通用排序算法,特别适合用于含有较长公共前缀的字符串
posted @ 2021-02-21 15:57  一天到晚睡觉的鱼  阅读(106)  评论(0)    收藏  举报