幾種常用的排序算法的分析及java實現(希爾排序,堆排序,歸併排序,快速排序,選擇排序,插入排序,冒泡排序)

最近決定每天學點數據結構與算法,寫博客來督促自己繼續學習~

以下的每個排序的寫法格式基本按照先介紹基本思想,再描述具體過程,最後是具體代碼。關於複雜度等問題後續更新。如有寫的不嚴謹的地方,歡迎指出,相互交流。


希爾排序

基本思想:將一組數據按照一定的步長分組,進行直接插入排序,然後再縮小步長,再排序,直到步長爲1,再進行一次排序,就得到了有序序列。

以下面一組數據爲例:

{ 8, 19, 2, 5, 7, 10, 12, 16, 18, 20 }

根據wiki百科,我選擇了一種選取步長的方式,即初始取n/2(n爲數據的長度)作爲步長,其後將步長減半,直至步長減爲1.其他步長選取方法可以參見wiki百科。

具體過程:





具體代碼:

	/**
	 * 希爾排序
	 * 
	 * 步長選擇size/2爲並且對步長取半直到步長達到 1
	 * 
	 * @param d
	 *            要排序的數組
	 */
	public static void shellSort(int[] d) {
		int size = d.length;
		int tmp;

		for (int i = size / 2; i > 0; i /= 2) {
			for (int j = i; j < size; j++) {
				int k = j;
				Boolean flag = false;

				while (k - i >= 0) {// 有優化的空間,如果兩個數比較後沒有進行交換,應可以直接跳出循環
					if (d[k] < d[k - i]) {
						tmp = d[k];
						d[k] = d[k - i];
						d[k - i] = tmp;
						flag = true;
					}
					if (flag)
						break;
					k -= i;
				}
			}
		}
	}


堆排序


首先需要知道的知識,二叉樹,已知父節點i,左孩子節點(2 * i + 1),右孩子節點(2 * i + 2);若已知孩子節點,父節點爲((i - 1) / 2)。

最小堆性質是,父節點的值的大小要求小於等於左右孩子節點的值。

建立最小堆,得到的是遞減序列;反之,建立最大堆,得到遞增序列。


基本思想:建立一個最小堆(或最大堆,本文中均以最小堆爲例)。根據最小堆性質可以知道,根節點元素是最小的元素,因此每次刪除這個節點,進行最小堆的調整,一直重複這個過程,直至僅剩最後一個元素,排序完成。


以下面一組數據爲例:

{ 8, 9, 2, 5, 7, 10, 12, 16, 18, 20 }


基本過程:

建立最小堆

根據圖片指示的順序,一個一個個將數組中的元素加入到堆中,並進行調整,最終得到最小堆。


至此省略幾步,將數據一個個加入即可。最終得到的最小堆:


建完最小堆之後,可以清楚看見最小堆的性質,根節點的值一定小於等於左右孩子節點的值。接下來,要做的是將根節點刪除,然後將最後一個元素的值賦給根節點,然後進行一個調整。這個過程的圖就不一一畫了,僅畫第一步,接下來的操作類似。

 

得到一個新的最小堆:


具體代碼:

	/**
	 * 堆排序
	 * 
	 * 因爲使用的是最小堆,所以得到的序列是遞減序列;如果要遞增序列則需要使用最大堆
	 * 
	 * @param d
	 *            要排序的數組
	 * @param n
	 *            數組的長度
	 */
	public static void heapSort(int[] d, int n) {
		int tmp;
		for (int i = n - 1; i > 0; i--) {
			tmp = d[i];
			d[i] = d[0];
			d[0] = tmp;
			heapMakeDown(d, 0, i);
		}
	}

	/**
	 * 建立最小堆
	 * 
	 * @param d
	 *            要建堆的數組
	 * @param n
	 *            數組的長度
	 */
	public static void makeMinHeap(int[] d, int n) {
		for (int i = n / 2 - 1; i >= 0; i--) {
			heapMakeDown(d, i, n);
		}
	}

	/**
	 * 從節點i開始進行調整
	 * 
	 * @param d
	 *            要調整的數組
	 * @param i
	 *            從節點i開始
	 * @param n
	 *            數組的長度
	 */
	public static void heapMakeDown(int[] d, int i, int n) {
		int j, tmp;

		tmp = d[i];
		j = 2 * i + 1;

		while (j < n) {
			if (j + 1 < n && d[j + 1] < d[j]) // 尋找左右孩子中小的那一個
				j++;

			if (tmp <= d[j])// 父節點的值比左右孩子中小的那個還小,則不需要調整
				break;

			d[i] = d[j];
			i = j;
			j = 2 * i + 1;
		}
		d[i] = tmp;
	}


歸併排序


基本思想:歸併排序用了分治的思想。將兩個有序的序列,合併爲一個序列。只需將兩個序列的第一個值比較,將較小的元素添加到合併的序列,刪除這個較小的元素,再繼續比較。直到某一個序列爲空,就可以將另一個序列的元素添加到合併的序列。

