字符串排序算法概述

一 键索引计数法

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

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


1 频率统计

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

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

2 将频率转化为索引

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

index012345
value003 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含有较长公共前缀的字符串

六 参考资料

算法(第四版)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值