排序方法:
public class SortMethods {
/* -------- 插入排序 -------- */
//原理:将数组分为前后两个部分,每次从后面的未排序的部分中取出最前面的一个元素,插入到前面已经排序好的部分的合适位置处。
public void insertSort(int[] A) {
for (int i = 0; i < A.length; i++) //[0,i-1]表示已排序的前半部,[i,length-1]表示未排序的后半部
for (int j = i; j > 0; j--) //取出未排序部分的第一个元素,让其一步一步的向前移动,直至找到第一个比它小的停下,插入完成
if (A[j-1] > A[j]) {
int tmp = A[j - 1];
A[j - 1] = A[j];
A[j] = tmp;
}
}
/* -------- 选择排序 -------- */
//原理:将数组分为两个部分,前部已排序,后部未排序。每次从未排序的部分找最小的元素,放到前部的最后
public void selectSort(int[] A) {
for(int i=0; i<A.length; i++){ //[0,i-1]是排序好的,每次从[i,length-1]取最小,与A[i]交换
int minIndex = i;
for(int j=i; j<A.length; j++) //利用循环,找到[i,length-1]中的最小元素
if(A[j] < A[minIndex]) minIndex = j;
int temp = A[i];
A[i] = A[minIndex]; //交换A[i]与找到的最小元素
A[minIndex] = temp;
}
}
/* -------- 冒泡排序 -------- */
//原理:大元素作为bubble,不断的向右移动,直至合适的位置
public void bubbleSort(int[] A) {
for(int i=0; i<A.length; i++) //取出一个元素A[i]作为bubble,让其向后移动
for(int j=i+1; j<A.length; j++) //用A[i]跟它后面的所有元素相比较,使其上浮,直至碰到比它更大的元素
if(A[i] > A[j]){
int temp = A[j];
A[j] = A[i];
A[i] = temp;
}
}
/* ---- 简单排序算法的总结与分析 ----
* 数学上证明:对于N个互异数所组成的数组而言,其平均逆序数是 N(N-1)/4。
* 一般的简单排序算法,都是通过比较相邻的两个元素并交换的。这种交换每次只能消除一个逆序。
* 那么就得到一般简单排序算法的下界:Ω(N2),即其处理一般的数组,最好情况也就是N方。
* 故,为了使一个排序算法以亚二次的或O(N2)的时间运行,必须要使它的每次交换不止消除一个逆序
---- */
/* -------- 希尔排序 -------- */
//原理:通过比较并交换相距一定间隔的元素来实现排序。使用一个增量序列:h1,h2,h3...ht, 在使用hk进行排序后,
//要保证数组中相隔hk的元素都有序,即A[i]<A[i+hk]。只要h1=1(全为1的序列就退化为简单排序),就一定能实现排序,
//对shell排序来说,增量序列的选取会影响算法的性能,下面算法选取序列:ht=N/2, hk=h(k+1)/2, h1=1
public void shellSort(int[] A) {
for(int gap=A.length/2; gap>0; gap/=2) //增量序列
for(int i=gap; i<A.length; i++){ //对A[i], A[i-h], A[i-2h]...A[i-n*h]执行插入排序
int temp = A[i];
int j;
for(j=i; j>=gap && temp<A[j-gap]; j -= gap)
A[j] = A[j-gap];
A[j] = temp;
}
}
//对每个增量序列中的元素,取A[i], A[i-gap], A[i-2gap]...A[i-n*gap]的子序列运用插入排序
//插入排序的另一种实现:
/*
* for(int i=1; i<A.length; i++){
* int temp = A[i];
* int j;
* for(j=i; j>0 && temp<A[j-1]; j--) //大于A[i]的都向后移
* A[j] = A[j-1];
* A[j] = temp; //此时A[j-1]是小于A[i]的第一个元素,执行插入
*/
/* -------- 堆排序 -------- */
//原理:对N个元素建立堆(优先队列),花费O(N)的线性时间。然后执行N次的deleteMin操作,每次花费O(logN)
//故利用heap能实现O(NlogN)的排序。但是算法需要额外的附加数组(heap的底层可由数组实现),故还需N个存储空间。
//下面的堆排序算法回避了使用额外数组的问题,方法是在每次deleteMin之后,堆缩小1,然后放入min元素。但是这样每次
//都把最小元素放入到数组的最后了,故改变堆序性,使用MAX堆,每次将堆中的MAX取出,堆缩1,然后max放到堆缩小所空出的位置
//算法描述:首先建立一个Max堆,堆序性为root节点最大。然后将当前堆中的第一个元素与最后一个元素交换,即将MAX拿到数组最后,
//原先堆的最后一个元素成为新的root,然后讲堆的大小缩1,并将新root下滤,保持堆序性,重复这个过程即完成排序。
public void heapSort(int[] A) {
for (int i = A.length / 2; i >= 0; i--) { //buildheap,遍历节点,然后下滤
percDown(A, i, A.length);
}
for (int i = A.length - 1; i > 0; i--) {
int tmp = A[0];
A[0] = A[i]; //交换当前堆的第一个元素与最后一个元素
A[i] = tmp;
percDown(A, 0, i); //将新的root下滤,并利用i的递减来实现堆的缩小
}
}
private int leftChild(int i) { //取左子节点
return 2 * i + 1;
}
private void percDown(int[] a, int i, int n) { //将node(i)下滤方法,使用的是MAX堆,根节点为最大的元素
int child; //a[i]为要下滤的节点,n为当前堆的后边界
int temp;
for (temp=a[i]; leftChild(i)<n; i=child) { //沿较大子节点的方向下滤,以保持堆序性
child = leftChild(i);
if (child!=n-1 && a[child]<a[child + 1]) {
child++;
}
if (temp < a[child]) {
a[i] = a[child];
} else {
break; //a[i]比它的两个子节点都大,则a[i]的堆序性正确,不用改变
}
}
a[i] = temp; //此时的i即为合适的插入位置
}
/* -------- 归并排序 -------- */
//原理:经典的分治策略,递归地将数组分为前后两个部分各自归并排序,然后使用合并算法将两个部分合并起来,最坏情形O(NlogN)
//将两个以排序的数组合并所需时间是线性的,最多进行N-1次比较
//归并排序与其他排序相比一般进行更少的比较次数,但是它使用了一个额外的附加内存,且还进行了将数据从临时数组拷贝回来这样的附加操作
//然而,在对Java泛型对象进行排序时,比较对象的大小是费时的,而拷贝移动对象则很容易(因为仅仅是引用的变化),故使用归并排序很合适
//Java类库中的泛型排序算法就是使用归并排序。
public void mergeSort(int[] A) {
int[] tmpArray = new int[A.length];
mergeSort(A, tmpArray, 0, A.length-1);
}
private void mergeSort(int[] a, int[] temp, int left, int right){
if(left < right){
int center = (left + right) / 2;
mergeSort(a, temp, left, center); //分成前后两部分递归
mergeSort(a, temp, center+1, right);
merge(a, temp, left, center, center+1, right); //合并已排序的数组
}
}
private void merge(int[] a, int[] temp, int leftPos, int leftEnd, int rightPos, int rightEnd){
int tmpPos = leftPos;
int numElements = rightEnd - leftPos + 1;
while(leftPos <= leftEnd && rightPos <= rightEnd){
if(a[leftPos] <= a[rightPos])
temp[tmpPos++] = a[leftPos++];
else
temp[tmpPos++] = a[rightPos++];
}
while(leftPos <= leftEnd)
temp[tmpPos++] = a[leftPos++];
while(rightPos <= rightEnd)
temp[tmpPos++] = a[rightPos++];
//**** 多注意下面的拷贝的形式,是在递归中动态的不断的拷贝交换数据的 ****//
for(int i=0; i<numElements; i++,rightEnd--) //将数组拷贝回去,注意此处的拷贝方法,切不能写成:
a[rightEnd] = temp[rightEnd]; // for(i=0;i<N;i++) a[i]=temp[i];
}
/* -------- 快速排序 -------- */
//原理:也是一种分治的递归算法,取一元素为枢纽元,大于枢纽元的和小于枢纽元的分成两个子集,对两个子集排序再合并起来
//其对C++或Java的基本类型排序特别有用,平均运行时间O(NlogN),最坏为O(N2)
//枢纽元的选取:随机取三选中值
//对于小数组来说(N《20),快速排序不如插入排序的
public void quickSort(int[] A) {
quickSort(A, 0, A.length-1);
}
public void quickSort(int[] a, int left, int right) {
if (left < right) {
int pivot = a[(left + right) / 2]; //取枢纽元
int i = left - 1;
int j = right + 1;
while (true) {
while (a[++i] < pivot); // 向右找
while (a[--j] > pivot); // 向左找
if (i >= j)
break;
int temp = a[i];
a[i] = a[j]; //if(i<j) swap(a[i], a[j]);
a[j] = temp;
}
quickSort(a, left, i - 1); // 对左边进行递回
quickSort(a, j + 1, right); // 对右边进行递回
}
}
}
测试main函数:
public class Main {
public static void main(String[] args) {
SortMethods mySort = new SortMethods();
final int MAX = 25;
int[] myArray = new int[MAX];
{
System.out.print("生成的随机数组是:");
for (int i = 0; i < MAX; i++) {
myArray[i] = (int) (Math.random() * 100);
System.out.print(myArray[i] + " ");
}
System.out.println();
}
long start=System.nanoTime();
// mySort.insertSort(myArray);
// mySort.selectSort(myArray);
// mySort.bubbleSort(myArray);
// mySort.shellSort(myArray);
// mySort.heapSort(myArray);
// mySort.mergeSort(myArray);
mySort.quickSort(myArray);
long end=System.nanoTime();
System.out.print("排序之后的数组是:");
for (int i = 0; i < MAX; i++) {
System.out.print(myArray[i] + " ");
}
System.out.println();
System.out.println("排序使用时间:" + (end - start) + " ns");
}
}
方法对比:
排序法 | 平均时间复杂度 | 最差情形复杂度 | 稳定度 | 额外空间 | 备注 |
冒泡 | | | 稳定 | O(1) | n小时较好 |
交换 | | | 不稳定 | O(1) | n小时较好 |
选择 | | | 不稳定 | O(1) | n小时较好 |
插入 | | | 稳定 | O(1) | 大部分已排序时较好 |
基数 | | | 稳定 | O(n) | B是真数(0-9), R是基数(个十百) |
Shell | | | 不稳定 | O(1) | s是所选分组 |
快速 | | | 不稳定 | O(nlogn) | n大时较好 |
归并 | | | 稳定 | O(1) | n大时较好 |
堆 | | | 不稳定 | O(1) | n大时较好 |