数据结构复习-06 排序

冒泡排序(稳定)

流程
1 从头开始比较每一对相邻元素,如果第1个比第2个大,就交换它们的位置
执行完一轮后,最未尾那个元素就是最大的元素。
2 忽略1中曾经找到的最大元素,重复执行步骤1,直到全部元素有序。

public class BubbleSort1<T extends Comparable<T>> extends Sort<T>{
	
	@Override
	protected void sort1() {
		for (int end = array.length - 1; end > 0; end--) {
			for (int begin = 1; begin <= end; begin++) {
				if (cmp(begin, begin - 1) < 0) {
					swap(begin, begin - 1);
				}
			}
		}
	}
	
}

/**
 * 优化 如果序列尾部已经局部有序,可以记录最后1次交换的位置,减少比较次数
 */
public class BubbleSort3<T extends Comparable<T>> extends Sort<T>{
	
	@Override
	protected void sort2() {
		for (int end = array.length - 1; end > 0; end--) {
			// 设为1是因为:如果这轮遍历一次交换都没进行
			// 则说明序列后面已经有序, 则 end = sortedIndex = 1
			// end--后等于0,会直接跳出循环
			int sortedIndex = 1; 
			for (int begin = 1; begin <= end; begin++) {
				if (cmp(begin, begin - 1) < 0) {
					swap(begin, begin - 1);
					// 记录最后1次交换的位置,减少比较次数
					sortedIndex = begin;
				}
			}
			end = sortedIndex;
		}
	}

}
protected int cmp(int i1, int i2) {
	
	return ((i1-i2 > 0) ? 1:0);

protected void swap(int i1, int i2) {
	swapCount++;
	T tmp = array[i1];
	array[i1] = array[i2];
	array[i2] = tmp;
}	

选择排序(不稳定)

流程
1 从序列中找出最大的那个元素,然后与最未尾的元素交换位置
执行完一轮后,最未尾的那个元素就是最大的元素。
2 忽略1中曾经找到的最大元素,重复执行步骤1

public class SelectionSort<T extends Comparable<T>> extends Sort<T> {

	@Override
	protected void sort3() {
		for (int end = array.length - 1; end > 0; end--) {
			int max = 0;
			for (int begin = 1; begin <= end; begin++) {
				if (cmp(max, begin) < 0) {
					max = begin;
				}
			}
			swap(max, end);
		}
	}
	
}

堆排序(不稳定)

流程
1 对序列进行原地建堆
2 依次取出堆中最大值,并删除
3 重复执行以2操作,直到堆的元素数量为 1

交换堆顶元素与尾元素
堆的元素数量减 1
对 0 位置进行 1 次 下滤

/**
 * 堆排序
 */
public class HeapSort<T extends Comparable<T>> extends Sort<T> {
	private int heapSize; // 堆大小

	@Override
	protected void sort4() {
		// 原地建堆(自下而上的下滤)
		heapSize = array.length;
		for (int i = (heapSize >> 1) - 1; i >= 0; i--) {
			siftDown(i);
		}
		
		while (heapSize > 1) {
			// 交换堆顶元素和尾部元素
			swap(0, --heapSize);

			// 对0位置进行siftDown(恢复堆的性质)
			siftDown(0);
		}
	}
	
	private void siftDown(int index) {
		T element = array[index];
		
		int half = heapSize >> 1;
		while (index < half) { // index必须是非叶子节点
			// 默认是左边跟父节点比
			int childIndex = (index << 1) + 1;
			T child = array[childIndex];
			
			int rightIndex = childIndex + 1;
			// 右子节点要存在, 并且比左子节点大
			if (rightIndex < heapSize && 
					cmp(array[rightIndex], child) > 0) { 
				child = array[childIndex = rightIndex];
			}
			
			// 大于等于子节点
			if (cmp(element, child) >= 0) break;
			
			array[index] = child;
			index = childIndex;
		}
		array[index] = element;
	}

}

插入排序(稳定)

流程

1 在执行过程中,插入排序会将序列分为 2 部分,头部是已经排好序的,尾部是待排序的。
2 从头开始扫描每一个元素,每当扫描到一个元素,就将它插入到头部合适的位置,使得头部数据依然保持有序。

/**
 * 插入排序-优化
 * 交换  -> 挪动
 */
public class InsertionSort2 <T extends Comparable<T>> extends Sort<T>{
	@Override
	protected void sort() {
		for(int begin = 1; begin < array.length; begin++){
			int cur = begin; 
			T v = array[begin]; // 将待插入元素备份
			while(cur > 0 && cmp(v, array[cur-1]) < 0){
			// 头部有序数据中比待插入元素大的,都朝尾部方向挪动1个位置
				array[cur] = array[cur - 1]; 
				cur--;
			}
			array[cur] = v; // 将待插入元素放到最终的合适位置
		}
	}
}	

二分搜索

/**
 * 插入排序-优化2
 * 二分搜索进行优化
 */
public class InsertionSort3 <T extends Comparable<T>> extends Sort<T>{

	@Override
	protected void sort() {
		for(int begin = 1; begin < array.length; begin++){
			// 将遍历到的元素插入到前面已经排好序的序列中
			insert(begin, search(begin)); //search() 查找到要插入的位置
		}
	}
	
	/**
	 * 将source位置的元素插入到dest位置
	 */
	private void insert(int source, int dest){
		T v = array[source]; // 备份要插入的元素
		// 将 [insertIndex, begin)范围内的元素往右边挪动一个单位
		for(int i = source; i > dest; i--){
			array[i] = array[i - 1];
		}
		array[dest] = v;
	}
	
	/**
	 * 利用二分搜索找到index位置元素的待插入位置
	 * 已经排好序数组的区间范围是[0,index)
	 */
	private int search(int index){
		int begin = 0;
		int end = index;
		while(begin < end){
			int mid = (begin + end) >> 1;
			if(cmp(array[index], array[mid]) < 0){
				end = mid;
			}else{
				begin = mid + 1;
			}
		}
		return begin;
	}
	
}

归并排序(稳定)


@SuppressWarnings("unchecked")
	public class MergeSort <T extends Comparable<T>> extends Sort<T> {
	private T[] leftArray;
	
	@Override
	protected void sort() {
		// 准备一段临时的数组空间, 在merge操作中使用
		leftArray = (T[])new Comparable[array.length >> 1];
		sort(0, array.length);
	}
	
	/**
	 * 对 [begin, end) 范围的数据进行归并排序
	 */	
	private void sort(int begin, int end){
		if(end - begin < 2) return; // 至少要2个元素
		
		int mid = (begin + end) >> 1;
		sort(begin, mid); // 归并排序左半子序列
		sort(mid, end);	// 归并排序右半子序列
		merge(begin, mid, end); // 合并整个序列
	}
	
	/**
	 * 将 [begin, mid) 和 [mid, end) 范围的序列合并成一个有序序列
	 */
	private void merge(int begin, int mid, int end){
		int li = 0, le = mid - begin; // 左边数组(基于leftArray)
		int ri = mid, re = end;	// 右边数组(array)
		int ai = begin; // array的索引
		
		// 备份左边数组到leftArray
		for(int i = li; i < le; i++){
			leftArray[i] = array[begin + i];
		}
		
		// 如果左边还没有结束
		while(li < le){ // li == le 左边结束, 则直接结束归并
			if(ri < re && cmp(array[ri], leftArray[li]) < 0){ // cmp改为<=0会失去稳定性
				array[ai++] = array[ri++]; // 右边<左边, 拷贝右边数组到array
			}else{
				array[ai++] = leftArray[li++]; // 左边<=右边, 拷贝左边数组到array
			}
		}
	}

}

快速排序(不稳定)


public class QuickSort<T extends Comparable<T>> extends Sort<T> {

	@Override
	protected void sort() {
		sort(0, array.length);
	}
	/**
	 * 对 [begin, end) 范围的元素进行快速排序
	 */
	private void sort(int begin, int end){
		if(end - begin < 2) return;
		
		// 确定轴点位置 O(n)
		int mid = pivotIndex(begin, end);
		// 对子序列进行快速排序
		sort(begin, mid);
		sort(mid + 1, end);
	}
	/**
	 * 构造出 [begin, end) 范围的轴点元素
	 * @return 轴点元素的最终位置
	 */
	private int pivotIndex(int begin, int end){
		// 随机选择轴点元素
		swap(begin, begin + (int)Math.random()*(end - begin));
		// 备份begin位置的元素
		T pivot = array[begin];
		// end指向最后一个元素
		end--;
		while(begin < end){
			while(begin < end){	// 从右往左扫描
				if(cmp(pivot, array[end]) < 0){ // 右边元素 > 轴点元素
					end--;
				}else{ // 右边元素 <= 轴点元素
					array[begin++] = array[end];	
					break;
				}
			}
			while(begin < end){ // 从左往右扫描
				if(cmp(pivot, array[begin]) > 0){ // 左边元素 < 轴点元素
					begin++;
				}else{ // 左边元素 >= 轴点元素
					array[end--] = array[begin];
					break;
				}
			}
		}
		// 将轴点元素放入最终的位置
		array[begin] = pivot;
		// 返回轴点元素的位置
		return begin; // begin==end
	}
	
}

希尔排序(不稳定)


public class ShellSort <T extends Comparable<T>> extends Sort<T>{

	@Override
	protected void sort() {
		// 根据元素数量算出步长序列
		List<Integer> stepSequence = shellStpSequence();
		// 按步长序列划分进行排序
		for (Integer step : stepSequence) {
			sort(step); // 按step进行排序
		}
	}
	
	/**
	 * 分成step列进行排序
	 */
	private void sort(int step){
		// col: 第几列, column的简称
		for(int col = 0; col < step; col++){
			// 插入排序对每一列进行排序
			for(int begin = col + step; begin < array.length; begin += step){
				// col、col+step、col+2*step、col+3*step
				int cur = begin;
				while(cur > col && cmp(cur, cur - step) < 0){
					swap(cur, cur - step);
					cur -= step;
				}
				
			}
		}
	}
	
	/**
	 * 希尔本人提出的步长序列
	 */
	public List<Integer> shellStpSequence(){
		List<Integer> stepSequence = new ArrayList<>();
		int step = array.length;
		while((step >>= 1) > 0){
			stepSequence.add(step);
		}
		return stepSequence;
	}
	
}

计数排序(稳定)


public class CountingSort extends Sort<Integer>{
	
	@Override
	protected void sort() {
		// 找出最值
		int max = array[0];
		int min = array[0];
		for(int i = 0; i < array.length; i++){
			if(array[i] < min){
				min = array[i];
			}
			if(array[i] > max){
				max = array[i];
			}
		}
		// 开辟内存空间,存储次数
		int[] counts = new int[max - min + 1];
		// 统计每个整数出现的次数
		for(int i = 0; i < array.length; i++){
			counts[array[i] - min]++;
		}
		// 累加次数
		for(int i = 1; i < counts.length; i++){
			counts[i] += counts[i - 1];
		}
		// 从后往前遍历元素,将它放到有序数组中的合适位置
		int[] newArray = new int[array.length];
		for(int i = array.length - 1; i >= 0; i--){
			newArray[--counts[array[i] - min]] = array[i];
		}
		// 将有序数组赋值到array
		for (int i = 0; i < newArray.length; i++) {
			array[i] = newArray[i];
		}
	}
	
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值