计数基数排序实验报告.docx
《计数基数排序实验报告.docx》由会员分享,可在线阅读,更多相关《计数基数排序实验报告.docx(15页珍藏版)》请在冰豆网上搜索。
计数基数排序实验报告
实验二计数基数排序
1.实验环境
本次算法实验选用java语言实现,环境:
JDK11.8;编译器:
eclipse
2.实验目的
通过计数排序和基数排序问题,更进一步了解排序思想和程序设计的思想和技巧。
3.计数排序
3.1设计思路
对于特定元素x来说,如果我们知道给定序列里比它小的元素的个数,则x的位置就已经确定。
如:
若序列比x小的元素有m个,则x在输出数组里面所占的位置就是m+1。
这就是计数排序:
对于每一个元素,我们计数比它小的元素个数,然后将该元素放入合适的位置。
给定一个输入数组:
A[1..n],这里A[j]∈{1,2,3,…,k},即每个元素的取值不超过k。
给定一个输出数组:
B[1..n],用于存放排好序的序列。
给定一个计数数组:
C[1..k],用于计数。
伪代码如下:
1.Fori<--1tokDo:
//将计数数组清零
C[i]<--0
2.Forj<--1tonDo:
//计数取值为A[j]的元素个数
C[A[j]]<--C[A[j]]+1
3.Fori<--2tokDo:
//计数取值为小于等于i的元素的个数
C[i]<--C[i]+C[i-1]]
4.Forj<--ndownto1Do:
B[C[A[j]]]<--A[j]//元素A[j]在输出数组里所处的位置为B[C[A[j]]]
C[A[J]]<--C[A[j]]–1//剩下的比取值小于等于A[j]的元素个数减1
3.2过程
例如我们需要对下面这个数组里的元素进行计数排序
A
6
1
4
5
8
3
4
第一行循环,计数数组C初始化为0:
(8个元素)
C
0
0
0
0
0
0
0
0
第二行循环后,计数C的元素变为:
C
1
0
1
2
1
1
0
1
第三行循环后,计数C变成:
C
1
1
2
4
5
6
6
7
第四行循环将每个元素放入它们相应的位置:
A
6
1
4
5
8
3
4
B
4
C
1
1
2
3
5
6
6
7
j=7:
A
6
1
4
5
8
3
4
B
3
4
C
1
1
1
3
5
6
6
7
j=6:
A
6
1
4
5
8
3
4
B
3
4
8
C
1
1
1
3
5
6
6
6
j=5:
A
6
1
4
5
8
3
4
B
3
4
5
8
C
1
1
1
3
4
6
6
6
j=4:
A
6
1
4
5
8
3
4
B
3
4
4
5
8
C
1
1
1
2
4
6
6
6
j=3:
A
6
1
4
5
8
3
4
B
1
3
4
4
5
8
C
0
1
1
2
4
6
6
6
j=2:
A
6
1
4
5
8
3
4
B
1
3
4
4
5
6
8
C
1
1
1
2
4
5
6
6
j=1:
至此,整个排序完成,B数组里面就是已经排好的序列:
1,3,4,4,5,6,8
3.3.实现源代码
packagecountsort;
publicclassCountSort{
privatestaticint[]countSort(int[]A,intk)
{
int[]C=newint[k+1];//构造C数组
intlength=A.length;//获取A数组大小用于构造B数组
int[]B=newint[length];//构造B数组
//第一个循环
for(inti=1;i<=k;i++){
C[i]=0;//将C初始化为0
}
//第二个循环
for(intj=0;j{
C[A[j]]+=1;//统计A中各元素个数,存入C数组
}
//第三个循环
for(inti=1;i{
C[i]=C[i]+C[i-1];
}
//第四个循环
for(inti=length-1;i>=0;i--)//遍历A数组,构造B数组
{
B[C[A[i]]-1]=A[i];//将A中该元素放到排序后数组B中指定的位置
C[A[i]]--;//将C中该元素-1,方便存放下一个同样大小的元素
}
returnB;//将排序好的数组返回,完成排序
}
publicstaticintgetMax(int[]arr){
intmax=arr[0];
for(inti=1;iif(arr[i]>max){
max=arr[i];
}
}
returnmax;
}
publicstaticvoidmain(String[]args)
{
int[]A=newint[]{6,1,4,5,8,4,3};
intk=getMax(A);
int[]B=countSort(A,k);
for(inti=0;i{
System.out.print(B[i]+"");
}
}
}
3.4断点调试及代码分析
在程序入口main方法里首先定义了需要排序的数组,然后编写了一个程序计算数组中的最大值,用于构造计数数组C:
然后调用计数排序的方法
参数的定义:
这里C的长度定义为k+1是为了方便从1开始计数
第一个循环,将C数组索引为1到k的值初始化为0:
第二个循环:
第三个循环:
第四个循环,因为数组为是从索引0开始的,为C是从索引1开始的,因此这里有一个减1操作:
然后返回数组B:
并在main方法中输出B数组:
3.5效率分析
计数排序一共只有4个循环,第1个循环需要执行k次,第2个循环一共执行n次,第3个循环执行k次,第4个循环执行n次。
因此,计数排序的时间复杂性为Θ(n+k),如果k=Ο(n),则计数排序的时间复杂性为Θ(n)。
但是此时的前提为:
k的数量级不高于n的数量级。
在计数排序算法里,计数数组C的大小为输入序列里最大元素的取值。
输入元素的取值范围越大,则计数数组C的空间需求也越大。
如果输入数组的元素取值很少,则数组C的大部分空间都会浪费。
如果k>>n,则计数排序就不再是线性的了,如k=Ο(n2),则Θ(n+k)=Θ(n2)
3.6实验结果
当数组源定义为:
时,得到的结果为
更换数据测试:
实验完成
4.基数排序
4.1设计思路
当输入序列里的元素取值很大的时候,计数排序算法在效率和可行性上都将崩溃,而基数排序却可以应付数值很大的情况。
该中算法在按照一个位数一个位数的进行排序,而对每个位数的排序则使用了某种稳定的排序。
因为越是后排的数位,对结果次序的影响越大,所以要先排最低有效位后排最高有效位。
4.2过程
下面是一个比较复杂的排序例子,第一次按个位数排序,第二次按十位数排序,第三次按百位数排序,第四次按千位数排序:
2323
3420
3420
8139
1338
5456
2323
2323
2323
2323
6657
4934
4934
1338
3420
8139
5495
1338
3420
4934
5495
5456
8139
5456
5456
1338
6657
5456
5495
5495
3420
1338
6657
6657
6657
4934
8139
5495
4934
8139
其中对每个位数的排序使用前面的计数排序算法。
4.3.实现源代码
packageradixsort;
publicclassRadixSort{
publicstaticint[]radixSort(int[]A,intdigit)
{
//低到高位
for(intk=1;k<=digit;k++)
{
int[]B=newint[A.length];//保存当前位的排序结果
int[]C=newint[10];//计数数组C
//计数排序算法的第一个循环
for(inti=0;iC[i]=0;//将还数组清零
}
//计数排序算法的第二个循环
for(inti=0;i{
//分离当前位
inttmpSplitDigit=A[i]/(int)Math.pow(10,k-1)-(A[i]/(int)Math.pow(10,k))*10;
//此时c保存的是当前位上的值的个数
C[tmpSplitDigit]+=1;
}
//计数排序算法的第三个循环
for(intm=1;m<10;m++)
{
C[m]+=C[m-1];
}
//计数排序算发的第四个循环
for(intn=A.length-1;n>=0;n--)
{
inttmpSplitDigit=A[n]/(int)Math.pow(10,k-1)-(A[n]/(int)Math.pow(10,k))*10;
B[C[tmpSplitDigit]-1]=A[n];
C[tmpSplitDigit]-=1;
}
//将当前排序值拷贝给A数组,进行下一位排序
for(intp=0;p{
A[p]=B[p];
}
}
//返回最后结果
returnA;
}
publicstaticvoidmain(String[]args){
int[]ary=newint[]{332,653,632,755,433,722,48};
int[]res=radixSort(ary,3);
for(intvr=0;vrSystem.out.println(res[vr]);
}
}
}
4.4断点调试及代码分析
在main方法中定义了需要排序的数组源
然后调用radixSort()方法,
参数A表示数组源,digit表示该数组最高有几位
第一层循环迭代位数
然后里面的前四个循环就是对当前位的数字进行计数排序算法,其中第二个循环里的:
tmpSplitDigit参数就是分离出来的当前位上的数字,将它写入计数数组C
第四个循环里的tmpSplitDigit在这里就相当于前面计数排序算法里的A数组
循环完一次后,就完成了对个位数的排序,然后再依次十位…直至到最高位排序完毕,算大就完成了实现。
4.5效率分析
基数排序的总时间成本是Θ(bn/lgn),此处b为二进制位数,n为数的个数。
4.6实验结果
定义数组源为
显而易见该数组的最高位为3位,所以调用radixSort():
输出结果:
得到:
更改数据源和radixSort()的digit参数测试,得到
也可以编写代码计算digit参数:
getDigit()方法:
privatestaticintgetDigit(int[]ary){
intvalue=getMax(ary);
intcount=0;
while(value/10>0){
count++;
value=value/10;
}
count++;//首位也要加进去
returncount;
}
方法首先调用getMax()方法得到该数组的最大值,再计算这个最大值的位数即为该数组的最大位数。
getMax()方法:
publicstaticintgetMax(int[]arr){
intmax=arr[0];
for(inti=1;iif(arr[i]>max){
max=arr[i];
}
}
returnmax;
}
5.实验总结
计数排序算法的实现相较于基数排序算法简单一些,主要是思想上的理解,实现起来并不算太难;基数排序算法的难点在于分离位数,将分离出来的位数的个数作为计数数组的取值在对它进行计数排序算法。
基数排序算法的核心在于从低位向高位进行排序,因为如果要从高位排序,那么次高位的排序会影响高位已经排好的大小关系.在数学中,数位越高,数位值对数的大小的影响就越大.从低位开始排序,就是对这种影响的排序.数位按照影响力从低到高的顺序排序,数位影响力相同则比较数位值。
在对每一位数进行稳定排序的时候,选择时候计数排序算法,计数排序算法适用于数组源中的元素值小,在这里,每一位数的取值都为[0..9],因此选择该算法比较客观。
稳定排序的意思是指,待排序相同元素之间的相对前后关系,在各次排序中不会改变.比如实例中具有十位数字5的两个数字58和356,在十位排序之前356在58之前,在十位排序之后,356依然在58之前。
稳定排序能保证,上一次的排序成果被保留,十位数的排序过程能保留个位数的排序成果,百位数的排序过程能保留十位数的排序成果。
通过这一次的实验,不仅仅是对这两种排序算法的学习实现,更加深了我对算法思想的理解,拓宽了编程的思维,对以后的编程学习有了更大的帮助。