字符串排序:键索引计数法

简介: 字符串排序:键索引计数法介绍

描述


关于字符串的排序有很多种方式,像《算法》一书中列举的低位优先、高位优先等,其中最先提到的是键索引计数法,它也是其他排序方式的基础,我们先来了解下。


适用性


关于键索引计数法进行字符串排序,并不是全部都适用,因为它的排序算法核心就是通过统计元素出现频次、构建排序因子的索引边界来递进式完成所有元素排序,它主要适合于以下场景:


  • 用于小整数键的算法
    比如数组arr ={1,2,3,4,2,3,4,2,1,3,4,2,3,4},它里面重复的数字比较多,不重复的只有1,2,3,4,这时就可以用此方法。
  • 用于小整数键的分组排序
    还可以应用在基于小整数键的分组排序,比如有字母{a,b,c,d,e,f,g,h},各字母不重复、但是分组重复,其分组序号格式{字母,分组号}描述为{a,1}、{b,2}、{c,1}、{d,2}、{e,3}、{f,4}、{g,4}、{h,1},最终按照分组号进行排序实现的元素排列为a、c、h、b、d、e、f、g
    网络异常,图片无法展示
    |

    综上,我们可以看到按照分组号可以将元素归纳如下:

分组号

元素

1

a、c、h

2

b、d

3

e

4

f、g


步骤


1、频率统计


网络异常,图片无法展示
|

我们可以看到,每个分组出现频次如下:

分组号

元素

元素出现频次

1

a、c、h

3

2

b、d

2

3

e

1

4

f、g

2


统计频次的目的是为了给构建索引提供条件,我们知道所谓键索引计数就是通过索引 + 频次来完成的。索引可以理解为每个分组的排序边界,每个分组的起始索引即较小一侧边界,而每个分组的结束索引即较大一侧边界,如果索引边界为startIndexendIndex,分组内元素出现频次为t,那么每个分组中的索引边界和分组内元素出现频次的关系是endIndex = startIndex + t,每个分组都按照逻辑序号紧紧相邻,较大的分组起始索引与它相邻的第一个较小分组的结束索引一致。


如果将这个场景延展到一个直线上,也可以将索引边界理解为坐标点,将频次理解为步长,或者是坐标点之间的间距


2、构建索引


网络异常,图片无法展示
|

当已知了 间距 ,下面根据间距从0开始计算和整理 索引边界 ,可以参考上图,方法很简单,是从0开始递增每个分组的频次即可。


3、数据分类


网络异常,图片无法展示
|

当建立好索引后,下面开始进行 数据分类 ,说白了这一步就是将元素排序填充的过程,因为已经知道了所有排序分组的起止边界了,也知道每个分组的容量了,接下来就是挨个映射填充就可以完成排序工作了。


分组元素填充后,起始索引递增
这里面的填充技巧的细节点在于,每填充一个分组内元素后,将对应的该分组的起始索引递增1,代表完成了一个分组内地排序,若后续还有同样分组内的元素填充则理所当然填充到该分组的下一个临近位即可。


4、回写数组


这一步是将已排序好的元素重新回写到原数组中即可。


代码实现


  • 纯小整数版


/**

* @author: <a href=mailto:keycasiter@qq.com>guanjian</a>

* @date: 2021/4/8 13:15

* @description:

*/

public class IndexCountSortDemo {

   

   public static void main(String[] args) {

       int[] nums = {2, 3, 4, 1, 2, 4, 3, 1, 2, 2, 1};

       indexCountIndex(nums);

       //打印结果   1 1 1 2 2 2 2 3 3 4 4

   }


   public static void indexCountIndex(int[] nums) {

       int[] count = new int[6];

       //计算频率

       for (int i = 0; i < nums.length; i++) {

           count[nums[i] + 1]++;

       }

       //将频率转化为索引

       for (int i = 1; i < count.length; i++) {

           count[i] = count[i] + count[i - 1];

       }

       //数据分类

       int[] aux = new int[nums.length];

       for (int i = 0; i < nums.length; i++) {

           aux[count[nums[i]]++] = nums[i];

           //aux[count[nums[i]]] = nums[i];

           //count[nums[i]]++;

       }

       //回写数据(这里是打印)

       for (int i = 0; i < nums.length; i++) {

           System.out.print(aux[i] + " ");

       }

   }

}


  • 小整数分组排序元素


