每日算法之——常见排序算法集锦(算法之发展历程)

常见排序算法及发展历程

所谓排序,就是将数据分为有序区和无序区,通过对无序区元素的调整并扩展有序区,最后达到所有元素都有序的状态。

在排序界,常见(或者说常用)的算法主要有冒泡排序,选择排序、直接插入排序、希尔排序(其实不太常用)、堆排序、快速排序、归并排序。一些不太常用但是比较有技巧性的排序有:计数排序、位图排序、基数排序、桶排序、外部排序(一般结合hash然后多路归并)。在此,我们仅仅介绍常规的排序。

联系发展的眼光看待事物是一个优秀程序员必备的品质,在面对一个新鲜事物的时候我们不仅要问为什么这个事物会出现,这个事物的出现解决了什么问题,他是由什么事物发展而来的。。。。。。一个事物不可能凭空出现,存在必有其存在的道理。回归到我们的排序算法,很多人常常抱怨排序算法太多了,记不住,一会冒泡一会堆,晕头转向。记不住的原因还是把各个算法孤立开来看待了,下面我们就用联系发展的眼光来捋一捋这些算法的发展历程以及每个算法相对于其之前的算法的优越的地方,也许你就能记忆深刻。

冒泡排序:

冒泡排序的基本思想是对比前后两个元素,如果逆序那我就交换两个位置上的元素,这样的过程重复n次,因此其比较和交换的次数都是n^2

后来有人就想,你冒泡排序每一次比较都要交换,那么我可不可以先不交换元素位置,直到我找到无序区中的最小元素的时候,我再把最小元素放到有序区的末尾的位置呢,这样就减少了交换的次数,岂不是能节省很多不必要的交换,有了这个思想,于是有了我们的选择排序。

选择排序:

选择排序的基本思想是每一趟排序我只找到无序区元素中最小的元素位置,然后将该元素放到有序区中的末尾位置,达到扩展有序区的目的。由于减少了交换的次数,所以选择排序是较冒泡排序优越的排序算法。

但是呢,我每一次都要去无序区中找最小元素的位置,麻烦。既然我都已经把整个待排序的数组划分为了有序区和无序区,那么为何不在访问无序区元素的过程中,我将当前访问的元素在有序区中找到一个合适的位置放下不就好了么??于是有了插入排序。

插入排序:

想象打扑克时抓牌和整理牌的情形,当手中的牌已经有序时,再次抓起来的牌只需要找一个合适的位置放好即可。在插入排序中,假设第一个元素是有序的,从第二个起依次找到有序区合适的位置插入即可。插入排序中需要移动元素的位置,当数组基本有序(小的基本在前边,大的基本在后边)的时候,插入排序的效率最高。时间复杂度依然是O(n^2)。

但是,很多情况下是很难满足基本有序的情况。既然插入排序在基本有序的情况下性能相对较好,那我们就想办法达到这样已终止状态。于是有人想出了跳跃分割的方式:将相距某个增量的数据组成一个子序列,这样就能保证在子序列内分别进行插入排序后得到的结果基本有序。这就是希尔排序。

希尔排序:

希尔排序是一个里程碑式的排序算法,在希尔排序之前,人们一直认为数组的排序时间复杂度不可能超越O(n^2),希尔排序时间复杂度达到了大概O(n^\frac{3}{2}),有了希尔排序的进步才有了后来的快速排序、堆排序、归并排序等一系列的O(n\log n)的算法出现。希尔排序的基本思想是先按跳跃分割的方式让数组基本有序,然后进行一趟直接插入排序。

堆排序:

堆的定义:每个节点的值都大于或等于其左右孩子的值(大顶堆)。堆其实就是一课完全二叉树。如果对堆按层次遍历并给节点从1开始编号,那么节点之间满足:Ki>=K2i,Ki>=K2i+1,1<=i<=n/2。

堆排序思想:将待排序的序列构造成一个大顶堆,然后依次将堆顶元素移走并重新调整剩余的n-1个元素为大顶堆。

快速排序:

通过一趟排序,将待排记录分割成独立的两部分,其中一部分关键字小于另一部分的关键字,然后分别对这两部分进行快速排序。

归并排序:

思想:分而治之,各个击破。将数组划分为左右两个部分,然后分别对左右两个部分进行归并排序。直到只有一个元素的时候停止划分,然后将左右两部分有序列表进行合并。

代码:

public class AllSort {
	public static void main(String[] args) {
		int[] nums = new int[] { 2, 3, 5, 7, -1, -8, 6, 9, 10 };
		// quickSort(nums, 0, nums.length - 1);
		// heapSort(nums);
		// mergeSort(nums, 0, nums.length - 1);
		// bubbleSort(nums);
		// selectSort(nums);
		insertSort(nums);
		for (int i = 0; i < nums.length; i++) {
			System.out.print(nums[i] + "  ");
		}
	}

