您的位置:首页 > 其它

算法原理系列:木桶排序

2017-05-31 23:57 176 查看

 算法原理系列:木桶排序

木桶排序是一种用标记来替代比较操作的排序手段,适用范围较窄,但效率极高,时间复杂度为O(n),在生活中,我们也经常能看到一些木桶排序的实际案例,比如扑克牌排序时,我们把它平摊在空间中,这种记录相对位置的排序方法是最直观的木桶排序。

缘由

先来看看,在计算机视角中,如何利用相对位置进行排序操作。给出数据集:

nums = [9,2,1,4,7,8,6]


这样的数据集有明显的特点,nums在指定范围内,所以我们可以建立一个map来映射nums的值和相对位置关系。

map
index: 0 1 2 3 4 5 6 7 8 9
value: 0 1 1 0 1 0 1 1 1 1

扫描value中非零元素,得到的排序就是最终的排序结果


代码如下:

public static void bucketSortWithNoDuplicate(int[] data){
int n = data.length;
int[] aux = new int
;
int[] map = new int[4 * n]; //开辟足够大的空间
for (int i = 0; i < n; i++){
map[data[i]]++;
}

for (int i = 0, k = 0; i < 4 * n; i++){
if (map[i] == 0) continue;
aux[k++] = i;
}

//回写
for (int i = 0; i < n; i++){
data[i] = aux[i];
}
}


这是最简单的思路,但该排序方法只针对非重复元素的排序,遇到重复元素就不起作用。

如:

nums = 2 5 5 6 8 5 2 6 1 1 6 5

map
index : 0 1 2 3 4 5 6 7 8 9
value : 0 2 2 0 0 4 3 0 1 0

元素1出现的次数为2次
元素2出现的次数为2次
...

做点变形:map[data[i]+1]++;
map
index : 0 1 2 3 4 5 6 7 8 9 10
value : 0 0 2 2 0 0 4 3 0 1 0

累加:
map
index : 0  1  2  3  4  5  6  7  8  9  10
value : 0  0  2  4  4  4  8  11 11 12 0

物理含义:
元素1出现的下标为1
元素2出现的下标为2
...

这样我们就充分利用了map统计的频次,并决定了元素的起始位置在何方。

最终排序时,借助aux数组:
for (int i = 0; i < n; i++){
aux[map[data[i]]++] = data[i];
}

此时,注意map在取映射时并未+1。


这就好比在扑克排序时,预先统计了每张排出现的频次是多少,如2出现3次,3出现2次,则预先给2留三个空位,再给3留5个空位,塞满元素后,也就排序完成。

代码如下:

public static void bucketSortWithDuplicate(int[] data){
int n = data.length;
int[] map = new int[11];
int[] aux = new int
;

//计算出现频率
for (int i = 0; i < n; i++){
map[data[i]+1]++;
}
//预留空位
for (int i = 1; i < map.length; i++){
map[i] = map[i-1] + map[i];
}
//排序
for (int i = 0; i < n; i++){
aux[map[data[i]]++] = data[i];
}
//回写
for (int i = 0; i < n; i++){
data[i] = aux[i];
}
}


低位优先排序

在上述基础上,我们可以实现固定位数的radix排序,如:

nums:
791372584
783336045
313758717
165485572
536175826
619392783
231528160
309593813
830298494
219957039

思路:
从右到左依次扫描每一位,用木桶排序,因为在针对高位排序时,上述排序稳定,所以不会影响相对位置,这样当遍历到最后一位时,就自然整体有序。


代码如下:

public static void radixSort(long[] data){
int n = data.length;
int exp = 1;
int radix = 10;
long max = data[0];

while (max/exp != 0){
int[] map = new int[11];
long[] aux = new long
;

for (int i = 0; i < n; i++){
map[(int)(data[i] / exp % 10) + 1]++;
}

for (int i = 1; i < n; i++){
map[i] += map[i-1];
}

for (int i = 0; i < n; i++){
aux[map[(int)(data[i] / exp % 10)]++] = data[i];
}

for (int i = 0; i < n; i++){
data[i] = aux[i];
}
exp *= radix;
}
}


针对不同位数的数组排序,低位优先只要明确了数组中最大数的最长位数,同样能达到排序效果。

高位优先排序

再介绍一种高位优先排序,如,给定字符数组:

输入:
she
sells
seashells
by
the
seashore
the
shells
she
sells
are
surely
seashells

高位排序,先按第一个字符进行木桶排序,所以有:
0 : are
----------
1 : by
----------
2 : she
3 : sells
4 : seashells
5 : sea
6 : shore
7 : shells
8 : she
9 : sells
10: surely
11: seashells
----------
12: the
13: the

所以经过一轮木桶排序后,我们只要能够得到分组后的头index和尾index,便能递归的进行排序操作了。

递归,无非就是当首字母相同时,如index(2~11)之间的元素,进行第二轮木桶排序。


代码如下:

private static int R = 256;
private static final int M = 0;
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[] data){
int N = data.length;
aux = new String
;
sort(data, 0, N - 1, 0);
}

private static void sort(String[] data, int lo, int hi, int d){

if (hi <= lo + M){
insertionSort(data, lo, hi, d);
return;
}

int[] count = new int[R + 2];

for (int i = lo; i <= hi; i++){
count[charAt(data[i], d) + 2] ++;
}

for (int i = 1; i < count.length; i++){
count[i] += count[i-1];
}

for (int i = lo; i <= hi; i++){
aux[count[charAt(data[i], d) + 1] ++] = data[i];
}

for (int i = lo; i <= hi; i++){
data[i] = aux[i - lo];
}

for (int r = 0; r < R; r++){
sort(data,lo + count[r], lo + count[r + 1] - 1, d+1);
}
}

public static void insertionSort(String[] elements, int lo, int hi, int d){
for (int i = lo + 1; i <= hi; i++){
for (int j = i; j > lo && elements[j].charAt(d) < elements[j-1].charAt(d); j--){
swap(elements, j, j-1);
}
}
}

private static void swap(String[] ele, int i, int j){
String tmp = ele[i];
ele[i] = ele[j];
ele[j] = tmp;
}


有些细节很有趣,比如在进行每一轮的木桶排序时,count频率计数是+2开始的,主要原因在于,count[1]这个位置被用来收集字符串末尾的(count[-1+2]),而在数据分类时,遇到末尾的情况直接把顺序照搬至aux即可。

练习:164. Maximum Gap

顺便来道练习题:

Given an unsorted array, find the maximum difference between the successive elements in its sorted form.

Try to solve it in linear time/space.

Return 0 if the array contains less than 2 elements.

You may assume all elements in the array are non-negative integers and fit in the 32-bit signed integer range.

非负整数,所以我们可以采用基数排序来,对数组进行排序,所需要的时间复杂度为O(n)

代码如下:

public int maximumGap(int[] nums) {
if (nums.length <= 1)
return 0;
radixSort(nums);
int max = 0;
for (int i = 1; i < nums.length; i++) {
max = Math.max(nums[i] - nums[i - 1], max);
}
return max;
}

private static void radixSort(int[] nums) {
int exp = 1;
int radix = 10;

//最大的一定是位数最长的!
int max = 0;
for (int i = 0; i < nums.length; i++) {
max = Math.max(max, nums[i]);
}

while (max / exp != 0) {
int[] count = new int[radix + 1];
int[] aux = new int[nums.length];

for (int i = 0; i < nums.length; i++) {
count[nums[i] / exp % 10 + 1]++;
}

for (int i = 1; i < count.length; i++){
count[i] += count[i-1];
}

for (int i = 0; i < nums.length; i++){
aux[count[nums[i] / exp % 10]++] = nums[i];
}

for (int i = 0; i < nums.length; i++){
nums[i] = aux[i];
}

exp *= 10;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: