【程式設計4】插入排序+快速排序+LeetCode.148(排序連結串列)
文章目錄
一、排序連結串列
1、題目描述——LeetCode.148
在 O(n logn) 時間複雜度和常數級空間複雜度下,對連結串列進行排序。
示例 1:
輸入: 4->2->1->3
輸出: 1->2->3->4
示例 2:
輸入: -1->5->3->4->0
輸出: -1->0->3->4->5
2、分析
本實現採用快速排序演算法實現
(1)一般的快排
需要一個指標指向頭,一個指標指向尾,然後兩個指標相向運動並按一定規律交換值,最後找到一個支點使得支點左邊小於支點,支點右邊大於支點
==》如果是這樣的話,對於單鏈表我們沒有前驅指標,怎麼能使得後面的那個指標往前移動呢?
==》使兩個指標都往next方向移動並且能找到支點那就好了。
(2)解題思路
只需要兩個指標p和q,這兩個指標均往next方向移動,移動的過程中保持p之前的key都小於選定的key,p和q之間的key都大於選定的key,那麼當q走到末尾的時候便完成了一次支點的尋找。
3、實現
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head) {
if(head == NULL)
return NULL;
ListNode *right = head;
while(right->next)
right = right->next;
QuickSort(head, right);
return head;
}
private:
void QuickSort(ListNode *left, ListNode *right)
{
if(left != right)
{
ListNode *partion = GetPartion(left, right);
QuickSort(left, partion);
if(partion->next)
QuickSort(partion->next, right);
}
}
ListNode *GetPartion(ListNode *left, ListNode *right)
{
int key = left->val;
ListNode *p = left;
ListNode *q = p->next;
while(q != right->next)
{
if(q->val < key)
{
p = p->next;
swap(p->val, q->val);
}
q = q->next;
}
swap(p->val, left->val);
return p;
}
};
二、排序演算法
排序方法 | 時間複雜度 | 是否基於比較 | 適用性 | |
---|---|---|---|---|
1 | 冒泡、插入、選擇 | O(n2) | √ | 適合小規模資料排序 |
2 | 快排、歸併 | O(nlogn) | √ | 適合大規模的資料排序 |
3 | 桶、計數、基數 | O(n) | × |
三、插入排序
1、基本思想
將一個記錄插入到已經排好序的有序表中,從而得到一個新的、記錄數增1的有序表。
(1)過程概述
插入排序在實現上,通常採用in-place排序(即只需用到O(1)的額外空間的排序),因而在從後向前掃描過程中,需要反覆把已排序元素逐步向後挪位,為最新元素提供插入空間。
(2)具體演算法描述:
① 從第一個元素開始,該元素可以認為已經被排序;
② 取出下一個元素,在已經排序的元素序列中從後向前掃描;
③ 如果該元素(已排序)大於新元素,將該元素移到下一位置;
④ 重複步驟 ③,直到找到已排序的元素小於或者等於新元素的位置;
⑤ 將新元素插入到該位置後
⑥ 重複步驟②~⑤
2、示例
原資料:{4,5,6,1,3,2}
排序過程:其中左側是已排序區間,右側是未排序區間。
3、實現
void insertSort(int a[], int len){
int tmp, j;
for(int i = 1; i < len; i++){
tmp = a[i];
int j = i - 1;
while(tmp < a[j])
{
a[j + 1] = a[j];
j--;
}
a[j + 1] = tmp;
}
}
4、分析
(1)原地排序演算法
- 空間複雜度為 O(1)
==》原地排序演算法
(2)穩定排序演算法
- 對於值相同的元素,可以選擇將後面出現的元素,插入到前面出現元素的後面。
==》保持原有前後順序,也就是穩定的排序演算法
(3)時間複雜度
- 最好情況時間複雜度(資料已經有序):O(n)
- 最壞情況時間複雜度:O(n2)
- 平均時間複雜度:O(n2)
- 對於插入排序來說,每次插入操作(時間複雜度為O(n))都相當於在陣列中插入一個數據,迴圈執行 n 次插入操作
四、快速排序
1、基本思想
基於分治思想,快速排序演算法:選擇一個基準數,通過一趟排序將要排序的資料分割成獨立的兩部分;其中一部分的所有資料都比另外一部分的所有資料都要小。然後,再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列。
(1)快速排序的流程
① 從數列中挑出一個基準值(樞軸,pivot)。
② 將陣列進行劃分(partition),將比基準數大的元素都移至樞軸右邊,將小於等於基準數的元素都移至樞軸左邊。在這個分割槽退出之後,該基準就處於數列的中間位置。
③ 遞迴地把"基準值前面的子數列"和"基準值後面的子數列"進行排序。
2、實現
- 實現一
將第一個元素設定為基準值來劃分區間,基準值的位置始終不變,最後交換使得基準值的位置。
// left、right分別表示要交換陣列的第一個元素和最後一個元素的位置
int partition(int arr[], int left, int right)
{
int i = left + 1;
int j = right;
// 將第一個元素設定為 基準值
int temp = arr[left];
while(i <= j)
{
while(arr[i] < temp)
i++;
while(arr[j] > temp)
j--;
if(i < j)
swap(arr[i++], arr[j--]);
else
i++;
}
swap(arr[j], arr[left]);
return j;
}
void quick_sort(int arr[], int left, int right)
{
if(left > right)
return;
int j = partition(arr, left, right);
quick_sort(arr, left, j - 1);
quick_sort(arr, j + 1, right);
}
- 實現二
將第一個元素設定為基準值來劃分區間,將基準值不斷與衝突值進行換位。
void QuickSort(int arr[], int left, int right)
{
int i = left;
int j = right;
int temp = arr[i];
if(i < j)
{
while(i < j)
{
while(i < j && arr[j] >= temp)
j--;
if(i < j)
{
arr[i] = arr[j];
i++;
}
while(i < j && arr[i] < temp)
i++;
if(i < j)
{
arr[j] = arr[i];
j--;
}
}
// 將基準數放在 i 位置
arr[i] = temp;
// 遞迴實現
QuickSort(arr, left, i - 1);
QuickSort(arr, i + 1, right);
}
}
3、效能分析
- 不穩定
- 原地排序——空間複雜度為O(1)
- 時間複雜度:大部分情況下的時間複雜度都可以做到 O(nlogn),只有在極端情況下,才會退化到 O(n2)。