	/**
	 * 插入排序
	 * 
	 * @param nums
	 */
	public static void insertSort(int[] nums) {
		for (int i = 0; i < nums.length - 1; i++) {
			int j = i + 1;
			int temp = nums[j];
			int k = j;
			while (k > 0 && nums[k - 1] > temp) {
				nums[k] = nums[k - 1];
				k--;
			}
			nums[k] = temp;
		}
	}

	/**
	 * 选择排序
	 * 
	 * @param nums
	 */
	public static void selectSort(int[] nums) {
		int minIdx = 0;
		for (int i = 0; i < nums.length; i++) {
			minIdx = i;
			for (int j = i + 1; j < nums.length; j++) {
				if (nums[minIdx] > nums[j])
					minIdx = j;
			}
			if (minIdx != i) {
				int temp = nums[minIdx];
				nums[minIdx] = nums[i];
				nums[i] = temp;
			}
		}
	}

	/**
	 * 冒泡排序
	 * 
	 * @param nums
	 */
	public static void bubbleSort(int[] nums) {
		boolean isSwap = false;// 优化1
		int lastExchangeIdx = 0;
		int sortBoard = nums.length - 1;
		for (int i = 0; i < nums.length; i++) {
			// for (int j = 0; j < nums.length - i - 1; j++) {
			for (int j = 0; j < sortBoard; j++) {// 优化2
				if (nums[j] > nums[j + 1]) {
					int temp = nums[j];
					nums[j] = nums[j + 1];
					nums[j + 1] = temp;
					isSwap = true;// 优化1
					lastExchangeIdx = j;// 优化2
				}
			}
			if (isSwap == false) {// 优化1
				break;
			}
			sortBoard = lastExchangeIdx;// 优化2
		}
	}

	/**
	 * 堆排序
	 * 
	 * @param nums
	 */
	public static void heapSort(int[] nums) {
		for (int i = nums.length / 2; i >= 0; i--) {
			heapAdjust(nums, i, nums.length);
		}
		for (int j = nums.length - 1; j >= 0; j--) {
			int temp = nums[j];
			nums[j] = nums[0];
			nums[0] = temp;
			heapAdjust(nums, 0, j);
		}
	}

	public static void heapAdjust(int[] nums, int rootIdx, int len) {
		int leftChildIdx = 2 * rootIdx < len ? 2 * rootIdx : -1;
		int rightChileIdx = 2 * rootIdx + 1 < len ? 2 * rootIdx + 1 : -1;
		int largestIdx = rootIdx;
		if (leftChildIdx != -1 && nums[largestIdx] < nums[leftChildIdx])
			largestIdx = leftChildIdx;
		if (rightChileIdx != -1 && nums[largestIdx] < nums[rightChileIdx])
			largestIdx = rightChileIdx;
		if (largestIdx != rootIdx) {
			int temp = nums[largestIdx];
			nums[largestIdx] = nums[rootIdx];
			nums[rootIdx] = temp;
			heapAdjust(nums, largestIdx, len);
		}
	}

	/**
	 * 快速排序
	 * 
	 * @param nums
	 * @param left
	 * @param right
	 */
	public static void quickSort(int[] nums, int left, int right) {
		if (left < right) {
			int temp = nums[left];
			int i = left;
			int j = right;
			while (i < j) {
				while (i < j && nums[j] > temp) {
					j--;
				}
				if (i < j) {
					nums[i++] = nums[j];
				}
				while (i < j && nums[i] < temp) {
					i++;
				}
				if (i < j) {
					nums[j--] = nums[i];
				}
			}
			nums[i] = temp;
			quickSort(nums, left, i - 1);
			quickSort(nums, i + 1, right);
		}
	}

	/**
	 * 归并排序
	 * 
	 * @param nums
	 * @param left
	 * @param right
	 */
	public static void mergeSort(int[] nums, int left, int right) {
		if (left == right) {
			return;
		}
		int mid = (left + right) / 2;
		mergeSort(nums, left, mid);
		mergeSort(nums, mid + 1, right);
		merge(nums, left, right);
	}

	public static void merge(int[] nums, int left, int right) {
		int[] temp = new int[right - left + 1];
		int i = left;
		int mid = (left + right) / 2;
		int j = mid + 1;
		int k = 0;
		while (i <= mid && j <= right) {
			if (nums[i] < nums[j]) {
				temp[k++] = nums[i++];
			} else {
				temp[k++] = nums[j++];
			}
		}
		while (i <= mid) {
			temp[k++] = nums[i++];
		}
		while (j <= right) {
			temp[k++] = nums[j++];
		}

		for (i = left; i <= right; i++) {
			nums[i] = temp[i - left];
		}
	}
}

总结

冒泡排序、选择排序、直接插入排序,希尔排序、(堆排序、归并排序和快速排序)之间是一种递进的关系,对冒泡排序进行了改进得到选择排序,对选择排序再进一步改进得出了直接插入排序,再对直接插入排序改进得到了希尔排序。希尔排序作为里程碑式的排序算法,起到了承上启下的作用,以希尔排序为界,之前的排序算法时间复杂度都是O(n^2),之后发展的堆排序、归并排序和快速排序时间复杂度都是O(n\log n)。

你可能感兴趣的:(java,算法)