歸并排序是采用分治算法,即將一個(gè)大問題切分成一些小問題然后遞歸求解。歸并排序的圖解如下:
分的過程簡(jiǎn)單,就是將數(shù)組拆開來,拆到每組只有一個(gè)元素為止。治的過程是怎么排序的呢?以最后一次治為例,即將4 5 7 8
和1 2 3 6
合并成最終的有序序列為例,看看如何實(shí)現(xiàn)。
tempArr
,長(zhǎng)度為要進(jìn)行“治”的兩個(gè)數(shù)組長(zhǎng)度之和;i
指向4
,即第一個(gè)數(shù)組的最左邊,j
指向1
,即第二個(gè)數(shù)組的最左邊;4
比1
大,那么就將1
存入tempArr
,同時(shí)指針j
后移;i
和j
所指元素的大小,發(fā)現(xiàn)2
比4
小,將2
存入tempArr
,同時(shí)j
繼續(xù)后移;3
存入tempArr
,j
繼續(xù)后移;6
比4
大,就將4
存入tempArr
,同時(shí)i
后移;5
比6
小,將5
存入tempArr
,i
繼續(xù)后移;7
比6
大,將6
存入tempArr
,此時(shí)j
已經(jīng)處于最后了,不能后移了;i
所指的剩余元素都存入tempArr
,這個(gè)tempArr
就是有序的了。1. 第一種方式:
這種方式很容易懂,我們先不是要拆分?jǐn)?shù)組嗎?那就拆唄,拆到什么時(shí)候?yàn)橹鼓??拆出來的?shù)組只有一個(gè)元素了那就不用拆了。
public static int[] sort(int[] arr) {
if (arr == null || arr.length == 1) {
return arr;
}
// 拆分?jǐn)?shù)組
int mid = arr.length / 2; // 中間指針,利用該指針將數(shù)組拆分
int[] left = Arrays.copyOfRange(arr, 0, mid);
int[] right = Arrays.copyOfRange(arr, mid, arr.length);
// 調(diào)用合并方法,因?yàn)閘eft和right可能可以再拆分,所以傳進(jìn)去的left和right再遞歸調(diào)用當(dāng)前方法
return merge(sort(left), sort(right));
}
public static int[] merge(int[] left, int[] right) {
// 定義臨時(shí)數(shù)組
int[] tempArr = new int[left.length + right.length];
// 定義兩個(gè)指針
int i = 0; // left的指針
int j = 0; // right的指針
// 進(jìn)行合并操作
for(int index=0; index < tempArr.length; index ++) {
// 如果left的遍歷完了,那么將right中的剩余元素全部依次放入tempArr中
if (i >= left.length) {
tempArr[index] = right[j];
j++; // 指針后移
} else if(j >= right.length) {
// 如果right遍歷完了,那么將left中剩余的元素全部依次放入tempArr中
tempArr[index] = left[i];
i++; // 指針后移
} else if (left[i] < right[j]) {
// 如果i所指的數(shù)更小,就將該數(shù)存入tempArr
tempArr[index] = left[i];
i++; // 指針后移
} else {
// 如果j所指的數(shù)大于等于i所指的數(shù),就將j所指的數(shù)存入tempArr
tempArr[index] = right[j];
j++; // 指針后移
}
}
return tempArr;
}
沒錯(cuò),這樣就搞定了,這就是完全按照上面案例分析來做的,不過缺點(diǎn)就是,拆分的時(shí)候,是真正的拆除兩個(gè)數(shù)組來了,會(huì)浪費(fèi)空間,優(yōu)點(diǎn)就是容易理解。
2. 第二種方式:
第二種方式就是不真正的將數(shù)組拆成兩部分,而是通過一個(gè)中間索引mid
,將數(shù)組標(biāo)識(shí)成兩部分。這樣就不需要真正的拆分,不會(huì)浪費(fèi)空間,但是代碼相對(duì)來說更難理解。
left
和mid
構(gòu)成左邊的數(shù)組,mid+1
和right
構(gòu)成右邊的數(shù)組,只要理解了這一點(diǎn),下面的代碼就容易理解了。public static void merge(int[] arr, int left, int mid, int right) {
// 定義臨時(shí)數(shù)組
int[] tempArr = new int[right - left + 1];
int i = left; // 左邊數(shù)組的指針
int j = mid + 1; // 右邊數(shù)組的指針
int k = 0; // 臨時(shí)數(shù)組的指針
// 當(dāng)左邊數(shù)組和右邊數(shù)組都還沒遍歷完的時(shí)候
while(i <= mid && j <= right) {
// 如果i所指的元素更小,就其放入tempArr,同時(shí)i后移
if (arr[i] <= arr[j]) {
tempArr[k++] = arr[i++];
} else {
// 如果j所指的更小,就將其放入tempArr,同時(shí)j后移
tempArr[k++] = arr[j++];
}
}
// 如果左邊的數(shù)組還沒遍歷完,就將左邊數(shù)組剩余元素依次放入tempArr中
while (i <= mid) {
tempArr[k++] = arr[i++];
}
// 如果右邊的數(shù)組還沒遍歷完,就將右邊數(shù)組剩余元素依次放入tempArr中
while (j <= right) {
tempArr[k++] = arr[j++];
}
// 將tempArr中排好序的添加到原數(shù)組中
for(int x=0; x<tempArr.length; x++) {
arr[left + x] = tempArr[x];
}
}
right
相等了,表示只有一個(gè)元素,那就不用拆了,否則就對(duì)左邊和右邊的都進(jìn)行遞歸拆分,拆到不可再拆就合并。public static void sort(int[] arr, int left, int right) {
// 如果左指針和右指針相等,表示只有一個(gè)元素,那就不需要拆分了
if (left == right) {
return;
}
// 否則就進(jìn)行拆分
int mid = left + (right - left) / 2; // 找到中間值進(jìn)行拆分
// 對(duì)左邊再進(jìn)行拆分
sort(arr, left, mid);
// 對(duì)右邊再進(jìn)行拆分
sort(arr, mid+1, right);
// 進(jìn)行合并
merge(arr, left, mid, right);
}
經(jīng)測(cè)試,對(duì)1000萬個(gè)隨機(jī)數(shù)進(jìn)行排序,大概需要2秒,方式一和方式二時(shí)間上差不多,但是方式二可以省不少的內(nèi)存,大家可以在執(zhí)行的時(shí)候看看內(nèi)存的占用情況。
聯(lián)系客服