基数排序算法
基数排序基本概念:
这里讨论的基数排序都是普通的以10为基数的运算,因为这亲更易于讲解。但是,以2为基数实现的基数排序也是非常高效的,这可以利用计算机高速的位运算。这里只考察基数据排序,而不考察与基数排序相似但更复杂一些的基数交换排序。基数这个词的意思是一个数字系统的基。10是十进制系统的基数,2是二进制系统的基数。排序包括分别检测关键字的每一个数字,检测从个位(最低有效位)开始。
1.根据数据项个位上的值,把所有的数据项分为10组。
2.然后对这10组数据项重新排列:把所有关键字是以0结尾的数据项排在最前面,然后是关键字结尾是1的数据项,照些顺序直到以9结尾的数据项。这个步骤被称为第一趟了排序。
3.在第二趟子排序中,再次把所有的数据项分为10组,但是这一次是根据数据项十位上的值来分组的。这次分组不能改变先前的排序顺序。也就是说,第二趟排序之后,从每一组数据项的内部来看,数据项的顺序保持不变:这趟子排序必须是稳定的。
4.然后再把10组数据项重新合并,排在最前面的是十位上为0的数据项,然后是10为1的数据项,如些排序直到十位上为9的数据项。
5.对剩余位重复这个过程。如果某些数据项的位数少于其他数据项,那么认为它们的高位为0.
下面是一个例子,有7个数据项,每个数据项都有三位。为了清晰起见显示第一位的0。
421 240 035 532 305 430 124
(240 430) (421) (532) (124) (035 305)
(305) (421 124) (430 532 035) (240)
(035) (124) (240) (305) (421 430) (532)
035 124 240 305 421 430 532
圆括弧表示组,在同一组中对应位置上的值是相同的。
基数排序理解与实现:
基数排序是一种不基于数与数之间比较的排序。它实现的基本思想是通过取不同的数位来排序:如在本实验中,提取数组中每个元素的个位数,分成十组,相同的放到同一个地方,不同的放在不同的地方,进行一趟排序后,也就是说把所有的数都放好了,重新按顺序读回数组,在第二趟排序中,提取数组中每个元素的十位数(只有个位数的元素,十位数当作零),分成十组,把数从数组下标从小到大读出,放到相对应的位置(分组中不改变上次排序的顺序),然后读回数组……这样操作下去,直到把数组中最大值的位数读出,又重新合并回数组。
实现细节:
基上上述分析,应采用链表来完成,至于用什么链表,得要考虑一番,因为它读出放到不同地方的时候只看位数上的值,并没有进行元素间的比较,插入是可以无序的,但又要考虑到不能改变上次排序的顺序,所以应该是每一次插入进是放到最后,这样的话,就应该考虑用双端链表。
设计方案:
每个链表和它的链结点当作一个分组,每进行一趟排序,就创建十个这样的分组(考虑创建链表数组),把数组中的每个元素都读出,放到相对应的链表中(记得每一次插入都是插到链表的最后),然后从链表数组中依次把每个链表的链结点按顺序读出,放回数组。那么,这样的排序要多少趟才算是合理呢?这时应该以数组中最大值的位数为准。
UML图
数组类
Array |
- arr : long[] - nElems : int |
+ Array () + insert (max : int ) : void + radixSort () void + operation ( kinds : int ) : void + getMaxDigit () : int + display():void + getDigitOFWhich ( value : long , i :int ) : int |
链表类
FirstLastList |
+ first : Link + last : Link |
+ FirstLastList () + insert ( dd : long ) : void |
链结点类
Link |
+ iData : long + next : Link |
+ Link ( id : long ) |
测试类
Test |
+ main ( args : String[] ) :void + operation( kinds:int) :void |
它们之间的关系如下图所示:
一、 实验结果
这是测试基数排序的正确性
效率:
利用Excel表来画绘制图表,分析其效率,初步得出的结论:基数排序的效率为 O(n),但是随着位数个数的增大,效率会倒退。
代码:
/**链连结点 , 存数据的类 */
public class Link {
public long iData;
public Link next;
public Link(long id) {
iData = id;
next = null;
}
public void displayLink() {
System.out.print(" " + iData);
}
}
/**双向链表*/
public class FirstLastList{
public Link first;
public Link last;
public FirstLastList() {
first = null;
last = null;
}
public void insert(long dd) /* 双向链表的插入:插入最后 */
{
Link newLink = new Link(dd);
if (first == null)
first = newLink;
else
last.next = newLink;
last = newLink;
}
public void clear() {
first = null;
last = null;
}
}public class Array {
/** 用于储存数据 */
private long[] arr;
/** 数据个数 */
private int nElems;
public Array(int max) {
arr = new long[max];
nElems = 0;
}
/** 无序插入 */
public void insert(long value) {
arr[nElems++] = value;
}
/** 实现基数排序 */
public void radixSort() {
/* 返回数组中最大值的位数 */
int maxDigit = getMaxDigit();
/* 创建十个双端链表的数组 */
FirstLastList[] linklist = new FirstLastList[10];
/* 对链表进行初始化 */
for (int j = 0; j < 10; j++) {
linklist[j] = new FirstLastList();
}
/* for1 */
for (int i = 1; i <= maxDigit; i++) {
/* for2把数组中的每一个元素都插入到相关链表中,找出数组下标为j的元素,元素从最右算起的第i位数,对应插入到不同的链表中 */
for (int j = 0; j < nElems; j++) {
switch (getDigitOFWhich(arr[j], i)) {
case 0:
linklist[0].insert(arr[j]);
break;
case 1:
linklist[1].insert(arr[j]);
break;
case 2:
linklist[2].insert(arr[j]);
break;
case 3:
linklist[3].insert(arr[j]);
break;
case 4:
linklist[4].insert(arr[j]);
break;
case 5:
linklist[5].insert(arr[j]);
break;
case 6:
linklist[6].insert(arr[j]);
break;
case 7:
linklist[7].insert(arr[j]);
break;
case 8:
linklist[8].insert(arr[j]);
break;
default:
linklist[9].insert(arr[j]);
}/* switch语句结束 */
}/* for2结束 */
/* 把数组元素全部插入链表之后,重新读入到数组中 */
int k = 0;
Link current = linklist[k].first;
/* for3 */
for (int j = 0; j < nElems; j++) {
while (current == null) {
linklist[k].clear();
current = linklist[++k].first;
}
arr[j] = current.iData;
current = current.next;
}/* for3结束 */
linklist[k].clear();
}/* for1结束 */
} /* radixSort方法结束 */
// 用于减轻main()方法的压力而写的
public static void operation(int kinds) {
long startTime = 0, endTime = 0;
int maxSize = 0;
for (int i = 1; i <= 10; i++) {
maxSize = 10000 * i;
Array arr = new Array(maxSize);
for (int j = 0; j < maxSize; j++) {
long n = (long) (java.lang.Math.random() * 100 * Math.pow(10,
kinds));
arr.insert(n);
}
startTime = System.currentTimeMillis();
arr.radixSort();
endTime = System.currentTimeMillis();
long testTime = (endTime - startTime);
System.out.println("amout:\t" + maxSize + "\tdigit:\t"
+ (kinds + 2) + "\ttime:\t" + testTime + "\tss");
}
}
public void display(){
System.out.println();
for(int i = 0 ; i< nElems ; i++){
System.out.print( arr[i] + " ");
}
}
/**
* 此方法是返回数组中最大值的位数
*/
public int getMaxDigit() {
long max = arr[0];
for (int k = 1; k < nElems; k++) {
if (max < arr[k]) {
max = arr[k];
}
}
int i = 1;
while (max / 10 != 0) {
max = max / 10;
i++;
}
return i;
}
/**
* 返回变量value从右边算起第i位数数值
*/
public int getDigitOFWhich(long value, int i) {
value = (long) (value / Math.pow(10,i-1));
return (int) value % 10;
}
}public class Test
{
public static void main(String[] args)
{
/*for(int i = 1;i <= 10;i++) 用来测试不同位数的效率
Array.operation(i);*/
Test.operation(1);
}
/* 测试radixSort正确性*/
public static void operation(int kinds) {
int maxSize = 20;
//for(int i = 0 ; i < 3 ; i ++){
Array arr = new Array(maxSize);
for (int j = 0; j < maxSize; j++) {
long n = (long) (java.lang.Math.random() * 100 * Math.pow(10,
kinds));
arr.insert(n);
}
arr.display();
arr.radixSort();
arr.display();
}
//}
}