【算法】第二章排序

##目录

  1. 初级排序
    1. 选择排序
    2. 插入排序
    3. 希尔排序
  2. 归并排序
    1. 自顶向下排序
    2. 自底向上排序
  3. 快速排序
  4. 优先队列

##初级排序 ###一、 选择排序

简述:选择排序就是遍历一遍数组把最小的和第一个数字交换。第二遍遍历数组时候选择和第二个交换,一次类推。

//注意不要在for循环中用a.length()不然每次都要获取a.length();
public voiv sort(Comparable[] a){
	//for(int i=0,i<a.length;i++){
	int N = a.length;
	for(int i=0,i<N;i++){
		int min = i;
		for(int j=i+1;j<N;j++){
			//if(a[min]>a[j]){
			if(less(a[j],a[min])){
				min = j;
			}
			//int tmp = a[i];
			//a[i] = a[min];
			//a[min] = tmp;
			exch(a,j,min);
		}
	}
}

二、 插入排序

简述:类似于打牌时候边抽牌边整理。第二张开始,就最大的开始对比,然后直到不大时候插入。

public void sort(Comparable[] a){
	int N = a.length;
	//下面是我写的,不知道书上有什么优势。有空测一下。
	//for(int i=1;i<N;i++){
	//	if(less(a[i],a[i-1])){
	///		int pos = i;
	//		while(less(a[pos],a[pos-1])&&pos>0){
	//			exch(a,pos,pos-1);
	//			pos--;
	//		} 
	//	}
	//}
	
	for(int i=1,i<N;i++){
		for(int j=i;j>0&&less(a[j],a[j-1]);j--){
			exch(a,j,j-1);
		}
	}
}
命题C

插入排序的比较次数 大于等于倒置的数量,小于等于倒置数量加上数组大小再减一。
最坏就是全倒置嘛,这时候每倒置一次就要多比一次,然而数组里每个数都要比较,除了第一个数时候和自身不比较,所以是 最差等于倒置数量+数组大小-1。
所以对 部分有序的数组十分高效,也适合小规模数组。

当不考虑交换和比较成本,两个差不多,但是感觉数组规模小的化插入有优势,规模大的化选择有优势。【平均而言】

三、希尔排序

public static void sort(Comparable a){
	int N = a.length;
	int h = 1;
	//
	while(h<N/3){
		h = h * 3 +1
	}
	while(h>=1){
		for(int i=h;i<N;i++){
			for(int j=i;j<N&&less(a[j],a[j-h]);j-=h){
				exch(a,j,j-h);
			}
		}
		h=h/3;
		
	}
}

平均每个比较次数增幅为N1/5 大的话才比较明显。
没什么可说的,三方比较,希尔排序一直赛高。可以把希尔排序看作套壳的插入排序。

归并排序

最基础先是合并两个有序表的方法[书上叫 原地归并抽象方法,不知道为甚么]

//谨记读数组时候要用copy数组,改写的时候才用原数组
public static void merge(Comparable[] a,int lo,int mid,int hi){
	int N = a.length;
	//copy
	for(int k = lo;k <= hi;k++){
		auk[k]=a[k];
	}
	int i = lo;
	int j = mid + 1;
	for(int k=0;k<N;k++){
		if(i>mid) 							a[k] = auk[j++];
		else if(j>=hi)						a[k] = auk[i++];
		else if(less(aux[j],aux[i])) 		a[k] = auk[i++];
		else 								a[k] = auk[i++];
	}
}

接下来是使用到原地归并的两个归并方法,使用思想不一样,但是我觉得本质都一样。
我测试了 两个排序50个100万完全随机数据,花的时间都差不多。
前者说是自顶向下,其实也是下到两两元素才开始正式比对。

自顶向下的归并排序 分治思想
private static Comparable[] aux;
public static void sort(Comparable[] a){
	int N = a.length;
	aux = new Comparable(N);
	sort(a,0,N-1);
}

public static void sort(Comparable[] a,int lo ,int hi){
	int mid = lo + (hi - lo)/2;
	if(lo>=hi) return;
	sort(a,lo,mid);
	sort(a,mid+1,hi);
	merge(a,lo,mid,hi);
}

因为插入排序适合小数组,据说是 归并 + 插入 赛高。还没有验证。

自底向上的归并排序
private static Compararble[] aux;
public static void sort(Comparable[] a){
	int N = a.length;
	//分割,1 ,2 ,4 ,6 ,8
	//边界问题最头疼,首先为什么是N 不是N - 1?
	//最重要的一点是 我门用sz表达的是分割的数组数字个数,如果是个数自然是按原值N过来。
	for(int sz=1;sz < N ;sz = sz + sz){	
		//这里为什么是N
		//暂时不知道,但是我用-1试过好几次也没差。反正就是为了防止过界又防止余下部分太小
		for(int lo = 0;lo < N - sz;lo +=2*sz){
			merge(a,lo,lo + sz -1 ,Math.min(lo + 2*sz - 1,N - 1));
		}
	}
}

