最近在看JAVA的一些排序算法,和大家分享一下,感觉非常有帮助~~为了简化,这里直接采用数组进行排序,没有引入比较策略。
为了便于测试,编写了一个生产数组的方法,如下:
/**
* 为排序生产测试数据
* @author Administrator
*
*/
public class NumberFactory {
/**
* 成产1个含有n个数的数组,每个数的大小在1-n之间
* @param n 数据的个数
* @return 生产的数组
*/
public static int[] buildNumber(int n){
int[] num = new int[n];
Random random = new Random();
for(int i = 0;i < num.length;i++){
num[i] = random.nextInt(n);
}
return num;
}
}
1 插入排序
1.1基本思路
从前往后进行循环(由n-1趟循环完成),当到循环到第p个数时,该算法保证前面的p-1个数都是有序的。
1.2复杂度
插入排序的平均复杂度为O(n2)。在下面的方法中,如果待排数组是以排序的,那么复杂度为O(n)。
1.3实现
import java.util.Arrays;
/**
* 插入排序
* @author Administrator
*
*/
public class InsertSequenceTest {
/**
* 插入排序
* @param array
*/
public static void insertSequence(int array[]){
int j;
for(int i=0;i<array.length;i++){//大循环
int p = array[i];
for(j=i;j>0&&p<array[j-1];j--){//将元素判断插入到前面
array[j] = array[j-1];
}
array[j] = p;
}
}
public static void main(String[] args) {
int[] a = NumberFactory.buildNumber(10);
System.out.println("***********插入排序前**************");
System.out.println(Arrays.toString(a));
System.out.println("***********插入排序后**************");
InsertSequenceTest.insertSequence(a);
System.out.println(Arrays.toString(a));
}
}
2希尔排序
2.1基本思路
该方法引入一个增量序列h1,h2,h3...,该增量是递减的,算法保证每轮循环中每隔增量h的数都是已排序的,如当增量为3时数组{4,2,3,6,8,7,10,9,8},当增量为1时,排序完成。2.2复杂度
该算法有着亚二次时间界。使用希尔排序的对坏情形的复杂度为O(n2),但是当增量h选择合理时最坏情形可达到O(n3/2)。
2.3实现
import java.util.Arrays;
/**
* 希尔排序
* @author Administrator
*
*/
public class ShellsortSquenceTest {
/**
* 希尔排序
* @param array 代排序数组
*/
public static void shellsort(int array[]){
for(int shell = array.length/2;shell>0;shell/=2){//递减的增量
int j;
for(int i = shell;i<array.length;i++){
int o = array[i];
for(j = i;j-shell >= 0&&o < array[j-shell];j -= shell){
array[j] = array[j-shell];
}
array[j] = o;
}
}
}
}
3堆排序
3.1基本思路
堆是一个被完全填满的二叉树,如果是大顶堆则每个父亲节点的值不小于其所有孩子的值,小顶堆则相反。在堆中很容易找到她的左孩子(2n+1)。堆排序首先先建立一个堆,然后让其根节点与最后一个叶子节点交换,此时堆的结构被破坏(根节点被替换),然后将根节点下滤到适合她的位置,循环直到剩下最后一个根节点(值得注意的是,在下滤的过程中由于不能肯定节点是否有右孩子,所以应做判断)。
3.2复杂度
构建堆需要O(n)的时间,每个元素下滤需要O(logn)的时间,n个元素的时间为O(nlogn),所以复杂度为O(n)+O(nlogn)即O(nlogn)。
3.3实现
import java.util.Arrays;
public class HeapSequenceTest {
/**
* 堆排序
* @param a
*/
public static void heapSort(int[] a){
for(int i = a.length/2; i >= 0; i--) //先建立堆
percolatedown(a,i,a.length);
for(int i = a.length-1; i >= 0; i--){//将头尾互换,建立堆
changeNum(a, 0, i);
percolatedown(a,0,i);
}
}
/**
* 建立堆主方法
* @param a
* @param begin 开始位置
* @param len 需要的总长度
*/
private static void percolatedown(int[] a, int begin, int len) {
int child; //与父亲交换位置的孩子
int temp = a[begin]; //用于存放变量
int j = 1;
for(; leftchild(begin) < len; begin = child){
child = leftchild(begin);
//如果存在右孩子,选择一个最小的
if(child+1 < len&& a[child] < a[child+1])
child++;
if(temp < a[child]){
a[begin] = a[child];
}
else
break;
j++;
}
a[begin] = temp;
}
/**
* 返回左孩子的下标
* @param begin
* @return
*/
private static int leftchild(int begin) {
return 2*begin+1;
}
/**
* 交换次序,该方法用于交换数组内两个下标的数字
* @param a 待交换的数组
* @param left 被交换的下标1
* @param center 被交换的下标2
*/
private static void changeNum(int[] a, int arg1, int arg2) {
int tem = a[arg1];
a[arg1] = a[arg2];
a[arg2] = tem;
}
public static void main(String[] args) {
int[] a = NumberFactory.buildNumber(19);
System.out.println("***********堆排序前**************");
System.out.println(Arrays.toString(a));
System.out.println("***********堆排序后**************");
HeapSequenceTest.heapSort(a);
System.out.println(Arrays.toString(a));
}
}
4归并排序
4.1基本思路
归并排序是合并已经排序的表,基本思路如下(递归分治思想)
待排序列{56,12,34,1,7,65,9}
第一步结果{[12,56],[1,34],[7,65],[9]}
第二步结果{[1,12,34,56],[7,9,65]}
第三部结果{[1,7,9,12,34,56,65]}
该方法是经典的分治策略,她将问题分成一些小的问题然后递归求解。
4.2复杂度
归并排序的复杂度是O(nlogn),但是有个明显的缺点是该方法需要拷贝的线性附加内存,所以内存开销会比较大,但是她的明显优点是有着很少的比较次数。
4.3实现
/**
* 归并排序
* @author Administrator
*
*/
public class MergeSequenceTest {
/**
* 引出归并排序
* @param a 待排序数组
*/
public static void mergeSort(int[] a){
int[] temp = new int[a.length];
mergeSort(a,temp,0,a.length-1);
}
/**
* 递归调用归并
* @param a 待排序数组
* @param temp 被拷贝的数组
* @param left 待排数组起始
* @param right 待排数组结尾
*/
private static 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+1,right);
}
}
/**
* 归并排序主要算法
* @param a 待排序数组
* @param temp 被拷贝的数组
* @param leftpos 第一个数组的起始下标
* @param rightpos 第二个数组的起始下标
* @param rightend 结尾下标
*/
private static void merge(int[] a, int[] temp, int leftpos, int rightpos, int rightend) {
int leftend = rightpos-1; //第一个数组的最后下标
int temppos = leftpos; //拷贝数组的起始下标
int sum = rightend - leftpos+1; //总的个数
//比较拷贝到被拷贝数组
while(leftpos<=leftend&&rightpos<=rightend)
if(a[leftpos]<a[rightpos])
temp[temppos++] = a[leftpos++];
else
temp[temppos++] = a[rightpos++];
while(leftpos<=leftend)
temp[temppos++] = a[leftpos++];
while(rightpos<=rightend)
temp[temppos++] = a[rightpos++];
//拷贝回来
for(int i = 0;i<sum;i++,rightend--)
a[rightend] = temp[rightend];
}
public static void main(String[] args) {
int[] a = NumberFactory.buildNumber(100);
System.out.println("***********归并排序前**************");
System.out.println(Arrays.toString(a));
System.out.println("***********归并排序后**************");
MergeSequenceTest.mergeSort(a);
System.out.println(Arrays.toString(a));
}
}
5快速排序
5.1基本思路
快速排序也是采用分治的思想,在快速排序中首先是找到一个枢纽元素,一轮排序后比枢纽小的放在枢纽前面,比枢纽大的放在枢纽后面。
待排序列{56,12,34,1,7,65,9}
选取枢纽34后(在下面的算法实现中选取枢纽采用了(首+中+尾)三点选取中值所以结果与下方有所不同,但是思想没变)
第一步结果{9,12,1,7,34,65,56}(9与56互换。。。)
第二步结果{9,1,7,12,34,56,65}(下划线为第二轮枢纽)
第三部结果{1,7,9,12,34,56,65}
5.2复杂度
快速排序的最坏运行时间为O(n2),但是如果枢纽选择合理很少会出现最坏情况,她的平均时间为O(nlogn)。
5.3实现
在下面的方法中,枢纽采用三个点选取中值的方法。另外考虑到存在相等元素的情况,为了保证分治的平衡(如果全部相等则会出现最坏情况,即分治不均匀)所以遇到相等的也会进行交换。
package com.example.sequence;
import java.util.Arrays;
public class QuickSequenceTest {
/**
* 快速排序入口
* @param a
*/
public static void quickSort(int[] a){
quickSort(a,0,a.length-1);
}
/**
* 快速排序主方法
* @param a
* @param left
* @param right
*/
private static void quickSort(int[] a, int left, int right) {
if(left+1 < right){
//查找枢纽中值
int flag = findFlag(a,left,right);
int i = left, j = right-1;
for(;;){
while(a[++i] < flag){} //此行可看出快速排序的优势。
while(a[--j] > flag){} //相等也进行交换,防止递归不均匀
if(i<j){//交换位置
changeNum(a, i, j);
}
else
break;
}
changeNum(a,i,right-1); //枢纽归位
quickSort(a,left,i-1);
quickSort(a,i+1,right);
}else{
//主要是对3个元素进行排序
findFlag(a,left,right);
}
}
/**
* 找到每轮的枢纽,采用中值法(首+尾+中),并且对该3个元素进行排序
* @param a 待查找的数组
* @param left 待查找的数组的首下标
* @param right 待查找的数组的尾下标
* @return 枢纽中值,并且将3个元素排序
*/
private static int findFlag(int[] a, int left, int right) {
if(left<right){
int center = (left+right)/2;
if(a[center] < a[left]) changeNum(a,left,center);
if(a[right] < a[left]) changeNum(a,right,left);
if(a[right] < a[center]) changeNum(a,right,center);
changeNum(a,center,right-1);//因为right已经大于枢纽值,所以不参加比较
return a[right-1];
}
return 0;
}
/**
* 交换次序,该方法用于交换数组内两个下标的数字
* @param a 待交换的数组
* @param left 被交换的下标1
* @param center 被交换的下标2
*/
private static void changeNum(int[] a, int arg1, int arg2) {
int tem = a[arg1];
a[arg1] = a[arg2];
a[arg2] = tem;
}
public static void main(String[] args) {
int[] a = NumberFactory.buildNumber(10);
System.out.println("***********快速排序前**************");
System.out.println(Arrays.toString(a));
System.out.println("***********快速排序后**************");
QuickSequenceTest.quickSort(a);
System.out.println(Arrays.toString(a));
}
}