這篇博客記錄一下兩種比較快速的排序。
歸並排序和快速排序。
歸並排序(\(O(nlogn)\))
歸並排序,顧名思義就是先遞歸后合並,這里畫了一張圖簡單理解歸並過程:
代碼肯定要用到遞歸啦,遞歸到不能再細分就可以開始合並了。而合並是通過申請額外的內存空間來完成的,合並時在左右兩個區間內進行比較,按照大小依次放入額外的空間中,最后復制回原數組,這樣一直遞歸往上,就實現了排序。
代碼:
void merge_sort(int arr[], int a,int b)//遞歸
{
if (a >= b)
return;
int mid = (a + b) / 2;
merge_sort(arr, a, mid);
merge_sort(arr, mid + 1, b);
merge(arr, a, mid, b);
}
void merge(int arr[], int l, int mid, int r)//合並
{
int *temp=new int[r - l + 1];//申請額外空間
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r)//比較並存放在額外的空間里
{
if (arr[i] <= arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
/*將剩下元素的放入額外空間*/
while (i <= mid)
temp[k++] = arr[i++];
while (j <= r)
temp[k++] = arr[j++];
for (int i = l; i <= r; i++)//復制回原空間
arr[i] = temp[i - l];
delete[]temp;
}
時間復雜度推算:
歸並排序的一大好處就是不管數據是什么樣的,它的時間復雜度都為\(O(nlogn)\)。但它的缺點就是會用額外空間,空間復雜度為\(O(n)\)。
快速排序
快速排序也是利用二分法和遞歸進行排序。
下面是簡單的快排過程:
快排的核心就在\(partition\)這個函數,他的作用就是再區間里隨機找一個點(一般找最右邊哪個元素),將這個點作為中心點,將比他小的元素放在左邊,比他大的元素放在右邊(升序),然后不斷二分最后就得到一個排列好的數組了。
void quick_sort(int arr[], int a, int b)
{
if (a >= b)
return;
int p = partition(arr, a, b);
quick_sort(arr, a, p - 1);
quick_sort(arr, p + 1, b);//這里一定是p+1,將中心點排除在外,不然會在區間里有重復元素的時候陷入死循環!!!!
}
int partition(int arr[], int a, int b)
{
int i = a, j;
int p = arr[b];//記錄中心點
for (j = a; j < b; j++)
{
if (arr[j] < p)//找到比中心點小的元素就交換
{
swap(arr[i], arr[j]);
i++;
}
}
swap(arr[i], arr[b]);//將中心點交換到中間
return i;//返回中心點下標
}
因為快排是個空間復雜度為\(O(lgn)\)的只有遞歸空間消耗的原地排序,所以在找中心點的時候為了不用額外空間算法設計的很巧妙。
這里將\(i\)作為一個游標,他的左邊是已經處理的區間,右邊為未處理區間,用\(j\)去找那些比中心點小的元素並和\(i\)交換,最后把選定的中心點和\(i\)交換就完成處理了。
值得注意的是快排是一種時間復雜度不穩定的算法,它一般情況下時間復雜度為\(O(nlogn)\),但在極端條件下會退化為\(O(n^2)\)。
快速排序和歸並排序的區別
快速排序和歸並的區別就在於它正好和遞歸的排序反過來。快速排序先排序再遞歸細分,排序是從上到下的。歸並排序先遞歸細分再排序,排序是從下到上的。