1 基数排序
基数排序对于整数特别有效。是一种稳定的算法(意思是相同的数字不会交换关系)。基数排序是根据数字的性质来逐步根据个位数,十位数,百位数分类求得排序结果的方法之一。它的想法如下:
(1)先将数字根据A[n]依个位数来分类,放入含有数字0,1,2,...,9的临时数组D[10][n]中,再按照数字大小顺序放回原数组。那么这时候数据已经按照个位数大小从小到大排序。
(2)同样将数字按照百位数,千位数,万位数排序,.....最后就可以得到排好序的数字。
假设有下面4个数字需要排序:123 42 765 64
第一次按照个位数的大小排序后:42 123 64 765
第二次按照十位数的大小排序后:123 42 64 765
的三次按照百位数的大小排序后:42 64 123 765,其中42,64都小于100,因此其百位数可以看成0。
从上面的算法描述可以看出,我们首先需要知道一个数据系列中的最大数据。接下来,我们还要知道它有多少位,最后我们必须知道每一位到底是什么。比如例子中最大的数据是765,我们需要765是一个3位数,而且需要知道它的个位是5,十位是6,百位是7。
取得一个数据系列中最大值是很容易的。可以用下面的函数:
/// <summary>
/// /找到一个数据序列中最大的数
/// </summary>
/// <param name="myArray">输入的整数序列</param>
/// <returns>数据序列中最大的整数</returns>
private static int FindMax(int [] myArray)
{
int max = myArray[0];
for(int i=1; i<myArray.Length; i++)
{
if( max < myArray[i] )
max = myArray[i];
}
return max;
}
我们要知道一个数据的位数也很容易。可以用下面的函数取得一个整数有多少位。
/// <summary>
/// 取得一个整数的位数。比如123, 返回3。
/// 利用整数除法的性质,不停地用10除以整数。
/// 比如:123/10 = 12,12/10 = 1,1/10 = 0。
/// 一共除了3次,那么就是一个3位数。
/// </summary>
/// <param name="number">输入的整数</param>
/// <returns>数字的位数,比如123 返回 3</returns>
private static int DigitNumber(int number)
{
int digit = 0;
do
{
number /= 10;
digit++;
} while( number != 0 );
return digit;
}
如果我们要知道一个整数的每一位,也是很简单的,比如我们需要知道765的每一位。首先765%10=5,就是个位数,然后765/10=76,76%10=6,就是十位数,765/100=7,7%10=7,就是百位数,因此可以根据简单的公式得到每一位。m=n/(10^i),m%10就是第i位数字。算法如下:
/// <summary>
/// 得到一个整数的某一位数,0表示个位数,1表示十位数
/// 2表示百位数,等等。
/// 利用的算法是求余。比如12/1 = 12,12%10 = 2,2就是个位数
/// 12/10 = 1,1%10 = 1,1就是十位数,那么可以得到一个简单的公司
/// 令n=n/(10^kth),那么n%10就是所要求的位数
/// </summary>
/// <param name="number">输入的整数</param>
/// <param name="kth">某位数字,0表示个位,1十位,2百位,依此类推</param>
/// <returns>返回第k位数字</returns>
private static int KthDigit(int number, int Kth)
{
number /= (int)Math.Pow(10, Kth);
return number % 10;
}
从上面的算法思想可以得到算法的实现如下:
/// <summary>
/// 基数排序的中心思想就是将数字按照个位,十位,百位.分别排好序
/// 那么整个数组也就有序了。
/// 比如我们先将数组安装个位数字进行排序,然后再将这个数组安装十位排序
/// 依次下去,我们就可以将整个数组进行排序
/// </summary>
/// <param name="myArray">输入的未排序数列</param>
public static void RadixSort(int [] myArray)
{
int [] count = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int[,] temp = new int[10, myArray.Length];
int max = FindMax( myArray ); //取得序列中的最大整数
int maxDigit = DigitNumber( max ); //得到最大整数的位数
int i, j, k;
for(i=0; i<maxDigit; i++)
{
for(j=0; j<10; j++)
count[j] = 0;
for(j=0; j<myArray.Length; j++)
{
int xx = KthDigit( myArray[j], i ); //将数据安装位数放入到暂存数组中
temp[xx,count[xx]] = myArray[j];
count[xx] = count[xx] + 1;
}
int index = 0;
for(j=0; j<10; j++) //将数据从暂存数组中取回,放入原始数组中
{
for(k=0; k<count[j]; k++)
{
myArray[index] = temp[j,k];
index++;
}
}
}
}
基数排序法的时间为O(n logRB)。其中B是数字个数(0-9),R是基数位数(最大值数据的位数),相同数据在基数排序法的过程中的位置不会变动,是一种稳定的排序的算法。但是如果数据量很大的话,这种算法也需要很多额外的存储空间。不过从总的看来,这种算法对于整数排序还是很好的。
2 Shell排序(希尔排序)
Shell排序属于交换方法的排序算法。从前面介绍的冒泡排序法,交换排序法,选择排序法,插入排序法4者可以发现,如果数据已经大致排好序的时候,其交换数据位置的动作将会减少。例如在插入排序法过程中,如果某一整数d[i]不是较小时,则其往前比较和交换的次数会更少。
如何用简单的方式让某些数据有一定的大小次序呢?Donald Shell(Shell排序的创始人)提出了先将数据按照固定的间隔分组,例如每隔4个分成一组,然后排序各分组的数据,形成以分组来看数据已经排序,从全部数据来看,较小值已经在前面,较大值已经在后面。将初步处理了的分组再用插入排序来排序,那么数据交换和移动的次数会减少。可以得到比插入排序法更高的效率。
假设有12个数据:71 101 81 111 51 11 91 21 121 61 31 43
分成4组
第一组 71 51 121
第二组 101 11 61
第三组 81 91 31
第四组 111 21 41
当我们把各个组的数据都排好序以后,再对总的数据进行排序,那么就可以看到数据的移动次数比起普通的插入排序会少了很多。
如何进行分组呢?按照Shell的分法,h(0)=[n/2],h(i+1)=h(i)/2。按照上面的思路,可以得到排序算法如下。
/// <summary>
/// Shell排序的中心思想是将数据进行分组,然后对每一组数据进行排序,
/// 在每一组数据都有序之后,就可以对所有的分组利用插入排序进行最后一次排序
/// 这样可以显著减少数据交换的次数,以达到加快排序速度的目的。
/// </summary>
/// <param name="myArray">输入的待排序序列</param>
public static void ShellSort(int [] myArray)
{
int i, j, increment;
int temp;
for( increment = myArray.Length / 2; increment > 0; increment /= 2 )
{
for( i = increment; i < myArray.Length; i++ )
{
temp = myArray[i];
for( j = i; j >= increment; j -= increment )
{
if( temp < myArray[j-increment] )
myArray[j] = myArray[j-increment];
else
break;
}
myArray[j] = temp;
}
}
}
在Shell之后,D.E.Knuth提出了一种新的分组方法:h(m+1)=3h(m)+1,h(1)=1,已经证明这种分组方法对数据量很大的时候很合适,而且可以避免一些原始分组方法的弊端,因此这种分组方法就成了Shell分组方法的标准分法。按照这种分组方法,可以对Shell排序改进如下:
/// <summary>
/// Shell排序的中心思想是将数据进行分组,然后对每一组数据进行排序,
/// 在每一组数据都有序之后,就可以对所有的分组利用插入排序进行最后一次排序
/// 这样可以显著减少数据交换的次数,以达到加快排序速度的目的。
/// 新的算法将增量固定为3,也就是满足了D.E.Knuth的h(m+1)=3h(m)+1,h(1)=1
/// </summary>
/// <param name="myArray">输入的待排序序列</param>
public static void ShellSort( int [] myArray )
{
int i, j, increment, temp;
increment = 3;
while( increment > 0 )
{
for( i=0; i < myArray.Length; i++ )
{
j = i;
temp = myArray[i];
while( (j >= increment) && (myArray[j-increment] > temp) )
{
myArray[j] = myArray[j - increment];
j = j - increment;
}
myArray[j] = temp;
}
if( increment/2 != 0 )
{
increment = increment/2;
}
else if( increment == 1 )
{
increment = 0;
}
else
{
increment = 1;
}
}
}
3 快速排序
快速排序是最有名且最常用的排序算法之一,因为它的时间复杂度为O(nlgn),而且可以按照递归的思路来设计程序,它的想法如下:一般的排序方法(冒泡排序法,交换排序法,选择排序法,插入排序法)一次都只能减少一个数据量,而Shell排序法每次都按照分组来排序,相当于减少了较多的数据量,如果每次都大幅度地减少数据量,那么效率会更高。
如果可以在排完一个数据后,使其余数据分成两部分,一部分都比它大,另外一部分都比它小,再分别排序两组数据,那么效果会更好。快速排序就是利用了这个思路。
如果第一次先以第0个数据为比较值,用pos代表其下标,希望可以把pos放到合适的位置k,使右边的数据都比它小,左边的数据都比它大。
要达到这个目标,可以按照下面的办法来处理。
(1)一方面,我们从左边开始,向右去找一个比pos大的数据,设其位置为lower,然后从右边开始,向左去寻找一个比pos小的数据,设其位置为upper。然后我们交换lower和upper。接下来继续寻找,找到符合条件的就交换。直到upper小于lower为止,这时候说明右边的数据都小于pos,左边的数据都大于pos。
以pos的位置将数据分成两边,再按相同的办法来处理两边的数据直到所有的数据结束。
其算法如下:
public class QuickSorter
{
private static int[] myArray;
private static int arraySize;
public static void Sort( int[] a )
{
myArray = a;
arraySize = myArray.Length;
QuickSort();
}
/// <summary>
/// 快速排序可以用递归的办法来设计程序。其基本思想如下:
/// 首先,找到一个基准的数据,是数据右边的数都比它小,左边都比它
/// 大,然后用这个基准将所有数据分成两部分,再对每一部分使用同样
/// 的算法。
/// 如何找到基准数据呢?从第0个数据开始,从数据的右端向左寻找比它大的数,
/// 同时从数据的左端向右寻找比它小的数,然后交换两个数,这样一直找下去
/// 直到左端数据的索引大于右端的数据索引,然后将第0个数和左端的数据索引交换
/// 这样就得到了数据右边的数都比它小,左边的都比它大。
private static void QuickSort()
{
QSort( 0, arraySize - 1);
}
private static void QSort(int left, int right)
{
int pivot, l_hold, r_hold;
l_hold = left;
r_hold = right;
pivot = myArray[left];
while (left < right)
{
//从右向左找小于pivot的值,然后将它和左边的值交换,直到
//相遇为止
while ((myArray[right] >= pivot) && (left < right))
right--;
if (left != right)
{
myArray[left] = myArray[right];
left++;
}
//从左向右找大于pivot的值,然后将它和右边的值交换,直到
//相遇为止
while ((myArray[left] <= pivot) && (left < right))
left++;
if (left != right)
{
myArray[right] = myArray[left];
right--;
}
}
//将pivot放在中间,现在左边的值都小于pivot,右边的值都大于pivot
myArray[left] = pivot;
pivot = left;
left = l_hold;
right = r_hold;
//递归,直到最后一个元素
if (left < pivot)
QSort(left, pivot-1);
if (right > pivot)
QSort( pivot+1, right);
}
}
可以象下面来引用:
QuickSorter.Sort( myArray );
4 结果比较
通过实际的排序比较表明,在数据量达到50000的时候,QuickSort是最快的算法,RadixSort其次,ShellSort最慢。但是考虑到RadixSort只能排序整数,而且需要很多的暂存空间,ShellSort和QuickSort都是不错的算法。当数据量很大的时候,QuickSort是最好的选择。
/******************************************************************************************
*【Author】:flyingbread
*【Date】:2007年1月31日
*【Notice】:
*1、本文为原创技术文章,首发博客园个人站点(http://flyingbread.cnblogs.com/),转载和引用请注明作者及出处。
*2、本文必须全文转载和引用,任何组织和个人未授权不能修改任何内容,并且未授权不可用于商业。
*3、本声明为文章一部分,转载和引用必须包括在原文中。
******************************************************************************************/
分享到:
相关推荐
Java 基数排序算法详解 基数排序(Radix Sort)是一种非比较排序算法,它的基本思想是将待排序的数组按照位数(个位、十位、百位)进行划分,然后依次对每个位上的数字进行排序,最终得到有序的数组。基数排序的...
经典排序算法 - 基数排序Radix sort 经典排序算法 - 鸽巢排序Pigeonhole sort 经典排序算法 - 归并排序Merge sort 经典排序算法 - 冒泡排序Bubble sort 经典排序算法 - 选择排序Selection sort 经典排序算法 - ...
《数据结构与算法》实验报告-典型排序算法实践-基数排序 本实验报告的主要目的是通过基数排序算法的实现来掌握三类内部排序的设计思想、适用范围与算法实现,并深入理解和掌握优化排序算法的设计思想和实现过程。 ...
数据结构学习笔记排序算法:基数排序数据结构学习笔记排序算法:基数排序数据结构学习笔记排序算法:基数排序数据结构学习笔记排序算法:基数排序数据结构学习笔记排序算法:基数排序数据结构学习笔记排序算法:基数...
基数排序算法是一种高效的排序算法,它的工作原理是通过将数组按照数字的每个位数排序,以达到排序的目的。基数排序算法的时间复杂度为O(nk),因此它适合大规模的数据排序。 9.桶排序算法 桶排序算法是一种高效的...
8. **计数排序、桶排序、基数排序**:这些是线性时间复杂度的非比较型排序算法,适用于特定的数据分布情况,例如整数排序。 这个压缩包中的Java代码实现可以帮助我们深入理解这些算法的逻辑和细节,同时也可以作为...
**基数排序**是一种非比较型整数排序算法,它的原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。这个过程可以类比为人类按身高排队,然后再按照年龄、姓氏等其他特征进行进一步的排序。基数排序在处理...
非比较排序算法如基数排序、桶排序和计数排序,不依赖于元素间的比较,而是利用了元素的固有特征进行排序,通常在特定条件下具有更高的效率。 #### 三、外部排序 外部排序涉及大量数据,通常超过内存容量,因此...
【排序算法概述】 排序算法是计算机科学中至关重要的一部分,它涉及到如何有效地重新排列一组数据,使其按照特定标准(如升序或降序)排列。排序算法的效率对程序的性能有着显著影响,尤其是在处理大量数据时。虽然...
基数排序是一种非比较型整数排序算法,它的原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。在这个C语言实现的版本中,主要包含两个核心函数:`RadixCountSort` 和 `RadixSort`。 `RadixCountSort` ...
此文件为数据结构中的九种排序算法,包含一些排序方法的过程,其九种排序包括:直接插入排序,折半插入排序,希尔排序,冒泡排序,快速排序,选择排序,堆排序,归并排序,基数排序!
十大经典排序算法 ... (2)排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部...常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。这种算法适用于大量整数的排序,特别是位数较少的情况,因为它的时间复杂度可以达到线性级别。 2. **...
7. 基数排序(Radix Sort):基数排序是一种非比较型排序算法,通过按位数从低到高进行排序,适用于整数排序。它的复杂度可以达到线性O(nk),其中k是数字的最大位数。 8. 堆排序(Heap Sort):堆排序利用了堆这种...
**排序算法——C语言实现的基数排序(RadixSort)详解** 在计算机科学中,排序算法是用于将一组数据按照特定顺序排列的算法。这里我们关注的是基数排序(RadixSort),这是一种非比较型整数排序算法,其原理是将...
C语言实现常见排序算法。编译环境:VS2010。 包括: 冒泡排序 快速排序 直接插入排序 Shell排序 直接选择排序 堆排序 归并排序(递归和非递归两种) 桶式排序 基数排序:顺序和静态队列两种方法 索引排序(采用简单...
8. 计数排序、桶排序和基数排序:这三种是线性时间复杂度的非比较排序算法,适用于特定类型的数据,如整数排序。它们不是比较元素之间的大小,而是利用数据特性进行排序。 了解并掌握这些排序算法有助于提升编程...
传统的排序算法如快速排序和基数排序各有优缺点:快速排序算法因其简洁的结构和较好的平均性能而广受欢迎,但在最坏的情况下其性能会退化至O(N^2);基数排序虽然具有稳定的O(N)时间复杂度,但由于其算法结构较为复杂...
7. **计数排序**(Counting Sort)、**桶排序**(Bucket Sort)和**基数排序**(Radix Sort):这三种排序算法属于非比较型排序,适用于特定类型的数据。例如,基数排序适用于整数排序,根据每一位进行排序。它们的...
链式基数排序是一种非比较型整数排序算法,它的原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。在描述中提到的实验报告中,主要目标是实现一个链式基数排序算法,用于对一组数据进行最低位优先的排序...