最后的几个结论不是很懂,只能二刷时候再看了。

快速排序

快速排序也是把问题分开再分开,不同的是它并不是平均分成两组,而是产生一个分开因子。笼统而言和归并差不多吧,但是我测试速度感觉比归并快一点。时间能达到1/2甚至更多

public static void sort(Comparable[] a){
	int N = a.length;
	sort(a,0,N-1);
}

public static void sort(Comparable[] a,int lo,int hi){
	if(lo>=hi) return;
	int par = partition(a,lo,hi);
	sort(a,lo,par - 1);
	sort(a,par + 1 ,hi);
}

//core code
public static void partition(Comparable[] a, int lo,int hi){
	Comparable v = a[lo];
	i = lo;
	j = hi+1;
	while(true){
		//while(less(a[++i],v)) if(i>=hi) break;
		while(less(a[++i],v)) if(i==hi) break;
		//while(less(v,a[--j])) if(j<=lo) break;
		//因为a[lo] = v; 所以 当j = lo时候根本进不来,所以if(j==lo)是个多余的条件
		while(less(v,a[--j])) {}/*if(j==lo) break;*/
		if(i>=j) break;
		exch(a,i,j);
	}
	exch(a,j,lo);
	return j;
}

//quick3way
public static void sort3way(Comparable[] a,int lo,int hi){
	if(lo>=hi) return;
	
	int lt = lo;
	int gt = hi;
	int i = lo + 1;
	Comparable v = a[lo];
	//其实就是用 i 去遍历每一个数字,小的从 lt 插入 大的 从 gt插入。此为三向切分
	while(i<=gt){
		int cmp = a[i].compareTo(v);
		if(cmp<0) exch(a,lt++,i++);
		//换了之后i不用往下走,因为gt已经把一个陌生值换给它了。
		else if(cmp>0) exch(a,i,gt--);
		//和自己相等,下一个
		else i++;
	}
	//lt-1 都是比v小的
	//gt+1 都是比v大的
	//剩下的都是等于v的。
	sort3way(a,lo,lt-1);
	sort3way(a,gt+1,hi);
}

###优先队列 优先队列是一种思想,只能说堆排序用了这种思想。 大概就是当一个完全二叉树时候,对于一个节点k(从 1 开始的序号), 他的父节点为k/2 左子节点为 2k 右子节点为 2k + 1 下面的节点总比上面的节点大(根据实际用途,自己要求) 我们可以通过遍历某个节点的 当我们添加一个方法的时候 可以先将插入对象添加到队尾 可以使用 swim方法,让其上浮 ####由下至上的对有序化(上浮) ``` //注意我们这里假设数组从 index 1 开始 public void swim(Comparable[] a,int k){ //k为插入元素index while(k>=1){ if(less(a[k/2],a[k])){ exch(a,k/2,k); k = k/2; } } //这样直接上浮到适合的位置为止。 } ``` 书上介绍优先队列用法,就是用来快速删除最大(最小【需另设队列】)元素的。

当我们删除最大节点,先使他下沉,使他子元素补位,
由上至下的堆有序化,下沉

//注意我们这里假设数组从 index 1 开始
public void sink(Comparable[] a,int k,int N){
	while(k<=N){
		int j = 2 * k;
		//让哪个儿子重哪个儿子上
		if(j<N && less(a[j],a[j+1]))  j=j+1;
		if(less(a[j],a[k])) break;
		exch(a,k,j);
		//换做最大元素,下一回合。
		k = j;
	}
}
堆排序
public static void sort(Comparable[] a){
	//第一步 排成优先队列
	int N = a.length;
	//元素先按1开始,等到使用数组的时候,再恢复实际元素
	for(int k=N/2;k>=1;k--){
		sink(a,k,N);
	}

	while(N>1){
		//最前面肯定使最大的,所以将其和正确位置置换
		exch(a,0,N-1);
		//最先面的已经不是最轻的了,换人。
		sink(a,1,--N);
	}
}

public static void sink(Comparable[] a,int k,int N){
	while(2*k<=N){
		int j = 2 * k;
		//使用时候按实际位置所以要-1.包括less 和 exch方法
		if(j < N && less(a[j-1],a[j])) j = j + 1;
		//如果不比儿子轻,就此结束,下一回合。
		if(!less(a[k-1],a[j-1])) break;
		exch(a,j-1,k-1);
		k = j;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值