1. 程式人生 > >每日演算法之——常見排序演算法集錦(演算法之發展歷程)

每日演算法之——常見排序演算法集錦(演算法之發展歷程)

常見排序演算法及發展歷程

所謂排序,就是將資料分為有序區和無序區,通過對無序區元素的調整並擴充套件有序區,最後達到所有元素都有序的狀態。

在排序界,常見(或者說常用)的演算法主要有氣泡排序,選擇排序、直接插入排序、希爾排序(其實不太常用)、堆排序、快速排序、歸併排序。一些不太常用但是比較有技巧性的排序有:計數排序、點陣圖排序、基數排序、桶排序、外部排序(一般結合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)。