具體代碼:

	/**
	 * 合併d[first]-->d[mid],d[mid+1]-->d[last]
	 * 
	 * @param d
	 *            要排序的數組
	 * @param first
	 *            起始下標
	 * @param mid
	 *            中間下標
	 * @param last
	 *            結束下標
	 * @param tmp
	 *            臨時數組
	 */
	public static void merge(int[] d, int first, int mid, int last, int[] tmp) {
		int i = first, j = mid + 1;
		int index = 0;

		while (i <= mid && j <= last) { // 從中選取小的那一個加入新的數組
			if (d[i] < d[j]) {
				tmp[index++] = d[i++];
			} else {
				tmp[index++] = d[j++];
			}
		}

		// 加入d[first]-->d[mid],d[mid+1]-->d[last]中長度更長的數組的剩餘元素

		while (i <= mid) {
			tmp[index++] = d[i++];
		}

		while (j <= last) {
			tmp[index++] = d[j++];
		}

		for (i = 0; i < index; i++) {
			d[first + i] = tmp[i];
		}
	}

	/**
	 * 歸併排序(遞歸)
	 * 
	 * @param d
	 *            要排序的數組
	 * @param first
	 *            起始下標
	 * @param last
	 *            結束下標
	 * @param tmp
	 *            臨時數組,用來存放排好序的元素
	 */
	public static void mergeSort(int[] d, int first, int last, int[] tmp) {
		if (first < last) {
			int mid = (first + last) / 2;
			mergeSort(d, first, mid, tmp);
			mergeSort(d, mid + 1, last, tmp);
			merge(d, first, mid, last, tmp);
		}
	}


快速排序


基本思想:快速排序使用了“分治法”。先從一個序列中找到一個基準元素,將序列分爲兩部分,左部分元素值都小於等於基準元素;右部分元素值都大於基準元素。遞歸的把左右兩部分序列繼續此操作,直至序列元素爲1.


以下面一組數據爲例:

{ 8, 9, 2, 5, 7, 10, 12, 16, 18, 20 }


具體過程:


這一組數據也可以說明快速排序是不穩定的。在序列大多數都有序的情況下,選擇快排不一定是最好的選擇。


具體代碼:

	/**
	 * 快速排序
	 * 
	 * @param d
	 *            要排序的數組
	 * @param first
	 *            開始下標
	 * @param last
	 *            結束下標
	 */
	public static void quickSort(int[] d, int first, int last) {
		if (first >= last)
			return;

		int i = first, j = last;
		int pivot = d[i];// 選取第一個元素作爲基準元素

		while (i < j) {
			while (i < j && d[j] > pivot) { // 從右往左找比基準元素小的元素
				j--;
			}
			if (i < j) {
				d[i++] = d[j];
			}

			while (i < j && d[i] <= pivot) { // 從左往右找比基準元素大的元素
				i++;
			}
			if (i < j) {
				d[j--] = d[i];
			}
		}

		d[i] = pivot;// 這時i = j,將基準元素放在這個位置
		quickSort(d, first, i - 1);
		quickSort(d, i + 1, last);
	}


選擇排序


基本思想:選擇排序就是將序列中未排序的部分中的最小元素,放入到序列的已排序部分的首位;接着在未排序部分繼續尋找最小元素,放入已排序部分的末尾,直到所有元素排序完畢。

以下的每個排序中使用的數據都相同。

具體過程:



具體代碼:

	/**
	 * 選擇排序
	 * 
	 * @param d
	 *            要排序的數組
	 * @param n
	 *            數組的長度
	 */
	public static void selectSort(int[] d, int n) {
		int tmp;
		Boolean flag = false;

		for (int i = 0; i < n - 1; i++) {
			for (int j = i + 1; j < n; j++) { // 記住索引與直接交換的區別?
				if (d[i] > d[j]) {
					tmp = d[i];
					d[i] = d[j];
					d[j] = tmp;
					flag = true;
				}
				flag = false;
			}
			if (flag)
				break;
		}
	}


插入排序


基本思想:插入排序是對每一個未排序元素,在已排序序列中從後往前查找,發現合適的位置將此元素插入到已排序序列,直到掃描完所有的未排序元素。


具體過程:



具體代碼:

	/**
	 * 插入排序
	 * 
	 * @param d
	 *            要排序的數組
	 * @param n
	 *            數組的長度
	 */
	public static void insertionSort(int[] d, int n) {
		int tmp;

		for (int i = 1; i < n; i++) {
			tmp = d[i];
			int j = i - 1;

			while (j >= 0 && d[j] > tmp) {
				d[j + 1] = d[j];
				j--;
			}
			d[j + 1] = tmp;
		}
	}


冒泡排序


基本思想:比較相鄰的兩個元素,如果前一個元素比後一個元素大,則交換;記錄下每次交換時的下標,下一次交換的時候僅需要從開始的元素到上輪交換的最後一個下標即可,優化了簡單冒泡排序。


具體過程:



具體代碼:

/**
	 * 冒泡排序
	 * 
	 * @param d
	 *            要排序的數組
	 * @param n
	 *            數組長度
	 */
	public static void bubbleSort(int[] d, int n) {
		int flag = n;
		int j, tmp;

		while (flag > 0) {
			j = flag;
			flag = 0;
			for (int i = 1; i < j; i++) {
				if (d[i] < d[i - 1]) {
					tmp = d[i - 1];
					d[i - 1] = d[i];
					d[i] = tmp;

					flag = i;// 記住有改變的下標,下一次只需要在0-->flag之間排序即可
				}
			}
		}
	}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章