Java常用的數組排序算法(面試寶典)


這段時間有些忙,今天空閑出來給大家分享下Java中常用的數組排序算,有冒泡排序、快速排序、選擇排序、插入排序、希爾算法、並歸排序算法、堆排序算法,以上排序算法中,前面幾種相對后面的比較容易理解一些。下面為大家一一介紹,寫的不好,大佬繞過。

1、冒泡排序

冒泡排序是一種簡單的排序算法。它重復地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重復地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端。

具體實現如下:

 /*
 * 冒泡排序
 * 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。  
 * 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對。在這一點,最后的元素應該會是最大的數。  
 * 針對所有的元素重復以上的步驟,除了最后一個。
 * 持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較。 
 * @param numbers 需要排序的整型數組
 */
public static void bubbleSort(int[] numbers){
    int temp = 0;
    int size = numbers.length;
    for(int i = 0 ; i < size-1; i ++){
      for(int j = 0 ;j < size-1-i ; j++){
        if(numbers[j] > numbers[j+1]){  //交換兩數位置
        temp = numbers[j];
        numbers[j] = numbers[j+1];
        numbers[j+1] = temp;
        }
      }
    }
}

2、快速排序

快速排序的基本思想是通過一趟排序將待排序記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分關鍵字小,則分別對這兩部分繼續進行排序,直到整個序列有序。把整個序列看做一個數組,把第零個位置看做中軸,和最后一個比,如果比它小交換,比它大不做任何處理;交換了以后再和小的那端比,比它小不交換,比他大交換。這樣循環往復,一趟排序完成,左邊就是比中軸小的,右邊就是比中軸大的,然后再用分治法,分別對這兩個獨立的數組進行排序。

具體實現如下:

1.查找中軸(最低位作為中軸)所在位置:

/**
	 * 查找出中軸(默認是最低位low)的在numbers數組排序后所在位置
	 * @param numbers 帶查找數組
	 * @param low 開始位置
	 * @param high 結束位置
	 * @return 中軸所在位置
	 */
	public static int getMiddle(int[] numbers, int low, int high) {
		int temp = numbers[low]; // 數組的第一個作為中軸
		while (low < high) {
			while (low < high && numbers[high] >= temp) {
				high--;
			}
			numbers[low] = numbers[high];// 比中軸小的記錄移到低端
			while (low < high && numbers[low] < temp) {
				low++;
			}
			numbers[high] = numbers[low]; // 比中軸大的記錄移到高端
		}
		numbers[low] = temp; // 中軸記錄到尾
		return low; // 返回中軸的位置
	}

2、遞歸形式的分治排序算法:

	/**
	 * 
	 * @param numbers  帶排序數組
	 * @param low 開始位置
	 * @param high 結束位置
	 */
	public static void quickSort(int[] numbers, int low, int high) {
		if (low < high) {
			int middle = getMiddle(numbers, low, high); // 將numbers數組進行一分為二
			quickSort(numbers, low, middle - 1); // 對低字段表進行遞歸排序
			quickSort(numbers, middle + 1, high); // 對高字段表進行遞歸排序
		}
	}

3、快速排序提供方法調用:

/**
 * 快速排序
 * @param numbers 帶排序數組
 */
public static void quick(int[] numbers) {
	if (numbers.length > 0) { // 查看數組是否為空
		quickSort(numbers, 0, numbers.length - 1);
	}
}

