1. 演算法實作
排序中比較復雜的有歸并排序,快速排序,堆排序三大演算法了,三個演算法的時間復雜度都是O(N * logN),三個演算法的思想我就簡單的展開詳述以下,
1.1 歸并排序
歸并排序的核心思想是鏈表中的經典題目:合并兩個有序鏈表,
劍指offer:合并兩個排序的鏈表
Leetcode P21: Merge Two Sorted Lists
采用分治的思想,將整個陣列分為兩個部分,先對左邊的陣列進行歸并排序,再對右邊的陣列進行歸并排序,最后兩者進行merge,
下面的函式就是歸并排序的歸并部分,將兩個已經有序的陣列歸并,
public class MergeSort implements Sort{
@Override
public void sort(int[] arr) {
if (arr == null || arr.length <= 1) return;
mergeSort(arr, 0, arr.length - 1);
}
public void mergeSort(int[] arr, int l, int r) {
if (l >= r) return;
int mid = l + ((r - l) >> 1);
//注意遞回是i
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
public void merge(int[] arr, int l, int mid, int r) {
int[] newArr = new int[r - l + 1];
int left = l;
int right = mid + 1;
int index = 0;
while (left <= mid && right <= r) {
if (arr[left] > arr[right]) {
newArr[index++] = arr[right++];
} else {
newArr[index++] = arr[left++];
}
}
while (left <= mid) {
newArr[index++] = arr[left++];
}
while (right <= r) {
newArr[index++] = arr[right++];
}
for (int i = 0; i < newArr.length; i++) {
arr[i + l] = newArr[i];
}
}
}
1.2 堆排序
堆排序首先就要利用堆這個資料結構,首先先將整個陣列結構調整成堆,
heapInset函式插入某個數字,并且將插入的部分進行調整,對于一個陣列,從位置0開始插入,并且邊插入邊進行調整,
public class HeapSort implements Sort{
@Override
public void sort(int[] arr) {
if (arr == null || arr.length <= 1) return;
for (int i = 0; i < arr.length; i++) {
heapInsert(arr, i);
}
int heapSize = arr.length;
swap(arr, 0, --heapSize);
while (heapSize > 0) {
heapify(arr, 0, heapSize);
swap(arr, 0, --heapSize);
}
}
public void heapInsert(int[] arr, int index) {
//index > 0 的條件可以省略,因為如果index是1的話,兩個值必定相等,無法進入回圈
while (arr[(index - 1) / 2] < arr[index]) {
int fa = (index - 1) / 2;
swap(arr, index, fa);
index = fa;
}
}
public void heapify(int[]arr, int index, int heapSize) {
int left = index * 2 + 1;
while (left < heapSize) {
int right = left + 1;
int maxLeftRightIndex = right < heapSize && arr[left] < arr[right] ? right : left;
int maxIndex = arr[maxLeftRightIndex] > arr[index] ? maxLeftRightIndex : index;
if (maxIndex == index) {
break;
}
swap(arr, index, maxIndex);
index = maxIndex;
left = maxIndex * 2 + 1;
}
}
public void swap(int []arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
這樣處理完之后,陣列就成為了一個最大堆,要將陣列調整為有序的,要經歷以下幾步:
- 陣列的第一個數是最大堆的頂,是最大的數,每次將第一個數和最大堆的最后一個數交換,這樣后面部分就排好序了
- 交換完之后,最大堆的結構就會被破壞了,所以需要調整最大堆,
//交換到最后一個位置
swap(arr, 0, --heapSize);
while (heapSize > 0) {
//調整位置成最大堆
heapify(arr, 0, heapSize);
//之后再次進行交換
swap(arr, 0, --heapSize);
}
調整位置
public void heapify(int[]arr, int index, int heapSize) {
int left = index * 2 + 1;
while (left < heapSize) {
int right = left + 1;
int maxLeftRightIndex = right < heapSize && arr[left] < arr[right] ? right : left;
int maxIndex = arr[maxLeftRightIndex] > arr[index] ? maxLeftRightIndex : index;
if (maxIndex == index) {
break;
}
swap(arr, index, maxIndex);
index = maxIndex;
left = maxIndex * 2 + 1;
}
}
1.3 快速排序
快速排序的核心思想思想是選一個數,不管是隨機的還是固定的,每一步將大于這個數的數字放在陣列的右邊,小于的所有數字放在左邊,聽起來是不是很熟悉,這就是經典題目荷蘭國旗的思想啦,
Leetcode P75: Sort Colors
public class QuickSort implements Sort{
@Override
public void sort(int[] arr) {
if (arr == null || arr.length <= 1) return;
int length = arr.length;
quickSort(arr, 0, length - 1);
}
public void quickSort(int[] arr, int l, int r) {
if (l >= r || l < 0) return;
int[] mids = partition(arr, l, r);
quickSort(arr, l, mids[0]);
quickSort(arr, mids[1], r);
}
public int[] partition(int arr[], int l, int r) {
int[] mids = new int[]{l ,r};
int num = arr[r];
int left = l - 1;
int right = r;
int cur = l;
while (cur < right) {
if (arr[cur] < num) {
swap(arr, ++left, cur++);
} else if (arr[cur] > num) {
swap(arr, --right, cur);
} else {
cur++;
}
}
swap(arr, right++, r);
mids[0] = left;
mids[1] = right;
return mids;
}
public void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = new int[]{4,1,2,3,4,3,1,4,1,1,23,4,5,4,2,2,3,5,3,1,1,2,3,4,5,6,7};
QuickSort q = new QuickSort();
q.sort(arr);
}
}
2. 時間復雜度分析
2.1 Master theorem
Master公式是為了評估遞回函式的復雜度而誕生的,
T(N) = a* T(N / 2) + O(N^d)
log(b,a) > d時,時間復雜度是O(N ^ log(b,a))log(b,a) = d時,時間復雜度是O(N ^ d + logN)log(b,a) < d時,時間復雜度是O(N^d)
2.2 演算法復雜度分析
使用master公式計算三個排序的時間復雜度
2.2.1 歸并排序
Merge-Sort的核心代碼如下:
public void mergeSort(int[] arr, int l, int r) {
if (l >= r) return;
int mid = l + ((r - l) >> 1);
(1)part 1
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
(2)part 2
merge(arr, l, mid, r);
}
時間復雜度估算:
part 1部分將問題分為兩個部分,所以master中a=2,b=2,part 2部分是有序鏈表的合并,時間復雜度是:O(N),所以d=1,
所以套用master公式中的第二條,時間復雜度是O(N*logN),
2.2.2 堆排序
Merge-Sort的核心代碼如下:
public void sort(int[] arr) {
if (arr == null || arr.length <= 1) return;
(1)part1
for (int i = 0; i < arr.length; i++) {
heapInsert(arr, i);
}
int heapSize = arr.length;
(2)part2
swap(arr, 0, --heapSize);
while (heapSize > 0) {
heapify(arr, 0, heapSize);
swap(arr, 0, --heapSize);
}
}
時間復雜度估算:
- 最大堆的插入后調整的時間復雜度是
O(logN),part 1部分是插入N個元素,時間復雜度是log1+log2+log3+log4+...+logN = N,所以part 1的時間復雜度是O(N), part 2部分中heapify的時間復雜度是logN,heapify就是堆的調整,所以part 2部分的時間復雜度是O(N * logN),- 加起來就是
O(N)+O(N*logN),取最大項即為O(N*logN),
2.2.3 快速排序
Quick-Sort的核心代碼如下:
public void quickSort(int[] arr, int l, int r) {
if (l >= r || l < 0) return;
(1)part 1
int[] mids = partition(arr, l, r);
(2)part 2
quickSort(arr, l, mids[0]);
quickSort(arr, mids[1], r);
}
時間復雜度估算:
part 1中的partition思想是荷蘭國旗問題,時間復雜度是O(N),part 2和歸并排序一樣,也是將問題分為了兩個部分,所以a=2,b=2,
所以快速排序的時間復雜度是O(N*logN),
2.2.4 空間復雜度
除了歸并排序中,需要提前分配O(N)的空間,作為Merge程序中的快取,堆排序和快速排序全部都是in-place修改的,所以不需要分配額外空間,
其實在在歸并排序和堆排序程序中,因為函式遞回,函式占用的堆疊地址也算空間復雜度,但是為什么沒有算進去呢?個人認為是因為在一些高手的眼中,遞回函式是可以改成回圈,所以這就不用考慮這些額外空間了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/161747.html
標籤:Java
