【数据结构与算法】排序

/**
 * 一些结论:
 * (1)所有的简单排序,即On2的,都是稳定排序。稳定排序的特征是比较操作存在于相邻的元素。
 * (2)比On2快的,除了归并,其余都不稳定,但是归并需要On空间
 */
public class Sort {

	//直接排序,最基本的思路就是在前i个元素已经排好的基础上,把第i+1个元素插入到合适的位置,但插入第i个时,缓存为t,向前遍历,只要大于就后移,直到找到第一个小于等于的元素k,然后k+1设置为t
	public void straightInsertSort(int nums[]){
		if (nums == null) {
			return;
		}
		for(int i = 1; i < nums.length; i++){
			int temp = nums[i];
			int j = i - 1;
			while(j >= 0 && nums[j] > temp){ //稳定
				nums[j + 1] = nums[j];
				j--;
			}
			nums[j + 1] = temp;
		}
	}
	
	//二分插入排序,在查找插入位置时使用二分法,设置low和high,直到low>high,这时要么是因为high - 1要么因为low + 1,结果真正的插入位置就是high+1处,因此high+1到i-1的所有元素后移。注意在循环体内,当中间小于t时,往左边找,大于等于往右边,这样当l==h时,右边的全大于,左边的小于等于,那么此时的元素是小于等于t的最大元素,因此在这个元素后面插入。
	//插入排序复杂度还是On2,因为sort分两种操作,比较和移动,比较是logn,但是移动仍然是On2
	public void binaryInsertSort(int[] nums){
		for(int i = 1; i < nums.length; i++){
			int temp = nums[i], low = 0, high = i - 1;
			while(low <= high){
				int m = (low + high) / 2;
				if (temp < nums[m]) {
					high = m - 1;
				}else {
					low = m + 1;
				}
			}
			for(int j = i ; j > high + 1; j--){
				nums[j] = nums[j - 1];
			}
			nums[high + 1] = temp;
		}
	}
	
	//希尔排序,思路是多次带gap的直接插入排序。最外层的while循环是控制gap的,每一次while循环都是一次带gap的插入排序,在每一次插入排序中,还是会遍历所有的元素,但是在向前插入的操作中是跳跃gap进行的。因此看上去像是有gap组子排序。
	//关于其的时间复杂度分析尚未得出,但是可以知道介于On和On2之间,所以是插入排序中最好的。不稳定。
	//关于选取increment也没有定论,但是有两条规则,第一是最后一次必须是1,第二个是相邻两次不要互为倍数。比较推荐的有2^k-1,3^k-1等。不稳定。
	//关于shell排序的有点有一个比较直接的分析,第一趟排序,元素基本无序,但是因为gap很大,所以操作次数很少,最后一次,基本接近有序,虽然gap是1,但操作次数也不多。
	public void shellSort(int[] nums){
		int length = nums.length;
		int increment = 1;
		while(increment * 2 + 1 < length){
			increment = increment * 2 + 1;
		}
		while(increment >= 1){
			for(int i = increment; i < length; i++){
				int temp = nums[i];
				int j = i - increment;
				while(j >= 0 && nums[j] > temp){
					nums[j + increment] = nums[j];
					j -= increment;
				}
				nums[j + increment] = temp;
			}
			increment = (increment - 1) / 2;
		}
	}
	
	//快速排序目前是比较快的方法,复杂度是平均Onlogn。其思路是一个递归,包含不断地分解过程。每一次partition都会以第一个元素为准,把low到high的元素进行移位,使得左边比key小,右边比k大。
	//这个partition本身就可以是一个算法题目,做法是通常选第一个为k,low和high指针分别交替靠拢,以遍历到所有之间的元素。一个移动时,另一个指向槽,最终high=low,均指向槽,把槽赋值为k即可。
	//一直递归下去,知道包含一个元素时返回。快排不稳定。
	public void Qsort(int[] nums){
		int low = 0, high = nums.length - 1;
		partition(nums, low, high);
	}
	
	private void partition(int[] nums, int low, int high) {
		if (low < high) {
			int key = nums[low];
			int l = low;
			int r = high;
			while (l < r) {
				while (l < r && key <= nums[r])
					r--;
				nums[l] = nums[r];
				while (l < r && key >= nums[l])
					l++;
				nums[r] = nums[l];
			}
			nums[r] = key;
			partition(nums, low, l - 1);
			partition(nums, l + 1, high);
		}
	}
	
	//归并排序,其思路很清晰,拿到数组以后,分为两半,各自递归调用排序子序列,然后再合并子序列。有两个注意点,在分割时,必须是[low,m]和[m + 1, high],这样如果是size为2,那么结果会被分割成两个siez为1.
	//第二点是,在merge操作时,要新建一个缓存,存两个序列的较小值,为了保证稳定性,如果相等,应该取前面序列的元素。所以if条件是first <= second,then choose first。
	//归并排序是稳定排序,复杂度nlgn。
	public void MergeSort(int nums[]){
		Msort(nums, 0, nums.length - 1);
	}
	
	private void Msort(int nums[], int low, int high){
		if (low >= high) {
			return;
		}
		int m = (low + high) / 2;
		Msort(nums, low, m);
		Msort(nums, m + 1, high);
		Merge(nums, low, m, high);
	}
	
	private void Merge(int nums[], int low, int m, int high){
		int[] cash = new int[high - low + 1];
		int index = 0;
		int first = low, second = m + 1;
		while(first <= m && second <= high){
			if (nums[first] <= nums[second]) {
				cash[index++] = nums[first++];
			}else {
				cash[index++] = nums[second++];
			}
		}
		if (first > m ) {
			while(second <= high){
				cash[index++] = nums[second++];
			}
		}
		if (second > high ) {
			while(first <= m){
				cash[index++] = nums[first++];
			}
		}
		for(int i = 0; i < cash.length; i++){
			nums[low + i] = cash[i];
		}
	}
}

下面是另一种思路的快排,更好理解:

 

public class QS {

	public static void main(String args[]){
		QS qs = new QS();
		int[] array = {6,9,30,6,-1,60,124};
		qs.QSort(array);
		print(array);
	}
	
	public static void print(int[] array){
		String s = "[";
		for(int i = 0; i < array.length; i++){
			s += array[i] + " ";
		}
		System.out.println(s);
	}
	
	public void QSort(int[] array){
		if(array == null)
			return;
		QSort(array, 0, array.length - 1);
	}
	
	private int partition(int[] array, int low, int high){
		int pivot = array[low];
		int last = low;
		for(int i = low + 1; i <= high; i++){
			if(array[i] <= pivot){
				last ++;
				swap(array, last, i);
			}
		}
		swap(array, low, last);
		return last;
	}
	
	private void QSort(int[] array, int low, int high){
		if(low > high)
			return;
		int m = partition(array, low, high);
		QSort(array, low, m - 1);
		QSort(array, m + 1, high);
	}
	
	private void swap(int[] array, int i, int j){
		int t = array[i];
		array[i] = array[j];
		array[j] = t;
	}
	
}


一躺遍历完成划分,用last记录了小于等于pivot的最后一个元素的位置,紧挨着该位置的后面的元素比pivot大。整个过程就是一趟遍历,遇到小于等于pivot的就向后移动last,并且交换,否则就跳过。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值