排序算法总结

排序方法:

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(n2)     O(n2) 稳定 O(1) n小时较好
交换     O(n2)     O(n2) 不稳定 O(1) n小时较好
选择     O(n2)     O(n2) 不稳定 O(1) n小时较好
插入     O(n2)     O(n2) 稳定 O(1) 大部分已排序时较好
基数    O(logRB)    O(logRB) 稳定 O(n)

B是真数(0-9),

R是基数(个十百)

Shell   O(nlogn)  O(ns) 1 不稳定 O(1) s是所选分组
快速   O(nlogn)    O(n2) 不稳定 O(nlogn) n大时较好
归并   O(nlogn)    O(nlogn) 稳定 O(1) n大时较好
  O(nlogn)    O(nlogn) 不稳定 O(1) n大时较好



原文地址:https://www.cnblogs.com/dosmile/p/6444444.html