排序——冒泡归并快速选择插入堆

Posted x_k

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序——冒泡归并快速选择插入堆相关的知识,希望对你有一定的参考价值。

冒泡排序,O(n²)

原理:遍历集合多次,比较相邻的两个元素,将较大或者较小的元素向后移动,类似于“气泡”一样向上浮动。

	/**
	 * 
	 * <p>Title: 基础原理</p>
	 * <p>author : xukai</p>
	 * <p>date : 2017年5月16日 下午2:51:22</p>
	 * @param array
	 */
	public static void bubbleSort1(int[] array) 
		// 1.外层循环,次数为(length-1)
		for (int i = 1; i < array.length; i++) 
			// 2.遍历集合,比较相邻元素大小
			for (int j = 0; j < array.length - i; j++) 
				if (array[j] > array[j + 1]) 
					swap(array, j, j + 1);
				
			
			System.out.print("第" + i + "次循环执行之后:");
			print(array);
		
	

优化:假如在外层循环中,某次循环一次都没有执行swap操作,说明集合已经排序完毕,无需再遍历

	/**
	 * 
	 * <p>Title: 某次遍历没有swap(排序完成),那么下一次也不需要遍历</p>
	 * <p>author : xukai</p>
	 * <p>date : 2017年5月16日 下午3:10:03</p>
	 * @param array
	 */
	private static void bubbleSort2(int[] array) 
		boolean needNextPass = true;
		for (int i = 1; i < array.length && needNextPass; i++) 
			// 1.假设集合排序完毕
			needNextPass = false;
			for (int j = 0; j < array.length - i; j++) 
				if (array[j] > array[j + 1]) 
					swap(array, j, j + 1);
					// 2.假如未被执行,集合排序完毕,外层循环结束
					needNextPass = true;
				
			
			System.out.print("第" + i + "次循环执行之后:");
			print(array);
		
	

归并排序,O(nlog(n))

原理:1.将集合平均拆分为两个子集合,对子集合进行归并排序。2.直到子集合中有且仅有一个元素,对两个子集合排序。

public class MergeSort 

	public static void main(String[] args) 
		int[] array =  2, 9, 5, 4, 1, 6, 7, 6 ;
		mergeSort(array);
	

	/**
	 * 
	 * <p>Title: 1.递归拆除数组</p>
	 * <p>author : xukai</p>
	 * <p>date : 2017年5月17日 下午5:51:16</p>
	 * @param array
	 */
	public static void mergeSort(int[] array) 
		if (array.length > 1) 
			// 1.左侧数组
			int[] arrayLeft = new int[array.length / 2];
			System.arraycopy(array, 0, arrayLeft, 0, array.length / 2);
			mergeSort(arrayLeft);
			
			// 2.右侧数组
			int arrayRightLength = array.length - arrayLeft.length;
			int[] arrayRight = new int[arrayRightLength];
			System.arraycopy(array, array.length / 2, arrayRight, 0, arrayRightLength);
			mergeSort(arrayRight);
			
			// 3.左右侧数组合并
			int[] temp = merge(arrayLeft, arrayRight);
			print(temp);

			// 4.返回已经排序完毕的子数组
			System.arraycopy(temp, 0, array, 0, temp.length);
		 else 
			return;
		
	

	/**
	 * 
	 * <p>Title: 合并两个数组</p>
	 * <p>author : xukai</p>
	 * <p>date : 2017年5月17日 下午5:51:33</p>
	 * @param arrayLeft
	 * @param arrayRight
	 * @return
	 */
	private static int[] merge(int[] arrayLeft, int[] arrayRight) 
		int[] temp = new int[arrayLeft.length + arrayRight.length];
		int indexOfLeft = 0;
		int indexOfRight = 0;
		int indexOfTemp = 0;

		while (indexOfLeft < arrayLeft.length && indexOfRight < arrayRight.length) 
			if (arrayLeft[indexOfLeft] < arrayRight[indexOfRight]) 
				temp[indexOfTemp++] = arrayLeft[indexOfLeft++];
			 else 
				temp[indexOfTemp++] = arrayRight[indexOfRight++];
			
		

		while (indexOfLeft < arrayLeft.length) 
			// 左侧未遍历完
			temp[indexOfTemp++] = arrayLeft[indexOfLeft++];
		

		while (indexOfRight < arrayRight.length) 
			// 右侧未遍历完
			temp[indexOfTemp++] = arrayRight[indexOfRight++];
		

		return temp;
	

	private static void print(int[] array) 
		for (int i = 0; i < array.length; i++) 
			System.out.print(array[i] + " ");
		
		System.out.print("\\n");
	

这里需要注意一点代码中的第4步,temp数组为已经排好序的数组,一定要复制给原数组

