5. 歸併排序
兩個有序陣列合並並不難, 但是歸併的思想確實是這個, 但是如何分, 分到何時呢 ?
這個名字含義就是分為歸 和 並
兩個階段執行
先說並吧, 並要求是兩個已經排序好了的陣列(兩個連續陣列是位置上也連續) , 比如1,2,3,4
, 連續陣列1,2
和3,4
, 不能是 1,2
,4
進行排序 ,
對於兩個已經排序好了的資料, 好處是一次遍歷便可以兩個陣列合併成一個有序的陣列
然後再說 歸吧, 歸的思想就是 , 4,3,2,1
, 先分 4,3
和2,1
, 將4,3
繼續分 4
,3
,此時這就是倆位置有序的陣列, 將他倆陣列進行並 , 就成了 3,4
, 然後2,1
分為2
,1
, 此時再將這倆陣列排序,此時是1,2
, 然後將3,4
和1,2
進行並排序, 此時就是1,2,3,4
, OK了
/**
* 歸併排序
*
*
* @author: <a href='mailto:fanhaodong516@qq.com'>Anthony</a>
*/
public class MergeSort {
public static void main(String[] args) {
int[] arr_1000 = Common.generate_Arr_1000();
splitAndMergerSort(arr_1000, 0, arr_1000.length - 1);
Common.showArr(arr_1000);
}
/**
* 將[1,2,3]陣列分割成直到 [1][2][3]這種 ,遞迴實現
*
* @param arr 陣列
* @param start 開始位置 ,索引從0開始
* @param end 結束位置 , 索引0開始
*/
private static void splitAndMergerSort(int[] arr, int start, int end) {
if (start == end) return;
int middle = (end + start) >> 1;
splitAndMergerSort(arr, start, middle);
splitAndMergerSort(arr, middle + 1, end);
merge(arr, start, end, middle + 1);
}
/**
* @param arr 資料
* @param start 索引從0開始 , 陣列開始位置
* @param end 索引從0開始 , 陣列結束位置
* @param delimiter 切割位置 : [1,2,4,5] 引數(arr,0 , 3 , 2) , 分隔符是 (star+end)/2+1
*/
private static void merge(int[] arr, int start, int end, int delimiter) {
if (start == end) return;
// 1. 初始化陣列
int llen = delimiter - start;
int rlen = (end - delimiter) + 1;
int[] left = new int[llen];
int[] right = new int[rlen];
//2. 將分割陣列 , 填充資料
System.arraycopy(arr, start, left, 0, llen);
System.arraycopy(arr, delimiter, right, 0, rlen);
// 3.歸併步驟
int l = 0;
int r = 0;
int len = end + 1;
for (int i = start; i < len; ++i) {
if (l < llen && r < rlen) {
if (left[l] < right[r]) {
arr[i] = left[l];
l++;
} else {
arr[i] = right[r];
r++;
}
} else {
if (l < llen) {
arr[i] = left[l];
l++;
}
if (r < rlen) {
arr[i] = right[r];
r++;
}
}
}
}
// 第二種合併方式
private static void merge(int[] left_arr, int[] right_arr) {
int lL = left_arr.length;
int rL = right_arr.length;
int resultL = lL + rL;
int[] result = new int[resultL];
int l = 0;
int r = 0;
int w = 0;
while (w < resultL) {
if (l < lL && r < rL) {
if (left_arr[l] < right_arr[r]) {
result[w] = left_arr[l];
l++;
w++;
} else {
result[w] = right_arr[r];
r++;
w++;
}
} else {
if (l < lL) {
System.arraycopy(left_arr, l, result, w, lL - l);
break;
}
if (r < rL) {
System.arraycopy(right_arr, r, result, w, rL - r);
break;
}
}
}
Common.showArr(result);
}
}
複製程式碼
6. 堆排序
陣列他可以利用索引關係構建出一個完全二叉樹 , 所以利用這個特性很好的組成堆,
堆排序 分為兩個階段
一階段 : 堆化 - > 將資料轉換成 大頂堆或者小頂堆 , 就是根節點資料大於子葉資料 , 就是大頂堆.
二階段 : 利用大頂堆或者小頂堆進行排序 , 就是將堆頂和最後一個資料進行交換, 因為堆頂是最大值或者最小值, 然後堆化, 重複操作 ,
/**
* 堆排序
*
* @author: <a href='mailto:fanhaodong516@qq.com'>Anthony</a>
*/
public class HeapSort {
public static void main(String[] args) {
int[] arr_1000 = Common.generate_Arr_1000();
heap_sort(arr_1000, arr_1000.length);
Common.showArr(arr_1000);
}
/**
* 堆排序
*
* @param tree
* @param n
*/
static void heap_sort(int[] tree, int n) {
// 1. 構建一個堆
build_heap(tree, n);
// 2.
// 堆頂和最後一個節點做交換 , 但是我們需要在陣列上擷取 , 所以就是每次
for (int i = n - 1; i >= 0; i--) {
// 交換節點
swap(tree, i, 0);
// 第0個位置 開始堆重新排序
heapify(tree, i, 0);
}
}
/**
* 構建一個 大頂堆
*
* @param tree
* @param n
*/
static void build_heap(int[] tree, int n) {
// 最後一個節點
int last_node = n - 1;
// 開始遍歷的位置是 : 最後一個堆的堆頂 , 意思就是 , 整個樹中最小的一個堆 , 其實就是最後一個節點的父節點
int parent = (last_node - 1) / 2;
// 遞減向上遍歷
for (int i = parent; i >= 0; i--) {
heapify(tree, n, i);
}
}
/**
* @param tree 代表一棵樹
* @param n 代表多少個節點
* @param i 對哪個節點進行 heapify
*/
static void heapify(int[] tree, int n, int i) {
// 如果當前值 大於 n 直接返回了 ,一般不會出現這種問題 .....
if (i >= n) {
return;
}
// 子節點
int c1 = 2 * i + 1;
int c2 = 2 * i + 2;
// 假設最大的節點 為 i (父節點)
int max = i;
// 如果大於 賦值給 max
if (c1 < n && tree[c1] > tree[max]) {
max = c1;
}
// 如果大於 賦值給 max
if (c2 < n && tree[c2] > tree[max]) {
max = c2;
}
// 如果i所在的就是最大值我們沒必要去做交換
if (max != i) {
// 交換最大值 和 父節點 的位置
swap(tree, max, i);
// 交換完以後 , 此時的max其實就是 i原來的數 ,就是最小的數字 ,所以需要遞迴遍歷
heapify(tree, n, max);
}
}
static void swap(int[] tree, int max, int i) {
int temp = tree[max];
tree[max] = tree[i];
tree[i] = temp;
}
}
複製程式碼
7. 桶排序
其實我認為他是 區間排序, 舉個例子 [1,2,3,4,5,6]
, 將他放入6個區間內 , (0,1] ,(1,2] , 依次到最後 , 那麼這個放置過程完全是可以通過計算得到的. 所以一次遍歷便可以完成排序 , 如果區間內部排序, 可以選擇其他排序方式.
package com.sort;
import java.util.Arrays;
/**
* 桶排序 ,其實就是區間排序 1,2,3,4,5,6 ,我們分成 0-3, 3-6的區間(首先區間是有順序的)
* , 1,2,3 進去區間一, 4,5,6進去區間二 , 然後區間內排序, 此時就構建了新的陣列
*
*
* @author: <a href='mailto:fanhaodong516@qq.com'>Anthony</a>
*/
public class BucketSort {
public static void main(String[] args) {
int[] arr_1000 = Common.generate_Arr_1000();
bucketSort(arr_1000, 10);
Common.showArr(arr_1000);
}
/**
* @param arr 陣列
* @param bucketCount 桶的個數
*/
public static void bucketSort(int[] arr, int bucketCount) {
int len = arr.length;
if (len <= 1 || bucketCount <= 0) {
return;
}
// 遍歷一次找到最大值 最小值
int max = arr[0], min = arr[0];
for (int i : arr) {
if (i > max) {
max = i;
}
if (i < min) {
min = i;
}
}
/**
* 劃分割槽間 , 比如 5 - 11 ,此時我們需要 / 桶數量 (假如 是 2), 如果我們不+1 , 6 / 2 = 3 ,那麼 (11-5)/3=2 , 此時座標2這個桶
*
* 所以區間需要+1 操作 , 所以上面就是 7/2=3.5=4 , (11-5)/4=1
*/
int range = ((max - min + 1) % bucketCount) == 0 ? (max - min + 1) / bucketCount : (max - min + 1) / bucketCount + 1;
// 建立桶 ,是一個二維陣列
int[][] bucket = new int[bucketCount][];
for (int i : arr) {
bucket[(i - min) / range] = arrAppend(bucket[(i - min) / range], i);
}
for (int[] ints : bucket) {
sort(ints);
}
int count = 0;
for (int[] ints : bucket) {
if (ints != null) {
for (int anInt : ints) {
arr[count++] = anInt;
}
}
}
}
/**
* 陣列拷貝
*
* @param arr
* @param value
* @return
*/
private static int[] arrAppend(int[] arr, int value) {
//陣列如果為空, 新建一個陣列,
if (arr == null) {
arr = new int[0];
}
// 陣列拷貝 , 其實就是長度+1
arr = Arrays.copyOf(arr, arr.length + 1);
// 將值複製
arr[arr.length - 1] = value;
//返回
return arr;
}
private static void sort(int[] arr) {
/**
* 空 或者 0 / 1 都直接返回
*/
if (null == arr || arr.length <= 1) {
return;
}
// 2 3 1
for (int index = 1; index < arr.length; index++) {
// 當前位置 , 開始必須從第二個開始
int temp = arr[index];
// 左邊位置
int left = index - 1;
// 移動座標其實就是 ...
while (left >= 0 && arr[left] > temp) {
// 互換位置
arr[left + 1] = arr[left];
// 向前移動
left--;
}
// 最後儲存資料
arr[left + 1] = temp;
}
}
}
複製程式碼
8. 基數排序
我沒有寫, 他和桶排序類似 , 一次比較個位數 , 十位數 , 百位數資料, 分成10個桶 , 對號入座, 第一遍比較個位數, 第二遍比較十位數, 第三遍比較百位數 .