字符串排序算法概述

来源:互联网 发布:cnzz数据统计 编辑:程序博客网 时间:2024/06/11 18:20

一 键索引计数法

首先针对小数组的排序方法,我们将数组中不同的字符串看做一个键r,对应键有个值r,如果需要按键值排序,那么键索引计数法就十分高效

例如,我们将学生分为若干组,要求按照组号进行排序。此处组好就是对应的键值,我们分一下四个步骤进行排序:


1 频率统计

创建一个int数组count,并计算每个键出现的频率。对于每一个字符串,使用对应的键访问count数组并将其加1。如果键为r,则将count[r + 1]加1。上述例子中统计频率后的count数组如下:

index012345value003 566可以看出,count[2]保存着第一小组的总人数,count[3]保存着第二小组的总人数,以此类推。

2 将频率转化为索引

对count数组进行叠加:count[r + 1] += count[r] 便可以将频率转化为索引。上述例子中转化为索引后的count数组如下:

index012345value003 81420

3 数据分类

新建一个辅助数组aux,将所有字符串移动到aux中。aux的索引是字符串对应的键的count值决定的,在移动之后将count[r]加1,保证count[r]总是下一个键为r的元素在aux中的索引位置。

4 回写

将aux数组回写到a中

整个过程码如下:

/** * Created by HP on 2017/5/19. */public class Main {    private static class Data<K, V> {        K key;        V value;        public Data(K key, V value) {            this.key = key;            this.value = value;        }        public K getKey() {            return key;        }        public V getValue() {            return value;        }        @Override        public String toString() {            return String.valueOf(key);        }    }    public static void indexBasedSort(Data<String, Integer>[] a) {        int size = a.length;        int R = 5;        Data<String, Integer>[] aux = new Data[size];        int[] count = new int[R + 1];        // 1 频率统计        for (int i = 0; i < size; i++) {            count[a[i].getValue() + 1]++;        }        // 2 将频率转化为索引        for (int i = 0; i < R; i++) {            count[i + 1] += count[i];        }        // 3 数据分类        for (int i = 0; i < size; i++) {            aux[count[a[i].getValue()]++] = a[i];        }        // 4 回写        for (int i = 0; i < size; i++) {            a[i] = aux[i];        }    }    public static void main(String[] args) {        Data<String, Integer>[] a = new Data[20];        a[0] = new Data<>("anderson", 2);        a[1] = new Data<>("brown", 3);        a[2] = new Data<>("davis", 3);        a[3] = new Data<>("carcia", 4);        a[4] = new Data<>("harris", 1);        a[5] = new Data<>("jackson", 3);        a[6] = new Data<>("johnson", 4);        a[7] = new Data<>("jones", 3);        a[8] = new Data<>("martin", 1);        a[9] = new Data<>("martinez", 2);        a[10] = new Data<>("miller", 2);        a[11] = new Data<>("moore", 1);        a[12] = new Data<>("robinson", 2);        a[13] = new Data<>("smith", 4);        a[14] = new Data<>("taylor", 3);        a[15] = new Data<>("thomas", 4);        a[16] = new Data<>("thompson", 4);        a[17] = new Data<>("white", 2);        a[18] = new Data<>("williams", 3);        a[19] = new Data<>("wilson", 4);        indexBasedSort(a);        for (int i = 0; i < a.length; i++) {            System.out.println(a[i].getKey() + " " + a[i].getValue());        }    }}

二 低位优先的字符串排序

该方法从右向左检查字符串中字符并进行排序,适用于字符串长度都相等的排序应用中。如果字符串长度为W,那么就从右向左将每个字符都看成键,用键索引计数法排序。

/** * Created by HP on 2017/5/19. */public class LSD {    /**     * 低位优先排序     *     * @param a     * @param W     */    public static void sort(String[] a, int W) {        int size = a.length;        int R = 256;        String[] aux = new String[size];        for (int j = W - 1; j >= 0; j--) {            int[] count = new int[R + 1];            for (int i = 0; i < size; i++) {                count[a[i].charAt(j) + 1]++;            }            for (int i = 0; i < R; i++) {                count[i + 1] += count[i];            }            for (int i = 0; i < size; i++) {                aux[count[a[i].charAt(j)]++] = a[i];            }            System.out.println("j = " + j + ", result:");            for (int i = 0; i < size; i++) {                a[i] = aux[i];                System.out.println(a[i]);            }        }    }    public static void main(String[] args) {        String[] a = new String[13];        a[0] = "4PGC938";        a[1] = "2IYE230";        a[2] = "3CIO720";        a[3] = "1ICK750";        a[4] = "1OHV845";        a[5] = "4JZY524";        a[6] = "1ICK750";        a[7] = "3CIO720";        a[8] = "1OHV845";        a[9] = "1OHV845";        a[10] = "2RLA629";        a[11] = "2RLA629";        a[12] = "3ATW723";        sort(a, 7);    }}

三 高位优先的字符串排序

如果字符串长度都不相同,那么应该从左向右遍历字符排序。

/** * Created by HP on 2017/5/19. * * 高位优先字符串排序 */public class MSD {    private static final int R = 256;    private static final int M = 15;    private static String[] aux;    public static int charAt(String a, int index) {        if (index < a.length()) {            return a.charAt(index);        }        return -1;    }    public static void sort(String[] a) {        int length = a.length;        aux = new String[length];        sort(a, 0, length - 1, 0);    }    private static void sort(String[] a, int lo, int hi, int d) {        if (lo > hi) {            return;        }        if (lo + M >= hi) {            insertSort(a, lo, hi);            return;        }        int[] count = new int[R + 2];        for (int i = lo; i <= hi; i++) {            count[charAt(a[i], d) + 2]++;        }        for (int i = 0; i <= R; i++) {            count[i + 1] += count[i];        }        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 < R; i++) {            sort(a, lo + count[i], lo + count[i + 1] - 1, d + 1);        }    }    private static void insertSort(String[] a, int lo, int hi) {        for (int i = lo; i <= hi; i++) {            for (int j = i; j > 0; j--) {                if (a[j].compareTo(a[j - 1]) < 0) {                    exange(a, j, j - 1);                }            }        }    }    private static void exange(String[] a, int i, int j) {        String temp = a[i];        a[i] = a[j];        a[j] = temp;    }    public static void main(String[] args) {        String[] a = new String[13];        a[0] = "she";        a[1] = "sells";        a[2] = "seashells";        a[3] = "by";        a[4] = "the";        a[5] = "seashore";        a[6] = "the";        a[7] = "shells";        a[8] = "she";        a[9] = "sells";        a[10] = "are";        a[11] = "surely";        a[12] = "seashells";        MSD.sort(a);        for (String s: a) {            System.out.println(s);        }    }}

性能分析:

该算法采用递归来实现,性能方面有三个要注意的点

1 小型子数组

假设需要将数百万个字符串(R=256)进行排序而不处理小数组,那么每个字符串最终会产生只含有它自己的子数组,因此你要对数百万个大小为1的子数组进行排序,每次排序都要将大小为R=256 + 2 个元素进行初始化并将其转化为索引,这十分消耗时间。

2 相等的字符串

如果待排序数组中含有大量相等的字符串,那么MSD的性能将会下降,最坏情况就是所有的键都相同,这样会退化到基于低位优先的排序。

3 额外空间

由于MSD采用递归实现,每次递归都要初始化一个count数组,所以count占有的空间才是主要问题。

时间复杂度:

基于大小为R的字母表的N个字符串排序,时间复杂度为N~Nw之间,w是字符串平均长度

空间复杂度:

count数组必须在sort()方法中创建,因此控件需求总量与R和递归深度成正比(再加上辅助数组N)。而递归深度就是最长字符串的长度,故空间复杂度为 RW+N。

四 三向字符串快速排序

三向字符串快速排序可以应对含有较长公共前缀的字符串的情况。例如分析网站日志便会应用此算法。

/** * Created by HP on 2017/5/22. * * 三项切分的字符串排序 */public class Quick3String {    public static void sort(String[] a) {        if (a == null || a.length == 0) {            return;        }        sort(a, 0, a.length - 1, 0);    }    private static void sort(String[] a, int lo, int hi, int d) {        if (lo >= hi) {            return;        }        int lt = lo;        int gt = hi;        int i = lo + 1;        int index = charAt(a[lo], d);        while (i <= gt) {            int t = charAt(a[i], d);            if (index < t) {                exange(a, i, gt--);            } else if (index > t) {                exange(a, i++, lt++);            } else {                i++;            }        }        sort(a, lo, lt, d);        if (index > 0) {            sort(a, lt + 1, gt, d + 1);        }        sort(a, gt + 1, hi, d);    }    public static int charAt(String a, int index) {        if (index < a.length()) {            return a.charAt(index);        }        return -1;    }    private static void exange(String[] a, int i, int j) {        String temp = a[i];        a[i] = a[j];        a[j] = temp;    }    public static void main(String[] args) {        String[] a = new String[13];        a[0] = "she";        a[1] = "sells";        a[2] = "seashells";        a[3] = "by";        a[4] = "the";        a[5] = "seashore";        a[6] = "the";        a[7] = "shells";        a[8] = "she";        a[9] = "sells";        a[10] = "are";        a[11] = "surely";        a[12] = "seashells";        Quick3String.sort(a);        for (String s: a) {            System.out.println(s);        }    }}

五 总结
算法是否稳定原地排序时间复杂度空间复杂度优势领域插入排序是是N~N^21小数组或部分有序数组低位优先排序是否NWN+R较短的定长字符串高位优先排序是否N~NwRW+N随机字符串三向字符串排序否是N~NwlogN含有较长公共前缀的字符串

六 参考资料

算法(第四版)