/**

* @author: <a href=mailto:keycasiter@qq.com>guanjian</a>

* @date: 2021/4/8 13:15

* @description:

*/

public class IndexCountSortDemo {

   

   public static class Item<T> {

       private int group;

       private T data;


       public Item(int group, T data) {

           this.group = group;

           this.data = data;

       }


       public int getGroup() {

           return group;

       }


       public void setGroup(int group) {

           this.group = group;

       }


       public T getData() {

           return data;

       }


       public void setData(T data) {

           this.data = data;

       }

   }


   public static void main(String[] args) {

       Item<String>[] arr = new Item[]{

               new Item(1, "a"),

               new Item(2, "b"),

               new Item(1, "c"),

               new Item(2, "d"),

               new Item(3, "e"),

               new Item(4, "f"),

               new Item(4, "g"),

               new Item(1, "h")

       };

       indexCountIndexByItem(arr);

       //打印结果   a c h b d e f g

   }


   public static void indexCountIndexByItem(Item[] arr) {

       int[] count = new int[6];

       //计算频率

       for (int i = 0; i < arr.length; i++) {

           count[arr[i].group + 1]++;

       }

       //将频率转化为索引

       for (int i = 1; i < count.length; i++) {

           count[i] = count[i] + count[i - 1];

       }

       //数据分类

       Item[] aux = new Item[arr.length];

       for (int i = 0; i < arr.length; i++) {

           aux[count[arr[i].group]++] = arr[i];

           // aux[count[arr[i].group]] = arr[i];

           // count[arr[i].group]++;

       }

       //回写数据(这里是打印)

       for (int i = 0; i < arr.length; i++) {

           System.out.print(aux[i].data + " ");

       }

   }

}


总结


键索引计数法是一种对于小整数键排序非常有效却常常被忽略的排序方法。理解它的工作原理是理解字符串排序的第一步,它是一个线性时间级别的排序方法。


参考


字符串排序:键索引计数法
《算法》第4版


相关文章
|
6月前
【每日一题Day370】LC318最大单词长度乘积 | 哈希表 位运算
【每日一题Day370】LC318最大单词长度乘积 | 哈希表 位运算
53 1
|
1月前
字符串排序
【10月更文挑战第1天】字符串排序。
29 2
|
6月前
|
算法 测试技术 C#
【字典树】 【哈希表】 【字符串】100251. 数组中的最短非公共子字符串
【字典树】 【哈希表】 【字符串】100251. 数组中的最短非公共子字符串
|
存储 算法
算法之字符串问题(第415题字符串相加、第43题字符串相乘、第316题去除重复字母)
算法之字符串问题(第415题字符串相加、第43题字符串相乘、第316题去除重复字母)
76 0
【Leetcode -844.比较含退格的字符串 -1047.删除字符串中的所有相邻重复项】
【Leetcode -844.比较含退格的字符串 -1047.删除字符串中的所有相邻重复项】
51 0
逆序一个字符串的每一组单词(不是倒叙)
整体思路: 1.先将整个字符串倒叙:i like china.->.anihc ekil i 2.将倒叙后的每一块单词再倒叙:.anihc->china. 想必大家都发现了,倒叙整个字符串和倒叙每一块是一样的,那么我们不妨写一个倒叙的函数在这里用reserve表示!
75 0
从排列字符串到排列序列:解析增减字符串匹配问题
题目要求根据给定的字符串 s,构造一个排列序列 perm,其中排列序列中的数字满足以下规则: 如果 perm[i] < perm[i + 1],则对应的字符为 'I'; 如果 perm[i] > perm[i + 1],则对应的字符为 'D'。 我们需要根据字符串 s 中的字符,构造满足上述规则的排列序列 perm。
56 0
|
机器学习/深度学习 Python
字符串和数字的去重操作和鞍点的寻找
字符串和数字的去重操作和鞍点的寻找
字符串和数字的去重操作和鞍点的寻找
|
Java API C语言
leetcode【哈希表—简单】242.有效的字母异位词
leetcode【哈希表—简单】242.有效的字母异位词
leetcode【哈希表—简单】242.有效的字母异位词
C/C++编程题之字符串排序
C/C++编程题之字符串排序