1. 程式人生 > >【程式設計4】插入排序+快速排序+LeetCode.148(排序連結串列)

【程式設計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)。