写程序时,经常会需要对数组进行排序。为了节省上网查找的时间,笔者花费了一些时间,专门对一些排序算法做了分析,并在本文中给出了总结,一方面方便自己日后使用,同时也方便大家用的时候能够在这里就找到合适自己的方法。
一、鸽巢排序
算法步骤:
1) 给定一个待排序数组,创建一个备用数组(鸽巢),并初始化元素为0,备用数组的索引即是待排序数组的值。
2)把待排序数组的值放入“鸽巢”里,即备用数组的索引
3)把鸽巢里的值再次送回到待排序数组
//鸽巢排序
public static void PigeonholeSort(int[]array) //用N表示array.length
{
int n = 256;//这里本应该是待排序数组的最大值+1,作为测试所以假设备用数组的长度为256
int j = 0;
int[] b = new int[n];
for(int i = 0; i < array.length; i++){
b[array[i]]++; //备用数组的索引即是待排序数组的值
}
for(int i = 0; i <b.length; i++){
for(int k = 0; k < b[i]; k++) {
array[j] = i; //把鸽巢里的值再次送回到待排序数组
j++;
}
}
}
鸽巢排序的平均时间复杂度为O(N+n).
鸽巢排序算法的限制条件有两个:
一是数组中存储的必须是int类型或者转换为int类型不丢失真实数据的数据类型;
二是必须事先预测到数组中存储的最大元素,在程序运行过程中求得后因为不是const值,所以无法用int b[Max] = {0};来初始化数组b.
二、计数排序
算法步骤:
1)找出待排序数组a中的最大元素值max和最小元素值min然后用k=max-min+1作为计数数组C的长度;
2)统计待排序数组a中元素值等于i的元素出现的次数,存入数组C的第i项;
3)对所有的计数累加(从C中的下标值为1的元素开始,每一项和前一项相加);
4)反向填充目标数组b,将每个元素i放在新数组b下标值为C(i)的那一项,每放一个元素,就将C(i)减1.
public class SortAlgorithm {
//计数排序
private static int[] CountingSort(int[] a) {
int[] b = new int[a.length];//目标数组
//计算a中最大最小值,以确定计数数组C的长度
int max = a[0];
int min = a[0];
for(int i=1;i<a.length;i++) { // n=a.length
if(a[i]>max) {
max = a[i];
} else if(a[i]<min){
min = a[i];
}
}
int k = max-min+1;//计数数组C的长度
int[] C = new int[k];
//统计数组a中每个值为i的元素出现的次数,存入数组C的下标为i-min的项
for(int i=0;i<a.length;i++) {
C[a[i]-min]++; //存储相对位置
}
//对所有的计数累加
for(int i=1;i<k;i++) {
C[i]=C[i-1]+C[i];
}
//反向填充目标数组b
for(int i = a.length-1;i>=0;i--) {
int q = a[i]-min;
b[C[q]-1] = a[i];
C[q] = C[q]-1;
}
return b;
}
public static void main(String[] args){
int[]a={49,38,65,97,76,13,27,49};
int[] b = CountingSort(a);
for(int i=0;i<b.length;i++){
System.out.print(b[i]+" ");
}
}
}
计数排序算法的时间复杂度为O(n+k),适用于待排序数组的每个元素值都大于等于0。
3、快速排序算法
基本思想:
1)首先选择一个基准数X
2)通过一趟将要排序的数据分割成独立的两部分,左边部分的所有数据要小于或者等于基准数,右边部分的所有数据要大于基准数。
3)然后按照此方法对两部分分别进行快速排序,整个过程可以递归进行,以此达到所有数据编程有序。
public class SortAlgorithm {
//快速排序
public static void QuickSort(int s[], int L, int R){
if (L < R){
int i = L,j = R,x =s[L];
while (i < j){
while(i < j && s[j] >= x)//从右向左找第一个小于x的数
j--;
if(i < j)
s[i++] =s[j];
while(i < j && s[i] <x)//从左向右找第一个大于等于x的数
i++;
if(i <j)
s[j--] = s[i];
}
s[i] =x;
// 递归调用
QuickSort(s, L,i - 1); //左半部分
QuickSort(s,i + 1, R);//右半部分
}
}
public static void main(String[] args){
int[] a={49,38,65,97,76,13,27,49};
QuickSort(a,0,a.length-1);
for(int i=0;i<a.length;i++){
System.out.print(a[i]+" ");
}
}
}
快速排序算法在最坏的情况下退化为冒泡排序,时间复杂度为O(n^2),实际应用中的平均时间复杂度为O(n*logn)。
4、冒泡排序
冒泡排序的过程很简单:首先将第一个记录的关键字和第二个记录的关键字进行比较,若为逆序,则将两个记录交换位置;然后比较第二个记录的关键字和第三个记录的关键字;以此类推,直至第N-1个记录和第N个记录的关键字进行过比较为止。上述过程为第一趟排序,其结果使得关键字最大的记录被安置在最后一个记录的位置上;然后进行第二趟排序,对前N-1个记录进行同的操作,以此类推。
void BubbleSort(int[] a,int n){//n=a.length
int temp;
for(int i=0;i<n-1;i++){
for(int j=n-1;j>i;j--){
if(a[i]>a[j]){
temp=a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}
时间复杂度:若初始序列为“正序”序列,则只需要进行一趟排序,需要进行N-1次关键字间的比较,且不移动记录;若为“逆序”序列,则需要进行N-1趟排序,需进行N(N-1)/2次比较,并做等数量级的记录移动。因此总的时间复杂度是:O(N^2)。
以上方法已经验证,如有不足之处,欢迎大家指正!!