算法笔记2——排序

基础排序

1.选择排序:
    首先,找到数组中最小的那个元素,其次,将他和数组第一个元素交换位置,
    再次,在剩下的元素中找到最小的元素,将他和数组的第二个元素交换位置。
    如此反复,直到将整个数组排序。不断的选择剩余元素的最小值
2.插入排序:(对部分有序数组很有效)
    为了给要插入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位。

	public class Insertion {

		public static void main(String[] args) {
			
			int []arr= {12,16,3,64,-5,69};
			for (int i = 1; i < arr.length; i++) {
				for(int j=i;j>0&&arr[j]<arr[j-1];j--) {
					int temp=arr[j];
					arr[j]=arr[j-1];
					arr[j-1]=temp;
				}
			}
			System.out.println(Arrays.toString(arr));
		}
	}	

    插入排序不会访问索引右侧的元素,而排序不会访问索引左侧的元素。
    对于随机排序数组,两者的运行时间都是平方级别的。

3.希尔排序
    对于大规模乱序数组插入排序很慢,因为它只会交换相邻的元素,因此元素只能一点一点的从数组的一段移动到另外一端。
    希尔排序为了加快速度简单的改进了插入排序,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组进行排序。
    希尔排序的思想是是数组中任意间隔为h的元素是有序的。这样的数组都是h有序数组。换句话说,一个h有序数组就是h个相互独立的有序数
    组编制在一起组成的一个数组。在进行排序时,如果h很大,我们就能将元素移动到很远的地方,为实现更小的h有序创造方便。
    用这种方式,对于任意以1结尾的h序列,我们都够能将数组排序。
    希尔排序更高效的原因是它权衡了子数组的规模和有序性。排序之初,各个子数组都很短,排序之后子数组都是部分有序的, 这种情况很适合插入排序。

	public class XirSort {

		public static void main(String[] args) {
			
			int []arr= {12,16,3,64,-5,69,25,75,95,-4,-36,52,76,-92,34,83};
			int N=arr.length;
			int h=1;
			while(h<N/3)h=h*3+1;//1,4,13,40,121,364,1093, ...
			while(h>=1)
			{
				for (int i = h; i < N; i++) {
					for (int j = i; j>=h&&arr[j]<arr[j-h]; j-=h) {
						int temp=arr[j];
						arr[j]=arr[j-h];
						arr[j-h]=temp;
					}
				}
				h=h/3;
			}
			System.out.println(Arrays.toString(arr));
		}
	}


高级排序

4.归并排序:要将一个数组排序,可以先将它分成两半分别排序,然后将结果归并起来。
    优点:保证任意长度为N的数组排序所需的时间和NlogN成正比
    缺点:所需的额外空间和N成正比。

原地归并抽象方法:将子数组a[lo...mid]和a[mid+1...hi]归并为一个有序的数组并将结果保存到a[lo...hi]中

从aux[]两边取值,谁小取谁,左边取完了取右边,右边取完了取左边。
该方法将所有元素复制到aux[]中,然后再归并到a[]中。方法在归并时(第二个for循环)进行了四个条件判断:
左半边用尽(取右半边的元素)、右半边用尽(取左半边元素)、右半边的当前元素小于左半边的当前元素(取右半边的元素)
以及右半边的当前元素大于等于左半边的当前元素(取左半边元素)。

	public static void merge(Comparable[]a,int lo,int mid,int hi)
	{
		int i=lo,j=mid+1;
		for(int k=lo;k<=hi;k++)//将a[lo...hi]复制到aux[lo...hi]
			aux[k]=a[k];
		for(int k=lo;k<=hi;k++)//归并到
			if(i>mid)				a[k]=aux[j++];
			else if(j>hi)			a[k]=aux[i++];
			else if(aux[j]<aux[i])	a[k]=aux[j++];
			else					a[k]=aux[i++];
	}

自顶向下的归并排序:分治思想

要对子数组a[lo...hi]进行排序,先将它分为a[lo...mid]和a[mid+1...hi]两部分,
分别通过递归调用将他们单独排序,最后将有序的子数组归并为最终的排序结果。

	public class Merge
	{
		private static Comparable[]aux;		//归并所需的辅助数组
		public static void sort(Comparable[]a)
		{
			aux=new Comparable[a.length];	//创建辅助数组
			sort(a,0,a.length-1);
		}
		private static void sort(Comparable[]a,int lo,int hi)
		{
			if(hi<=ho)	return;
			int mid=lo+(hi-lo)/2;
			sort(a,lo,mid);		//将左半边排序
			sort(a,mid+1,hi);	//将右半边排序
			merge(a,lo,mid,hi);	//归并
		}
	}

扫描二维码关注公众号,回复: 4945438 查看本文章

自底向上的归并排序:先归并那些微型数组,然后再成对归并得到子数组,如此这般,知道整个数组归并到一起。
首先进行的是两两的归并(把每个元素想象成一个大小为1的数组),然后是四四归并(把两个大小为2的数组归并为大小为4的数组),然后一直下去。。。
在每一轮归并中,最后一次归并的第二个数组可能比第一个子数组要小,如果不是的话所有归并的中两个数组的大小都应该一样。而下一轮会翻倍。
自底向上的归并排序会多次遍历整个数组,根据子数组大小进行两两归并。子数组的大小sz的初始值为1,每次加倍。最后一个子数组的大小只有在数组是sz的偶数倍的时候才会等于sz(否则它会比sz小)。

public class MergeBU
{
	private static Comparable[]aux;		//归并所需的辅助数组
	public static void sort(Comparable[]a)
	{
		int N=a.length;
		aux=new Comparable[N];
		for(int sz=1;sz<N;sz=sz+sz)					//sz子数组的大小
			for(int lo=0;lo<N-sz;lo+=sz+sz)			//lo:子数组的索引
			merge(a,lo,lo+sz-1,Math.min(lo+sz+sz-1,N-1));
	}		
}

猜你喜欢

转载自blog.csdn.net/TDOA1024/article/details/86511859