1、冒泡排序
冒泡排序是一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。
Java代码:
import java.util.Random;
public class BubbleSort {
/**
* 改进的冒泡排序算法
* 通过标志位flag避免无谓的比较
*/
public static void bubbleSort( int[] data ){
boolean flag = true;
for( int i = 0; i < data.length && flag; i++ ){
flag = false;
for( int j = data.length - 1; j > i; j-- ){
if( data[j] < data[j-1] ){
swap( data, j - 1, j );
flag = true;
}
}
}
}
/**
* 交换数组中的两个数
*/
public static void swap( int[] data, int index1, int index2 ){
int temp = data[index1];
data[index1] = data[index2];
data[index2] = temp;
}
}
2、选择排序
时间复杂度为O(n^2),但性能上优于冒泡排序。
选择排序法就是通过n-i-1次关键字的比较,从n-i-1个关键字中选出关键字最小的记录,并和第i个记录交换。
Java代码:
import java.util.Random;
public class SelectSort {
/**
* 选择排序算法
*/
public static void selectSort( int[] data ){
for( int i = 0; i < data.length; i++ ){
for( int j = i + 1; j < data.length; j++ ){
if( data[j] < data[i] ){
swap( data, i, j );
}
}
}
}
/**
* 交换数组中的两个数
*/
public static void swap( int[] data, int index1, int index2 ){
int temp = data[index1];
data[index1] = data[index2];
data[index2] = temp;
}
}
3、插入排序
时间复杂度为O(n^2),但比冒泡和选择排序的性能要好一些。
插入排序的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。
Java代码:
import java.util.Random;
public class InsertSort {
/**
* 插入排序算法
*/
public static void insertSort( int[] data ){
for( int i = 1; i < data.length; i++ ){
for( int j = i; j > 0 && data[j] < data[j-1]; j-- ){
swap( data, j - 1, j );
}
}
}
/**
* 交换数组中的两个数
*/
public static void swap( int[] data, int index1, int index2 ){
int temp = data[index1];
data[index1] = data[index2];
data[index2] = temp;
}
}
4、希尔排序
D.L.Shell于1959年提出的一种排序算法,时间复杂度为O(n^k),1<k<2,是突破时间复杂度为O(n^2)的首批算法之一。希尔排序所需要比较的记录是跳跃式的移动,因此并不是一种稳定的排序算法。
希尔排序可以看作是对插入排序的一种改进,采取跳跃分割的策略,将相距某个“增量”的记录组成一个子序列,这样保证在子序列内分别进行直接插入排序后得到的结果是基本有序,从而减少交换次数,提高排序效率。
Java代码:
import java.util.Random;
public class ShellSort {
/**
* 希尔排序
* i为增量,增量排序的最后一个增量必须为1才行
*/
public static void shellSort( int[] data ) {
for( int inc = data.length / 2; inc > 0; inc /= 2 ){
for( int start = 0; start < inc; start++ ){
insertSort( data, start, inc );
}
}
}
/**
* 以start为起点,inc为间隔进行插入排序
*/
private static void insertSort( int[] data, int start, int inc ) {
for( int i = start + inc; i < data.length; i += inc ){
for( int j = i; ( j >= inc ) && ( data[j] < data[j-inc] ); j -= inc ){
swap( data, j, j - inc );
}
}
}
}
5、堆排序
堆排序就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是,将带排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次大值,如此反复执行,便能得到一个有序序列了。
堆排序无论是最好、最坏和平均时间复杂度均为O(nlogn),空间复杂度上,它只有一个用来交换的暂存单元,也非常不错。但由于记录的比较和交换是跳跃式进行,因此堆排序也是一种不稳定的排序方法。
Java代码:
import java.util.Random;
public class HeapSort {
/**
* 堆排序
*/
public static void heapSort( int[] data ) {
for( int i = data.length / 2; i >= 0; i-- ){
heapAdjust( data, i, data.length );
}
for( int i = data.length - 1; i > 0; i-- ){
swap( data, 0, i );
heapAdjust( data, 0, i );
}
}
/**
* 调整数组data[start..end]中的关键字,使之成为一个大顶堆
*/
private static void heapAdjust( int[] data, int start, int end ) {
int temp,child;
for( temp = data[start]; leftChild( start ) < end; start = child ){
child = leftChild( start );
//调整child,使其指向最大那个子节点
if( child != end - 1 && data[child] < data[child+1] )
child++;
//start既可是最初要调整的节点索引,也可是子节点索引
if( temp < data[child] )
data[start] = data[child];
else
break;
}
data[start] = temp;
}
/**
* 返回左子树的索引
*/
private static int leftChild( int i )
{
return 2 * i + 1;
}
/**
* 交换数组中的两个数
*/
public static void swap( int[] data, int index1, int index2 ){
int temp = data[index1];
data[index1] = data[index2];
data[index2] = temp;
}
}
6、归并排序
归并排序就是利用归并的思想实现的排序方法。它的原理是假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到[n/2]个长度为2或1的有序子序列;在两两归并,……,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法成为2路归并排序。
归并排序的最好、最坏、平均的时间复杂度都是O(nlogn)。由于归并排序在归并过程中需要与原始记录序列同样数量的存储空间存放归并结果以及递归时深度为logn的栈空间,因此空间复杂度为O(n+logn)。
另外,归并排序的merge函数中有if( data[leftPos] <= data[rightPos] )的语句,这就是它需要两两比较,不存在跳跃,因此归并排序是一种稳定的排序算法。
Java代码:
import java.util.Random;
public class MergeSort {
/**
* 归并排序
*/
public static void mergeSort( int[] data ) {
int[] tempArray = new int[data.length];
mergeSort( data, tempArray, 0, data.length - 1 );
}
/**
* 递归调用
*/
private static void mergeSort( int[] data, int[] tempArray, int left, int right ) {
if( left < right ){
int center = ( left + right ) / 2;
mergeSort( data, tempArray, left, center );
mergeSort( data, tempArray, center + 1, right );
merge( data, tempArray, left, center + 1, right );
}
}
/**
* 归并算法
*/
private static void merge( int[] data, int[] tempArray, int leftPos, int rightPos, int rightEnd )
{
int leftEnd = rightPos - 1;
int tempPos = leftPos;
int numElements = rightEnd - leftPos + 1;
//主循环
while( leftPos <= leftEnd && rightPos <= rightEnd ){
if( data[leftPos] <= data[rightPos] )
tempArray[tempPos++] = data[leftPos++];
else
tempArray[tempPos++] = data[rightPos++];
}
//复制剩余部分到tempArray
while( leftPos <= leftEnd )
tempArray[tempPos++] = data[leftPos++];
while( rightPos <= rightEnd )
tempArray[tempPos++] = data[rightPos++];
//复制tempArray回原数组
for( int i = 0; i < numElements; i++, rightEnd--)
data[rightEnd] = tempArray[rightEnd];
}
}
7、快速排序
快速排序的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
快速排序的最优和平均时间复杂度均为O(nlogn),最坏的时间复杂度为O(n^2)。
就空间复杂度来说,主要是递归造成栈空间的使用,最好情况,递归树的深度为logn,其空间复杂度也就是O(logn),最坏情况,需要n-1次递归调用,其空间复杂度为O(n),平均情况,空间复杂度为O(logn)。
由于关键字的比较和交换是跳跃进行的,因此,快速排序是一种不稳定的排序方法。
import java.util.Random;
public class QuickSort {
public static void quickSort( int[] data ){
quickSort( data, 0, data.length - 1 );
}
/**
* 递归操作
*/
public static void quickSort( int[] data, int low, int high ){
int pivotIndex;
if( low < high ){
pivotIndex = partition( data, low, high );
quickSort( data, low, pivotIndex - 1 );
quickSort( data, pivotIndex + 1, high );
}
}
/**
* 分割操作(排序)
*/
public static int partition( int[] data, int low, int high ){
int pivotValue = median3( data, low, high );
while( low < high ){
while( low < high && data[high] >= pivotValue )
high--;
swap( data, low, high );
while( low < high && data[low] <= pivotValue )
low++;
swap( data, low, high );
}
return low;
}
/**
* 选取枢纽值
*/
public static int median3( int[] data, int low, int high ){
int median = (low + high) / 2;
if( data[low] > data[high] )
swap( data, low ,high );
if( data[median] > data[high] )
swap( data, median, high );
if( data[median] > data[low] )
swap( data, low, median );
return data[low];
}
/**
* 交换数组中的两个数
*/
public static void swap( int[] data, int index1, int index2 ){
int temp = data[index1];
data[index1] = data[index2];
data[index2] = temp;
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] a = new int[10000000];
Random r = new Random();
for( int i = 0; i < a.length; i++ ){
a[i] = r.nextInt( 10000000 );
}
System.out.print( "数组a排序前:" );
for( int i = 0; i < 10; i++ )
System.out.print( a[i] + " , " );
long startTime = System.currentTimeMillis();
quickSort( a );
System.out.println( "\n耗费时间(毫秒):" + (System.currentTimeMillis() - startTime) );
System.out.print( "数组a排序后:" );
for( int i = 0; i < 100; i++ )
System.out.print( a[i] + " , " );
}
}
8、桶排序
桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。
桶式排序不再是一种基于比较的排序方法,它是一种比较巧妙的排序方式,但这种排序方式需要待排序的序列满足以下两个特征:
(1)待排序列所有的值处于一个可枚举的范围之类;
(2)待排序列所在的这个可枚举的范围不应该太大,否则排序开销太大。
排序的具体步骤如下:
(1)对于这个可枚举范围构建一个buckets数组,用于记录“落入”每个桶中元素的个数;
(2)将(1)中得到的buckets数组重新进行计算,按如下公式重新计算:
buckets[i] = buckets[i] +buckets[i-1] (其中1<=i<buckets.length);
桶式排序是一种非常优秀的排序算法,时间效率极高,它只要通过2轮遍历:第1轮遍历待排数据,统计每个待排数据“落入”各桶中的个数,第2轮遍历buckets用于重新计算buckets中元素的值,2轮遍历后就可以得到每个待排数据在有序序列中的位置,然后将各个数据项依次放入指定位置即可。
桶式排序的空间开销较大,它需要两个数组,第1个buckets数组用于记录“落入”各桶中元素的个数,进而保存各元素在有序序列中的位置,第2个数组用于缓存待排数据。
桶式排序是稳定的。
如果待排序数据的范围在0~k之间,那么它的时间复杂度是O(k+n)的
桶式排序算法速度很快,因为它的时间复杂度是O(k+n),而基于交换的排序时间上限是nlg n。
但是它的限制多,比如它只能排整形数组。而且当k较大,而数组长度n较小,即k>>n时,辅助数组C[k+1]的空间消耗较大(要求数字要分布均匀,最大值和总个数差距不要太大)。
当数组为整形,且k和n接近时, 可以用此方法排序。(有的文章也称这种排序算法为“计数排序”)
Java代码:
import java.util.Random;
public class BucketSort {
/**
* 桶式排序:
* 桶式排序不再是基于比较的了,它和基数排序同属于分配类的排序,
* 这类排序的特点是事先要知道待排 序列的一些特征。
* 桶式排序事先要知道待排 序列在一个范围内,而且这个范围应该不是很大的。
* 比如知道待排序列在[0,M)内,那么可以分配M个桶,第I个桶记录I的出现情况,
* 最后根据每个桶收到的位置信息把数据输出成有序的形式。
* 这里我们用两个临时性数组,一个用于记录位置信息,一个用于方便输出数据成有序方式,
* 另外我们假设数据落在0到MAX,如果所给数据不是从0开始,你可以把每个数减去最小的数。
*/
private static void bucketSort( int[] data,int max ){
int[] temp = new int[data.length];//创建临时数组
int[] count=new int[max];//创建桶
//进桶,假如有重复的,那桶里的值为重复的次数
for( int i = 0; i < data.length; i++ ){
count[data[i]]++;
}
//计算data数组的索引值,data数组索引值和前面count索引相加的值是相同的,重复的除外,但下面的方法也计算了重复的值
for( int i = 1; i < max; i++ ){
count[i] = count[i] + count[i-1];
}
//复制数组,从data数组的0位置开始,向temp的0位置开始拷贝data.length长的数据
System.arraycopy(data, 0, temp, 0, data.length);
//根据上一个for 循环计算的索引值进行计算,当碰到重复值的时候,--count[temp[k]]起了作用。
for( int k = data.length - 1; k >= 0; k-- ){
data[--count[temp[k]]] = temp[k];
}
}
public static void main(String[] args) {
Random r = new Random();
int MAX = 10000000;
int[] keys = new int[MAX];
for(int i = 0; i < MAX; i++)
{
keys[i] = r.nextInt(99999);
}
System.out.println("排序之前:");
for(int i=0;i<10;i++)
{
System.out.print(keys[i]+",");
}
bucketSort(keys,100000);//actually is 18, but 20 will also work
System.out.println();
System.out.println("排序之后:");
for(int i=keys.length-1;i>keys.length-100;i--)
{
System.out.print(keys[i]+",");
}
}
}
9、基数排序
分配排序的基本思想:排序过程无须比较关键字,而是通过“分配”和“收集”过程来实现排序。它们的时间复杂度可达到线性阶:O(n)。
一、两种多关键码排序方法
最高位优先法(MSD法)。先按k1排序,将序列分成若干子序列,每个子序列中的记录具有相同的k1值;再按k2排序,将每个子序列分成更小的子序列;然后,对后面的关键码继续同样的排序分成更小的子序列,直到按kd排序分组分成最小的子序列后,最后将各个子序列连接起来,便可得到一个有序的序列。前面介绍的扑克牌先按花色再按面值进行排序的方法就是MSD法
最次位优先法(LSD法)。先按kd排序,将序列分成若干子序列,每个子序列中的记录具有相同的kd值;再按kd-1排序,将每个子序列分成更小的子序列;然后,对后面的关键码继续同样的排序分成更小的子序列,直到按k1排序分组分成最小的子序列后,最后将各个子序列连接起来,便可得到一个有序的序列。前面介绍的扑克牌先按面值再按花色进行排序的方法就是LSD法。
二、基于LSD方法的链式基数排序的基本思想
“多关键字排序”的思想实现“单关键字排序”。对数字型或字符型的单关键字,可以看作由多个数位或多个字符构成的多关键字,此时可以采用“分配-收集”的方法进行排序,这一过程称作基数排序法,其中每个数字或字符可能的取值个数称为基数。比如,扑克牌的花色基数为4,面值基数为13。在整理扑克牌时,既可以先按花色整理,也可以先按面值整理。按花色整理时,先按红、黑、方、花的顺序分成4摞(分配),再按此顺序再叠放在一起(收集),然后按面值的顺序分成13摞(分配),再按此顺序叠放在一起(收集),如此进行二次分配和收集即可将扑克牌排列有序。
Java代码:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
public class RadixSort {
/**
* 基数排序,默认基数radix为10
* @data待排数组
* @size数组中最大数位数
*/
public static void radixSort( int[] data ){
//首先确定排序的趟数;
int max = data[0];
for( int i = 1; i < data.length; i++ ){
if( data[i] > max ){
max = data[i];
}
}
//判断位数;
int size = 0;
while( max > 0 ){
max /= 10;
size++;
}
int radix = 10;
int[] temp = new int[data.length];//用于暂存元素
int[] count = new int[radix];//用于计数排序
int divide = 1;
for( int i = 0; i < size; i++, divide *= radix ){
System.arraycopy( data, 0, temp, 0, data.length );
Arrays.fill( count, 0 );
for( int k = 0; k < temp.length; k++ ){
int tempKey = temp[k] / divide % radix;
count[tempKey]++;
}
for( int m = 1; m < count.length; m++ ){
count[m] = count[m] + count[m-1];
}
for( int n = data.length - 1; n >= 0; n-- ){
int tempKey = temp[n] / divide % radix;
data[--count[tempKey]] = temp[n];
}
}
}
}