快速排序是通常被認為在同數量級(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按關鍵碼有序或基本有序時,快排序反而蛻化為冒泡排序。為改進之,通常以“三者取中法”來選取基准記錄,即將排序區間的兩個端點與中點三個記錄關鍵碼居中的調整為支點記錄。快速排序是一個不穩定的排序方法。

4、選擇排序

在要排序的一組數中,選出最小的一個數與第一個位置的數交換;然后在剩下的數當中再找最小的與第二個位置的數交換,如此循環到倒數第二個數和最后一個數比較為止。

    /**
	 * 選擇排序算法 在未排序序列中找到最小元素,存放到排序序列的起始位置 再從剩余未排序元素中繼續尋找最小元素,然后放到排序序列末尾。
	 * 以此類推,直到所有元素均排序完畢。
	 * @param numbers
	 */
	public static void selectSort(int[] numbers) {
		int size = numbers.length; // 數組長度
		int temp = 0; // 中間變量

		for (int i = 0; i < size; i++) {
			int k = i; // 待確定的位置
			// 選擇出應該在第i個位置的數
			for (int j = size - 1; j > i; j--) {
				if (numbers[j] < numbers[k]) {
					k = j;
				}
			}
			// 交換兩個數
			temp = numbers[i];
			numbers[i] = numbers[k];
			numbers[k] = temp;
		}
	}

5、插入排序

每步將一個待排序的記錄,按其順序碼大小插入到前面已經排序的字序列的合適位置(從后向前找到合適位置后),直到全部插入排序完為止。

    /**
	 * 插入排序
	 * 從第一個元素開始,該元素可以認為已經被排序 取出下一個元素,在已經排序的元素序列中從后向前掃描 如果該元素(已排序)大於新元素,將該元素移到下一位置
	 * 重復步驟3,直到找到已排序的元素小於或者等於新元素的位置 將新元素插入到該位置中 重復步驟2
	 * @param numbers 待排序數組
	 */
	public static void insertSort(int[] numbers) {
		int size = numbers.length;
		int temp = 0;
		int j = 0;

		for (int i = 0; i < size; i++) {
			temp = numbers[i];
			// 假如temp比前面的值小,則將前面的值后移
			for (j = i; j > 0 && temp < numbers[j - 1]; j--) {
				numbers[j] = numbers[j - 1];
			}
			numbers[j] = temp;
		}
	}

6、希爾算法

先將整個待排序的記錄序列分割成為若干子序列分別進行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進行依次直接插入排序。

1、選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
2、按增量序列個數k,對序列進行k 趟排序;
3、每趟排序,根據對應的增量ti,將待排序列分割成若干長度為m 的子序列,分別對各子表進行直接插入排序。僅增量因子為1 時,整個序列作為一個表來處理,表長度即為整個序列的長度。

    /**
	 * 希爾排序的原理:根據需求,如果你想要結果從大到小排列,它會首先將數組進行分組,然后將較大值移到前面,較小值
	 * 移到后面,最后將整個數組進行插入排序,這樣比起一開始就用插入排序減少了數據交換和移動的次數,可以說希爾排序是加強 版的插入排序 拿數組5, 2, 8,
	 * 9, 1, 3,4來說,數組長度為7,當increment為3時,數組分為兩個序列
	 * 5,2,8和9,1,3,4,第一次排序,9和5比較,1和2比較,3和8比較,4和比其下標值小increment的數組值相比較
	 * 此例子是按照從大到小排列,所以大的會排在前面,第一次排序后數組為9, 2, 8, 5, 1, 3,4
	 * 第一次后increment的值變為3/2=1,此時對數組進行插入排序, 實現數組從大到小排
	 */

	public static void shellSort(int[] data) {
		int j = 0;
		int temp = 0;
		// 每次將步長縮短為原來的一半
		for (int increment = data.length / 2; increment > 0; increment /= 2) {
			for (int i = increment; i < data.length; i++) {
				temp = data[i];
				for (j = i; j >= increment; j -= increment) {
					if (temp > data[j - increment])// 如想從小到大排只需修改這里
					{
						data[j] = data[j - increment];
					} else {
						break;
					}

				}
				data[j] = temp;
			}

		}
	}

7、歸並排序算法

歸並(Merge)排序法是將兩個(或兩個以上)有序表合並成一個新的有序表,即把待排序序列分為若干個子序列,每個子序列是有序的。然后再把有序子序列合並為整體有序序列。

合並方法:
設r[i…n]由兩個有序子表r[i…m]和r[m+1…n]組成,兩個子表長度分別為n-i +1、n-m。
1、j=m+1;k=i;i=i; //置兩個子表的起始下標及輔助數組的起始下標
2、若i>m 或j>n,轉⑷ //其中一個子表已合並完,比較選取結束
3、//選取r[i]和r[j]較小的存入輔助數組rf
        如果r[i]<r[j],rf[k]=r[i]; i++; k++; 轉⑵
        否則,rf[k]=r[j]; j++; k++; 轉⑵
4、//將尚未處理完的子表中元素存入rf
        如果i<=m,將r[i…m]存入rf[k…n] //前一子表非空
        如果j<=n ,  將r[j…n] 存入rf[k…n] //后一子表非空
5、合並結束。


    /**
	 * 歸並排序 簡介:將兩個(或兩個以上)有序表合並成一個新的有序表 即把待排序序列分為若干個子序列,每個子序列是有序的。然后再把有序子序列合並為整體有序序列
	 * 時間復雜度為O(nlogn) 穩定排序方式
	 * 
	 * @param nums
	 *            待排序數組
	 * @return 輸出有序數組
	 */
	public static int[] sort(int[] nums, int low, int high) {
		int mid = (low + high) / 2;
		if (low < high) {
			// 左邊
			sort(nums, low, mid);
			// 右邊
			sort(nums, mid + 1, high);
			// 左右歸並
			merge(nums, low, mid, high);
		}
		return nums;
	}

	/**
	 * 將數組中low到high位置的數進行排序
	 * 
	 * @param nums
	 *            待排序數組
	 * @param low
	 *            待排的開始位置
	 * @param mid
	 *            待排中間位置
	 * @param high
	 *            待排結束位置
	 */
	public static void merge(int[] nums, int low, int mid, int high) {
		int[] temp = new int[high - low + 1];
		int i = low;// 左指針
		int j = mid + 1;// 右指針
		int k = 0;

		// 把較小的數先移到新數組中
		while (i <= mid && j <= high) {
			if (nums[i] < nums[j]) {
				temp[k++] = nums[i++];
			} else {
				temp[k++] = nums[j++];
			}
		}

		// 把左邊剩余的數移入數組
		while (i <= mid) {
			temp[k++] = nums[i++];
		}

		// 把右邊邊剩余的數移入數組
		while (j <= high) {
			temp[k++] = nums[j++];
		}

		// 把新數組中的數覆蓋nums數組
		for (int k2 = 0; k2 < temp.length; k2++) {
			nums[k2 + low] = temp[k2];
		}
	}

8、堆排序

堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。
  堆的定義下:具有n個元素的序列 (h1,h2,...,hn),當且僅當滿足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,...,n/2)時稱之為堆。在這里只討論滿足前者條件的堆。由堆的定義可以看出,堆頂元素(即第一個元素)必為最大項(大頂堆)。完全二 叉樹可以很直觀地表示堆的結構。堆頂為根,其它為左子樹、右子樹。
  思想:初始時把要排序的數的序列看作是一棵順序存儲的二叉樹,調整它們的存儲序,使之成為一個 堆,這時堆的根節點的數最大。然后將根節點與堆的最后一個節點交換。然后對前面(n-1)個數重新調整使之成為堆。依此類推,直到只有兩個節點的堆,並對 它們作交換,最后得到有n個節點的有序序列。從算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最后一個元素交換位置。所以堆排序有兩個函數組成。一是建堆的滲透函數,二是反復調用滲透函數實現排序的函數。


	public static void main() {
		int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64 };
		int arrayLength = a.length;
		// 循環建堆
		for (int i = 0; i < arrayLength - 1; i++) {
			// 建堆
			buildMaxHeap(a, arrayLength - 1 - i);
			// 交換堆頂和最后一個元素
			swap(a, 0, arrayLength - 1 - i);
			System.out.println(Arrays.toString(a));
		}
	}

	// 對data數組從0到lastIndex建大頂堆
	public static void buildMaxHeap(int[] data, int lastIndex) {
		// 從lastIndex處節點(最后一個節點)的父節點開始
		for (int i = (lastIndex - 1) / 2; i >= 0; i--) {
			// k保存正在判斷的節點
			int k = i;
			// 如果當前k節點的子節點存在
			while (k * 2 + 1 <= lastIndex) {
				// k節點的左子節點的索引
				int biggerIndex = 2 * k + 1;
				// 如果biggerIndex小於lastIndex,即biggerIndex+1代表的k節點的右子節點存在
				if (biggerIndex < lastIndex) {
					// 若果右子節點的值較大
					if (data[biggerIndex] < data[biggerIndex + 1]) {
						// biggerIndex總是記錄較大子節點的索引
						biggerIndex++;
					}
				}
				// 如果k節點的值小於其較大的子節點的值
				if (data[k] < data[biggerIndex]) {
					// 交換他們
					swap(data, k, biggerIndex);
					// 將biggerIndex賦予k,開始while循環的下一次循環,重新保證k節點的值大於其左右子節點的值
					k = biggerIndex;
				} else {
					break;
				}
			}
		}
	}

	// 交換
	private static void swap(int[] data, int i, int j) {
		int tmp = data[i];
		data[i] = data[j];
		data[j] = tmp;
	}

以上就是全部算法的代碼實現,有些參考了互聯網上的實現思路,有問題可以在下面評論,討論技術可以私聊我。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM