前面已经讲述了很多排序算法,但是他们的排序算法都是基于两个值之间的比较,通过决策树的方法可以证明深度为d的二叉树则最多有个树叶。具有L片树叶的二叉树的深度至少是logL向上取整。因为N个元素的排列组合为N!。所以至少需要logN!次比较。通过推导只要是比较的算法,至少需要的时间复杂度为O(NlogN)。
一些好的排序算法是可以达到时间复杂度是线性的,桶排序就是其中一种。比如有N个数,但是这些数的最大数不超过M。这个时候就可以定义一个含有M个元素的数组(初始值为0),然后遍历N个数,在M的相应位置进行+1的操作。然后在遍历输出M中不为0的值的位置就可以了。
程序:
public class bucketSort {
//假设数组中的数值是不超过50的。
public void sort(int[] nums) {
int[] temp = new int[50];
for(int i = 0;i<nums.length;i++) {
temp[nums[i]]++;
}
int j = 0;
for(int i = 0;i<temp.length;i++) {
while(temp[i]>0) {
nums[j++] = i;
temp[i]--;
}
}
}
public static void main(String[] args) {
bucketSort sort = new bucketSort();
int[] nums = {5,9,7,6,48,45,32,1,7};
sort.sort(nums);
for(int i = 0;i<nums.length;i++) {
System.out.print(nums[i]+",");
}
}
}
基数排序
基数排序也叫卡片排序,当数组中的数字较大的时候,这个时候我们就不能用桶排序了,那样会浪费太多的存储空间。但是可以利用多次的桶排序,因为每个字符都是用ASCII编码的,范围是0-256。可以利用一个位置一个位置的进行循环排序,比如共三位数,那么第一次就进行个位上数字的排序,排序后得到个位上有序的数组,然后在此基础上进行十位上数字的排序,最后进行百位数字上的排序,则最后得到的结果就是整个数组有序的排列。这样的时间复杂度为O(p(N+b))p是躺数,N是待排序元素的个数,b是桶的个数。下面以字符排序(等长的字符)为例示范:
public void characterSort(String[] arr,int length) {
//因为字符的共256个;
final int BUCKETS = 256;
//桶选择用ArrayList数组
ArrayList<String>[] buckets = new ArrayList[BUCKETS];
//对这些桶进行初始化
for(int i = 0;i<buckets.length;i++) {
buckets[i] = new ArrayList<>();
}
//进行每个字符的遍历
for(int i = length-1;i>=0;i--) {
//每个字符串上的该字符
for(int j = 0;j<arr.length;j++) {
//获取该位置上字符的ASCII的值
buckets[arr[j].charAt(i)].add(arr[j]);
}
int index = 0;
//要对排序好的数组进行重新赋值
for(ArrayList<String> temp:buckets) {
for(String s :temp) {
arr[index++] = s;
}
//要对这个temp进行清空
temp.clear();
}
}
}
计数基数排序
计数基数排序是基数排序的另一种实现方法,该方法避免使用了ArrayList数组。采用了对比某个元素小的值有几个的计数方式进行排序。需要一个临时数组in[],进行排序过程中排序后的数组的记录,还需要个count数组进行数值的记录,比如count[K]的代表的就是严格比K小的元素的个数。然后根据这个个数对数据进行遍历的排序。一般的,计数基数排序要比基数排序的速度要快。代码:
public void countingBucketSort(String[] arr,int length) {
//单位数字的ASCII的是有256个
final int BUCKETS = 256;
//初始化计数数组
int[] count ;
//每次排序后暂存数组的顺序
String[] in = arr;
//进行交换的时候先暂存,因为第一次in和arr是同一个,所以不能直接对arr进行修改、
String[] out = new String[arr.length];
//进行字符的遍历
for(int i = length-1;i>=0;i--) {
//遍历前要对count数组初始化
count = new int[BUCKETS+1];
//进行数组字符串的遍历
for(int j = 0;j<arr.length;j++) {
count[in[j].charAt(i)+1]++;
}
//进行计数的统计
for(int j = 1;j<=BUCKETS;j++) {
count[j] += count[j-1];
}
//统计后就得到了字符串在改位置的顺序,只需要根据count找到自己的位置就行
for(int j = 0;j<arr.length;j++) {
out[count[in[j].charAt(i)]] = in[j];
//要对位置进行+1,因为可能有重复的数值
count[in[j].charAt(i)]++;
}
//要进行数组的变换,
String [] tmp = in;
//in就保持上个排序后的字符串的顺序
in = out;
//out就代表将要进行的
out = tmp;
}
//如果是偶数次比较,那么此时的out的引用就是arr。奇数次要进行替换
if(length% 2 == 1) {
for(int i = 0;i<arr.length;i++) {
arr[i] = in[i];
}
}
}