最近在準備面試,回顧了一下之前學習的幾種排序算法,並參考材料嘗試實現。現在此記錄一下,以後忘了可以回顧。
直接貼上代碼(有許多值得優化的地方)。
package hpp.sort;
/**
* Created by hpp on 2017/8/4.
*/
public class SortTest {
public static void main(String[] args) {
int[] array = {1,2,5,4,7,9,0,8,3,6};
// insertSort(array,array.length);
// insertSort2(array,array.length);
// shellSort(array);
// bubbleSort(array);
// quickSort(array, 0, array.length - 1);
// selectSort(array);
// heapSort(array);
mergeSort(array);
for(int item:array){
System.out.print(item + " ");
}
}
/**
* 直接插入排序,空間複雜度O(1),時間複雜度O(n²)
* 適用於基本有序的排序表和數據量不大的排序表
*/
public static void insertSort(int[] array, int n){
if(array==null)
return;
int i,j,temp;
for(i=1;i<n;i++){
if(array[i]<array[i-1]){//減少比較移動的次數
temp = array[i];
j = i - 1;
while (j>=0){
if (temp<array[j]) {
array[j + 1] = array[j];
} else {
break;
}
j--;
}
array[j+1] = temp;
}
}
}
/**
* 折半插入排序,空間複雜度O(1),時間複雜度O(n²)
* @param array
* @param n
*/
public static void insertSort2(int[] array, int n){
if(array==null)
return;;
int i,j,temp,low,high,mid;
for(i=1;i<n;i++){
temp = array[i];
low = 0;
high = i-1;
while (low<=high){//找到要插入的位置
mid = (low + high)/2;
if(array[mid]>temp){
high = mid - 1;
}else {
low = mid + 1;
}
}
for(j = i-1;j>=high+1;--j){
array[j+1] = array[j];
}
array[high+1] = temp;
}
}
/**
* 希爾排序
* @param arr
*/
private static void shellSort(int[] arr) {
int j;
int len = arr.length;
for(int val=len>>1; val>0; val>>=1) {
//下面是對本次的所有分組做直接插入排序
for(int i=val; i<len; i++) {
int temp = arr[i];
/*
* 爲什麼每次都用temp比較呢?
* 因爲直接插入就是找到temp的合適位置。
* 爲什麼temp<arr[j-val]這個條件可以放在for內呢?
* 因爲原來的組內數據已經有序,找到位置就停止便是。
* 不甚理解的去看直接插入排序吧。
*/
for(j=i; j>=val&&temp<arr[j-val]; j-=val) {
/*
* 爲什麼是arr[j-val]不是arr[j]呢?
* 因爲j=i開始的,而且條件是j>=val&&temp<arr[j-val]
*/
arr[j] = arr[j-val];
}
/*
* 注意不是arr[i] = temp
* 直接插入排序也是這樣的。
* 爲什麼呢?
* 因爲j是位置,i是待插入元素
*/
arr[j] = temp;
}
}
}
/**
* 冒泡排序,空間複雜度O(1),時間複雜度O(n²),穩定
* @param array
*/
private static void bubbleSort(int[] array){
if (array==null)
return;
int length = array.length-1;
for(int i=0;i<length;i++){
for (int j=length;j>i;j--){
if (array[j-1]>array[j]){
int tmp = array[j];
array[j] = array[j-1];
array[j-1] = tmp;
}
}
}
}
/**
* 快排,空間複雜度最壞O(n),平均O(logn),時間複雜度最壞情況下O(n²),平均情況下O(nlogn),不穩定
*/
public static void quickSort(int[] array,int left,int right){
if (array==null)
return;
if (left<right){
int pivot = partition(array,left,right);
quickSort(array,left,pivot-1);
quickSort(array,pivot+1,right);
}
}
//快排一次劃分
private static int partition(int[] array,int left,int right){
int tmp = array[left];
while (left<right){
while (left<right && array[right]>=tmp)// 從右向左找小於tmp的數來填array[left]
right--;
if (left<right)
array[left++] = array[right];
while (left<right && array[left]<tmp)// 從左向右找小於tmp的數來填array[right]
left++;
if (left<right)
array[right--] = array[left];
}
array[left] = tmp;//此時的left相當於pivot
return left;
}
/**
* 簡單選擇排序,空間複雜度O(1),時間複雜度O(n²),不穩定
*/
public static void selectSort(int[] array){
if (array==null)
return;
int length = array.length;
for(int i=0;i<length;i++){
int min = i;//記錄最小元素位置
for(int j=i+1;j<length;j++){
if(array[j]<array[min]){//更新最小元素位置
min = j;
}
}
if (min!=i){//把最小元素和i位置上的元素交換
int tmp = array[i];
array[i] = array[min];
array[min] = tmp;
}
}
}
/**
* 堆排序:空間複雜度O(1),時間複雜度O(nlogn),不穩定
*/
public static void heapSort(int[] data) {
buildMaxHeap(data);// 構建最大堆
for (int i = data.length; i > 1; i--) {// 循環,每次把根節點和最後一個節點調換位置
int tmp = data[0];
data[0] = data[i - 1];
data[i - 1] = tmp;
maxHeapify(data, 1, i - 1);// 堆的長度減少1,排除置換到最後位置的根節點
}
}
// 根據輸入數組構建一個最大堆
private static void buildMaxHeap(int[] array) {
for (int i = array.length / 2; i > 0; i--) {
maxHeapify(array, i, array.length);
}
}
//堆調整,使其生成最大堆
private static void maxHeapify(int[] data, int parentNodeIndex, int heapSize) {
// 左子節點索引
int leftChildNodeIndex = parentNodeIndex * 2;
// 右子節點索引
int rightChildNodeIndex = parentNodeIndex * 2 + 1;
// 最大節點索引
int largestNodeIndex = parentNodeIndex;
// 如果左子節點大於父節點,則將左子節點作爲最大節點
if (leftChildNodeIndex <= heapSize && data[leftChildNodeIndex - 1]>data[parentNodeIndex - 1]) {
largestNodeIndex = leftChildNodeIndex;
}
// 如果右子節點比最大節點還大,那麼最大節點應該是右子節點
if (rightChildNodeIndex <= heapSize && data[rightChildNodeIndex - 1]>data[largestNodeIndex - 1]) {
largestNodeIndex = rightChildNodeIndex;
}
// 最後,如果最大節點和父節點不一致,則交換他們的值
if (largestNodeIndex != parentNodeIndex) {
int tmp = data[parentNodeIndex - 1];
data[parentNodeIndex - 1] = data[largestNodeIndex - 1];
data[largestNodeIndex - 1] = tmp;
// 交換完父節點和子節點的值,對換了值的子節點檢查是否符合最大堆的特性
maxHeapify(data, largestNodeIndex, heapSize);
}
}
/**
* 2-路歸併排序,空間複雜度O(n),時間複雜度O(nlogn)
*/
public static void mergeSort(int[] data) {
sort(data, 0, data.length - 1);
}
public static void sort(int[] data, int left, int right) {
if (left >= right)
return;
// 找出中間索引
int center = (left + right) / 2;
// 對左邊數組進行遞歸
sort(data, left, center);
// 對右邊數組進行遞歸
sort(data, center + 1, right);
// 合併
merge(data, left, center, right);
}
/**
* 將兩個數組進行歸併,歸併前面2個數組已有序,歸併後依然有序
* @param data 數組對象
* @param left 左數組的第一個元素的索引
* @param center 左數組的最後一個元素的索引,center+1是右數組第一個元素的索引
* @param right 右數組最後一個元素的索引
*/
public static void merge(int[] data, int left, int center, int right) {
// 臨時數組
int[] tmpArr = new int[data.length];
// 右數組第一個元素索引
int mid = center + 1;
// third 記錄臨時數組的索引
int third = left;
// 緩存左數組第一個元素的索引
int tmp = left;
while (left <= center && mid <= right) {
// 從兩個數組中取出最小的放入臨時數組
if (data[left] <= data[mid]) {
tmpArr[third++] = data[left++];
} else {
tmpArr[third++] = data[mid++];
}
}
// 剩餘部分依次放入臨時數組(實際上兩個while只會執行其中一個)
while (mid <= right) {
tmpArr[third++] = data[mid++];
}
while (left <= center) {
tmpArr[third++] = data[left++];
}
// 將臨時數組中的內容拷貝回原數組中
// (原left-right範圍的內容被複制回原數組)
while (tmp <= right) {
data[tmp] = tmpArr[tmp++];
}
}
}