下面是三种线性排序算法,都不是比较算法,不涉及元素间的比较操作 项目地址
普通排序
高级排序
时间复杂度 | 空间复杂度 | 稳定性 | |
---|---|---|---|
桶排序 | O(N+K) | O(N+K) | 稳定 |
计数排序 | O(N+K) | O(N+K) | 稳定 |
基数排序 | O(N) | O(N) | 稳定 |
/**
* 获取最大值和最小值
*
* @param data
* @return [最大值][最小值]
*/
private int[] getMaxAndMinData(int[] data) {
int length = data.length;
int[] arr = new int[]{
data[0], data[0]
};
for (int i = 1; i < length; i++) {
if (data[i] > arr[0])
arr[0] = data[i];
if (data[i] < arr[1])
arr[1] = data[i];
}
return arr;
}
第一种实现
private void bucketSort(int[] data) {
// 1
int max = getMaxAndMinData(data)[0];
int min = getMaxAndMinData(data)[1];
int bucketNum = max - min + 1; // 桶数量
// 2
List> buckets = new ArrayList<>(bucketNum);
for (int i = 0; i < bucketNum; i++) {
buckets.add(new ArrayList<>());
}
// 3
for (int i = 0; i < data.length; i++) {
int bucketIndex = data[i] - min;
buckets.get(bucketIndex).add(data[i]);
}
// 4
for (int i = 0, k = 0; i < bucketNum; i++) {
List bucket = buckets.get(i);
for (int j = 0; j < bucket.size(); j++) {
data[k] = bucket.get(j);
k++;
}
}
}
getMaxAndMinData 获取数组的最大值和最小值,算法设计也可以让外面将最大值传入,有时候外面是知道数据中的最大值的或数组中最大值不会超过一个固定值的。
1:确认桶的数量,桶的数量bucketNum为最大值减最小值加一个桶(最大值是1因为有0所以顶加一)。如果数组中最小值比较小可以不减min,第3步也不减即可,但是当最小值比较大时减去min会少创建很多min前面不需要的空置的桶。
2:创建bucketNum个桶,for循环创建bucketNum个空桶,每个桶是一个List。
3:将数组中的数据分别放到对应的桶中
4:依次将桶中的数据放回到原数组中
第二种实现
private void bucketSort(int[] data) {
// 1
int length = data.length;
int max = getMaxAndMinData(data)[0];
int min = getMaxAndMinData(data)[1];
int bucketNum = (max - min) / length + 1;
// 2
List> buckets = new ArrayList<>(bucketNum);
for (int i = 0; i < bucketNum; i++) {
buckets.add(new ArrayList<>());
}
// 3
for (int i = 0; i < data.length; i++) {
int bucketIndex = (data[i] - min) / length;
buckets.get(bucketIndex).add(data[i]);
}
// 4
for (int i = 0; i < buckets.size(); i++) {
Collections.sort(buckets.get(i));
}
// 5
for (int i = 0, k = 0; i < buckets.size(); i++) {
List bucket = buckets.get(i);
for (int j = 0; j < bucket.size(); j++) {
data[k] = bucket.get(j);
k++;
}
}
}
第二种实现跟第一种相比是第一种实现桶中基本就一个数,相同的数才会在一个桶中,第二种实现是按范围划分的,比如第一个桶中放010,第二个桶中放1120,…,第10个桶中放91~100 等,不过第二种实现中每个桶中不同的数需要再自己排序,最后再合并。下面看下实现
1:根据最大值最小值和排序总数量确认桶的数量,(max - min) / length+1个桶,桶中数据范围是 minmin+length-1,min+lengthmin+2*length-1,…,桶的大小间隔是length,最后一个桶中放的是max。如果数组中最小值比较小可以不减min,第3步也不减即可,但是当最小值比较大时减去min会少创建很多min前面不需要的空置的桶。
2:创建bucketNum个桶,for循环创建bucketNum个空桶,每个桶是一个List
3:将数组中的数据分别放到对应的桶中,(data[i]-min)/length计算出data[i]所属范围放入对应桶中。
4:对每个桶中数据排序,这里为了简单使用的是系统的排序,这里最好使用插入排序,因为在每个桶中数据量不会太大,而且数据比较接近有序度比较大,插入排序会非常快。最重要的是插入排序是稳定的排序算法,这里要是使用不稳定的排序会导致桶排序变为不稳定排序。
5:依次将桶中的数据放回到原数组中
private void countSort(int[] data) {
// 1
int length = data.length;
int max = getMaxAndMinData(data)[0];
int min = getMaxAndMinData(data)[1];
int[] buckets = new int[max - min + 1];
// 2
for (int i = 0; i < length; i++) {
buckets[data[i] - min]++;
}
// 3
for (int i = 1; i < buckets.length; i++) {
buckets[i] += buckets[i - 1];
}
int[] tempArr = new int[length];
// 4
for (int i = length - 1; i >= 0; i--) {
int value = data[i] - min;
tempArr[buckets[value] - 1] = data[i];
buckets[value]--;
}
// 5
for (int i = 0; i < length; i++) {
data[i] = tempArr[i];
}
}
计数排序可以看作是桶排序的一种特殊情况,跟桶排序比较像,不过计数排序的桶中存的不是数组中的数,而是存的每个数对应的个数,通过计数巧妙的找到具体数在数组中对应的位置,所以叫计数排序。
1:确定桶大小,看下面第一个表格(原始数据data),最大值为4,最小值为0,所以桶大小为(max-min+1)5;
2:每个桶中放的不是数组中的数,而是数组中数的个数。看下面第二个表格(执行完2的for循环后的buckets),下标为0存放的是2,说明有2个0;没有1;2个2;1个3;1个4;
3:对buckets数组顺序求和,则buckets的含义为:buckets[i]里存的是小于等于i的数的个数。看下面第三个表格(执行完3的for循环后的buckets数据变为表三了),下标为0存放的是2,说明小于等于0的数有2个;小于等于1的数有2个;小于等于2的数有4个;小于等于4的数有5个;小于等于4的数有6个;
4:从后向前(要是从前向后的话相同数据就放反了,就成了不稳定排序了)依次扫描数组data,根据buckets[i]里存的是小于等于i的数的个数,可以确定data中数最终的位置,存放到临时数组tempArr中,buckets中每取出一个数后,根据buckets[i]里存的是小于等于i的数的个数就少一个,所以对应的下标要减1。 具体可以对应着下面表一和表三看一下,从表一中取出最后一个数0,应该放到tempArr中的位置为buckets[0]-1,即将0放到第1个位置(这里可以看出第0个位置空出来放前面的0,保证了稳定性),然后buckets[0]–,buckets数组为[1,2,4,5,6],然后从data中取出倒数第二个数2,buckets[2]-1=3,则2应该放到tempArr[3]中,buckets[2]–,buckets数组为[1,2,3,5,6],然后从data中取出倒数第三个数2,buckets[2]-1=2,则2应该放到tempArr[2]中,buckets[2]–,buckets数组为[1,2,2,5,6],然后从data中取出倒数第四个数4,buckets[4]-1=5,则2应该放到tempArr[5]中,buckets[4]–,buckets数组为[1,2,2,5,5],然后从data中取出倒数第五个数0,buckets[0]-1=0,则2应该放到tempArr[0]中,buckets[0]–,buckets数组为[0,2,2,5,6],然后从data中取出倒数第六个数3,buckets[3]-1=4,则2应该放到tempArr[4]中,buckets[3]–,buckets数组为[1,2,2,4,6],这时data中最后一个数已经取出,循环结束了,此时已经将data中的数据都放入了tempArr中,tempArr为[0,0,2,2,1,4] 此时tempArr数组有序了。
5:最好将临时数组中的数放回到data中。
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
具体数据 | 3 | 0 | 4 | 2 | 2 | 0 |
下标 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
具体数据 | 2 | 0 | 2 | 1 | 1 |
下标 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
具体数据 | 2 | 2 | 4 | 5 | 6 |
private void radixSort(int[] data) {
// 1
int length = data.length;
int max = getMaxCount(data);
// 2
for (int digit = 1; max / digit > 0; digit *= 10) {
// 3
int[] buckets = new int[10];
// 4
int[] tempArr = new int[length];
for (int i = 0; i < length; i++)
buckets[getDigitCount(data[i], digit)]++;
for (int i = 1; i < 10; i++)
buckets[i] += buckets[i - 1];
for (int i = length - 1; i >= 0; i--) {
int value = getDigitCount(data[i], digit);
tempArr[buckets[value] - 1] = data[i];
buckets[value]--;
}
for (int i = 0; i < length; i++)
data[i] = tempArr[i];
}
}
/**
* 获取某个正整数value对应位数digit的值
*
* @param value
* @param digit 位数 1(个位数),10(十位数),100(百位数)...
* @return
*/
private int getDigitCount(int value, int digit) {
return (value / digit) % 10;
}
private int getMaxCount(int[] data) {
return findMax(data, 0, data.length - 1);
}
/**
* 递归获取最大值
*/
private int findMax(int[] data, int left, int right) {
if (left == right) {
return data[left];
} else {
int a = data[left];
int b = findMax(data, left + 1, right);
if (a > b) {
return a;
} else {
return b;
}
}
}
基数排序是当单个数据太大时,且数据的位数相同时,根据位数分别排序。比如一组手机号排序,都是11位的,则使用基数排序是先按照最后一位来排序,排完后再按照倒数第二位重新排,一直到第一位重新排序,经过11次排序后,手机号码就都有序了。11位太长了,下面以三位数看下这个过程,第一个表是原始数据,表中总共4组数据,排列顺序从上到下;然后第一次根据个位数排序,排完后就是第二个表;然后根据十位数再排,排完后是第三个表;最后根据百位数排,排完后就是第四个表;最高位排完后整个数据就有序了。
注:如果排序的数位数不等怎么办,比如台湾的手机号是10位,大陆的是11位,这个也可以用基数排序,可以将位手机号的第一位看作是0就好了。
1:获取数组的最大值
2:for循环根据位数通过计数排序为每个位数排序,直到最高位数排完
3:创建桶的大小为10,因为数字0~9最多10位数
4:下面就是计数排序的过程了,其中getDigitCount方法根据传入的位数digit获取数具体位的值,比如 5个位数的值digit传1,getDigitCount=5;5X十位数的值digit传10,getDigitCount=5;5XX百位数的值digit传100,getDigitCount=5;…