快速排序,O(nlog(n))

原理:任意选择主元元素(默认index=0),将数组分为两部分,左侧元素小于或等于主元,右侧元素大于主元。对左右两部分递归此操作。

/**
 * @moudle: QuickSort
 * @version:v1.0
 * @Description: 快速排序:任意选择主元元素(默认index=0),将数组分为两部分,左侧元素小于或等于主元,右侧元素大于主元。递归此规则。
 * @author: xukai
 * @date: 2017年5月17日 下午6:21:08
 *
 */
public class QuickSort 

	public static void main(String[] args) 
		int[] array =  5, 2, 9, 3, 8, 5, 0, 1, 6, 7 ;
		quickSort(array);
		print(array);
	

	public static void quickSort(int[] array) 
		quickSort(array, 0, array.length - 1);
	

	private static void quickSort(int[] array, int firstIndex, int lastIndex) 
		if (firstIndex < lastIndex) 
			int pivotIndex = partition(array, firstIndex, lastIndex);
			quickSort(array, firstIndex, pivotIndex - 1);
			quickSort(array, pivotIndex + 1, lastIndex);
		
	

	/**
	 * 
	 * <p>Title: partition</p>
	 * <p>author : xukai</p>
	 * <p>date : 2017年5月20日 上午11:39:50</p>
	 * @param array
	 * @param firstIndex
	 * @param lastIndex
	 * @return
	 */
	private static int partition(int[] array, int firstIndex, int lastIndex) 
		int pivot = array[firstIndex]; // 主元
		int low = firstIndex + 1; // 向前下标
		int high = lastIndex; // 向后下标

		while (low < high) 
			// 当前元素小于等于主元,next
			while (low <= high && array[low] <= pivot)
				low++;

			// 当前元素大于主元,prenext
			while (high >= low && array[high] > pivot)
				high--;

			// low,high未移动,array[low]>pivot && array[high] <= pivot
			if (low < high)
				swap(array, low, high);
		

		// TODO
		/**
		 * case:
		 * 1.(array[high]==pivot)=true,next
		 * 2.lastIndex - firstIndex == 1
		 */
		while (high > firstIndex && array[high] >= pivot)
			high--; 

		if (array[high] < pivot) 
			// pivot放在中间
			array[firstIndex] = array[high];
			array[high] = pivot;
			return high; // 返回主元新下标
		 else 
			return firstIndex; // 主元下标
		
	

	private static void print(int[] array) 
		for (int i = 0; i < array.length; i++) 
			System.out.print(array[i] + " ");
		
		System.out.print("\\n");
	

	private static void swap(int[] array, int i, int j) 
		int temp = array[i];
		array[i] = array[j];
		array[j] = temp;
	

***我一直没看懂代码中TODO标签下的while循环有什么作用,为什么不删除掉… ***

选择排序,O(n²)

原理:遍历数组,将最小的数放在最前面。遍历剩余元素,持续此操作。

/**
 * @moudle: SelectSort
 * @version:v1.0
 * @Description: 选择排序:遍历数组,将最小的数放在最前面。遍历剩余元素,持续此操作。
 * @author: xukai
 * @date: 2017年5月21日 下午2:51:31
 *
 */
public class SelectSort 

	public static void main(String[] args) 
		int[] array =  5, 2, 9, 3, 8, 5, 0, 1, 6, 7 ;
		selectSort(array);
		System.out.println(Arrays.toString(array));
	

	public static void selectSort(int[] array) 
		// 遍历次数为length - 1;
		for (int i = 0; i < array.length - 1; i++) 
			// 1.遍历数组找到min元素
			int minIndex = i;
			for (int j = i + 1; j < array.length; j++) 
				if (array[j] < array[minIndex]) 
					minIndex = j;
				
			
			// 2.swap(array[i],array[index])
			if (minIndex != i) 
				int temp = array[minIndex];
				array[minIndex] = array[i];
				array[i] = temp;
			
		
	


插入排序,O(n²)

原理:将新元素重复插入已排序好的子数列中

	public static void insertSort(int[] array) 
		// 遍历未排序数组下标
		for (int i = 1; i < array.length; i++) 
			// 遍历已排序完毕的子数组下标
			for (int j = 0; j < i; j++) 
				if (array[j] > array[i]) 
					// j为插入位置
					int insert = array[i];
					for (int k = i; k > j; k--) 
						array[k] = array[k - 1];
					
					array[j] = insert;
				
			
			System.out.print("第" + i + "次循环执行之后:");
			System.out.println(Arrays.toString(array));
		
	

堆排序,O(nlog(n))

堆特性:

  1. 一颗完整的二叉树(除了最后一层未满且叶子偏左,或每层都是满的)
  2. 每个结点大于或者等于它的子节点

添加结点原理:

  1. 将新元素放置进内部List集合
  2. 将新元素下标作为游标,开始遍历其父结点
  3. 如果大于父结点,swap(子节点,父结点),执行2和3,直到遍历树完毕。

删除结点原理:

  1. 判断是否为空树,if(true) return null
  2. 将最后的元素放置在index=0(根),并移除原先元素,temp=root
  3. 遍历树,游标从根开始,找到左右子树中最大值的下标maxIndex
  4. 比较游标和maxIndex的值大小,如果list(cursor)<maxIndex,那么swap
  5. cursor=maxIndex,依旧向下遍历树。直到无swap执行
import java.util.ArrayList;

/**
 * @moudle: 堆类:1.每个结点大于它的所有子结点。2.完全二叉树
 * @version:v1.0
 * @Description: TODO
 * @author: xukai
 * @date: 2017年5月21日 下午1:58:16
 *
 * @param <E>
 */
public class Heap<E extends Comparable<E>> 

	private ArrayList<E> list = new ArrayList<>();

	public Heap() 
	

	public Heap(E[] objects) 
		for (int i = 0; i < objects.length; i++) 
			add(objects[i]);
		
	

	public void add(E object) 
		list.add(object);
		// 1.新元素下标
		int currentIndex = list.size() - 1;

		while (currentIndex > 0) 
			int parentIndex = (currentIndex - 1) / 2; // 当前结点的父结点

			// 2.新元素大于其父结点,swap
			if (list.get(currentIndex).compareTo(list.get(parentIndex)) > 0) 
				E temp = list.get(currentIndex);
				list.set(currentIndex, list.get(parentIndex));
				list.set(parentIndex, temp);
			 else 
				break;
			
			
			// 3.向上遍历树
			currentIndex = parentIndex;
		
	

	public E remove() 
		// 1.判断是否为空树,if(true) return null
		if (list.size() == 0)
			return null;

		// 2.将最后的元素放置在index=0(根),并移除原先元素
		E removeObject = list.get(0);
		list.set(0, list.get(list.size() - 1));
		list.remove(list.size() - 1);
		
		// 3.从头遍历树
		int currentIndex = 0;
		while (currentIndex < list.size()) 
			// 3.1 maxIndex(left,right)
			int leftChildIndex = 2 * currentIndex + 1;
			int rightChildIndex = leftChildIndex + 1;
			if (leftChildIndex >= list.size())
				break; // 超出范围
			int maxIndex = leftChildIndex; // 默认为左子结点,右子节点可能为空
			if (rightChildIndex < list.size())
				if (list.get(maxIndex).compareTo(list.get(rightChildIndex)) < 0) 
					maxIndex = rightChildIndex;
				
			// 3.2 if(current<maxIndex) swap(current, maxIndex)
			if (list.get(currentIndex).compareTo(list.get(maxIndex)) < 0) 
				E temp = list.get(maxIndex);
				list.set(maxIndex, list.get(currentIndex));
				list.set(currentIndex, temp);
				currentIndex = maxIndex;
			 else 
				break;
			
		
		// 4.返回被删除元素
		return removeObject;
	
	
	public int getSize() 
		return list.size();
	

排序原理:执行堆的删除操作,即获得堆中最大值。

/**
 * @moudle: HeapSort 
 * @version:v1.0
 * @Description: 堆排序
 * @author: xukai
 * @date: 2017年5月21日 下午2:38:36
 *
 */ 
public class HeapSort 

	public static void main(String[] args) 
		Integer[] array = 5, 2, 9, 3, 8, 5, 0, 1, 6, 7;
		heapSort(array);
		System.out.println(Arrays.toString(array));
	
	
	public static Integer[] heapSort(Integer[] array) 
		// 1.创建一个堆对象,并初始化完毕
		Heap<Integer> heap = new Heap<>(array);
		// 2.反向遍历数组,从头开始删除
		for (int i = array.length - 1; i >= 0; i--) 
			array[i] = heap.remove();
		
		return array;
	

总结

JDK中有很多的排序方法实现,例如:Arrays里面的sort方法。
查看代码
动画演示

以上是关于排序——冒泡归并快速选择插入堆的主要内容,如果未能解决你的问题,请参考以下文章

排序算法(冒泡排序选择排序插入排序快速排序归并排序)

排序算法(冒泡排序选择排序插入排序快速排序归并排序)

排序算法(冒泡排序选择排序插入排序快速排序归并排序)

常见排序算法详解(冒泡选择插入快速希尔归并)

js实现,归并排序,快速排序;插入排序,选择排序,冒泡排序

JavaScript实现常见排序算法:冒泡,插入,选择,归并,快速,堆排序