每日一省之————字符串排序算法(包括低位优先、高位优先、三向快速排序)

今天的目的是循序渐进的分别复习字符串排序的低位优先算法、高位优先算法和三向快速排序算法。

不过,在开始这三中算法之前,作为基础,我们先从一种字符串的分组算大讲起。

一:字符串的分组算法

/**
 假设存在下列字符串,这些字符串都以一个数字结尾, 结尾的数字代表这个字符串的组号(组号的范围在0-9之内),
 现在我们想要将这些字符串按照其所属的组号归类,并且归类以后组号小的在前面,组号大的在后面。可以使用如下类中的方法计算
  "david-2"
 "lhever-3"
  "kevin-2"
  "chris-0"
   "json-6"
"downald-6"
   "lety-1"
  "bruce-6"
 *
 */


public class Assortment {
    public static String[] assortMent(String[] a)
    {
        int R = 256;
        int N = a.length;
        String[] temp = new String[N];
        int[] count = new int[R + 1];

        //统计属于各个组的字符串出现频数
        for(int i = 0; i < N; i++)
        {
            int group = Integer.valueOf(a[i].charAt(a[i].length() - 1));
            count[group + 1]++; //此处加1的目的是为了便于后续得到各个组的起始索引
        }

        //根据频数计算各个字符串存入临时数组的起始索引
        for(int r = 0; r < R; r++ )
        {
            count[r + 1] += count[r];
        }


        //根据各个字符串所属的组,将字符串移入临时数组
        for(int i = 0; i < N; i++)
        {
            int group = Integer.valueOf(a[i].charAt(a[i].length() - 1));
            temp[count[group]++] = a[i];
        }

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

        return a;       

    }   


    public static void main(String... args)
    {
        String[] a = new String[] {"david-2","lhever-3","kevin-2","chris-0","json-6",
                "downald-6", "lety-1", "bruce-6"};

        System.out.println(Arrays.toString(assortMent(a)));

    }

}

运行该类中的主方法,若将排序后的数组以竖直的方向显示,则结果为:

  chris-0
   lety-1
  david-2
  kevin-2
 lhever-3
   json-6
downald-6
  bruce-6

通过上面的分组算法,我们发现我们已经将字符串数组中的各个字符串元素按照其所属的组或者说按照最后一个字符的大小进行了排序。并且,对于最后一个字符值相同的,分组排序后的相对顺序不变,比如 json-6、downald-6、bruce-6这三个字符串在分组前与分组后的先后顺序保持不变,依然是json-6在数组中的索引最小,bruce-6在数组中的索引最大。事实上,以上分类算法已经为接下来将要讲述的字符串的低位优先排序算法做足了铺垫:倘若我们的字符串中的字符全部来自于ASCII字母表,则字符串中的每个字符都对应于一个范围在0-255的数字。如果我们按照上面算法的逻辑进行排序,将相当于将字符串分为255个组 。

二:字符串的低位优先排序算法
字符串的低位优先排序算法目的就是将一组字符串按照从右到左的顺序依次比较指定索引位置的字符大小并排序。根据上述字符串的分组算法的逻辑,很容易使用下面的代码实现:下面的代码实质上就是将一组字符串按照倒数第一个字符分组(最后一个字符相同的,分组前后相对顺序不变),接着再按照倒数第二个字符分组(倒数第二个字符相同的,分组前后相对顺序不变),直到最终按照倒数第n个字符分组完毕。代码如下:


public static void sortByLSD(String[] a, int W) {
        int N = a.length;
        int R = 256;   // 假设字符串中的字符都来自于ASCII字母表
        String[] temp = new String[N];

        for (int d = W-1; d >= 0; d--) {
            // 对于第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++)
                temp[count[a[i].charAt(d)]++] = a[i];

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

三:字符串的高位优先排序算法
与低位优先排序相反,高位优先排序按照从左到右的顺序比较并排序。代码如下:



    // 返回字符串制定索引位置的字符,如果索引位置值等于字符串长度值,则返回-1
    private static int charAt(String s, int d) {
        assert d >= 0 && d <= s.length();
        if (d == s.length())
            return -1;
        return s.charAt(d);
    }

    private static void sort(String[] a, int lo, int hi, int d, String[] temp) {
        if (hi <= lo) {
            return;
        }
        int R = 256;

        // 计算出现频率
        int[] count = new int[R + 2];// 加2是因为把超出字符串索引找不到的字符也当做一个字符,注意低位优先算法是+1
        for (int i = lo; i <= hi; i++) {
            int c = charAt(a[i], d);
            count[c + 2]++;
        }

        // 计算迁移到临时数组的起始索引
        for (int r = 0; r < R + 1; r++)
            count[r + 1] += count[r];

        // 对字符串进行分类
        for (int i = lo; i <= hi; i++) {
            int c = charAt(a[i], d);
            temp[count[c + 1]++] = a[i];
        }

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

        // 递归的以各个字符进行分类
        for (int r = 0; r < R; r++)
            sort(a, lo + count[r], lo + count[r + 1] - 1, d + 1, temp);
    }

    // 交换a[i] 与 a[j] 两个字符串
    private static void exch(String[] a, int i, int j) {
        String temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    public static void sortByMSD(String[] a) {
        int N = a.length;
        String[] temp = new String[N];
        sort(a, 0, N - 1, 0, temp);
    }

    public static void main(String... args) {
        String[] a = new String[] { "aaaabbbb", "bbbbbbbb", "ccccdddd", "ccccaaaa" };
        sortByMSD(a);
        System.out.println(Arrays.toString(a));

    }

四:字符串的三向快速排序
该算法更加高效,具体实现如下:




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

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


    //对a[lo..hi]范围的字符按照第d个字符排序或分类
    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++;
        }

        /*
         *看下面的代码,知道为什么称为三向排序了吧? 
         */
        sort(a, lo, lt-1, d);  //排序a[lo..lt-1]返回的字符串
        if (v >= 0) sort(a, lt, gt, d+1); //排序a[lt..gt]范围的字符串
        sort(a, gt+1, hi, d); //排序a[gt+1..hi]范围的字符串
    }



    private static void exch(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[] { "aaaabbbb", "bbbbbbbb", "ccccdddd", "ccccaaaa" };
        sort(a);
        System.out.println(Arrays.toString(a));
    }

你可能感兴趣的:(算法)