交換排序(氣泡排序和快速排序)
交換排序:根據序列中兩個元素關鍵字的比較結果來對換這兩個記錄在序列中的位置。
氣泡排序的基本思想:假設待排序元素表長為size,從前往後(或從後往前)兩兩比較相鄰元素的值,若為逆序(即arr[j]>arr[j+1]),則交換他們,直至整個元素表有序。
// 氣泡排序(升序 從前往後)(下沉)每趟排序確定一個相對最大的數,放在右邊 void bubbleSort(int *arr, int size) { if (nullptr == arr || size <= 0) return; for (int i = 0; i < size - 1; i++) // 一共掃描(size-1)趟 { bool flag = false; // 本趟掃描是否發生交換的標誌 for (int j = 0; j < size - 1 - i; j++) // 第(i+1)趟時,共需比較(size-1-i)次 { if (arr[j]>arr[j + 1]) // 逆序則交換 { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; flag = true; } } if (!flag) break; } }
// 氣泡排序(升序 從後往前 上浮)每趟排序確定一個相對最小的數 放在左邊 void bubbleSort_min(int *arr, int size) { if (nullptr == arr || size <= 0) return; for (int i = 0; i < size - 1; i++) { bool flag = false; for (int j = size - 1; j>i; j--) { if (arr[j - 1] > arr[j]) { int temp = arr[j - 1]; arr[j - 1] = arr[j]; arr[j] = temp; flag = true; } } if (!flag) break; } }
(1)演算法的最好時間複雜度
若檔案的初始狀態是正序的,一趟掃描即可完成排序。所需的關鍵字比較次數C和記錄移動次數M均達到最小值:
Cmin=n–1
Mmin=0
氣泡排序最好的時間複雜度為O(n)。
(2)演算法的最壞時間複雜度
若初始檔案是反序的,需要進行n–1趟排序。每趟排序要進行n–i次關鍵字的比較(1≤i≤n–1),且每次比較都必須移動
記錄3次來交換記錄位置。在這種情況下,比較和移動次數均達到最大值:
Cmax=n(n–1)/2=O(n2)
Mmax=3n(n–1)/2=O(n2)
氣泡排序的最壞時間複雜度為O(n2)。
(3)演算法的平均時間複雜度為O(n2)
雖然氣泡排序不一定要進行n–1趟,但由於它的記錄移動次數較多,故平均時間效能比直接插入排序要差得多。
(4)演算法穩定性
氣泡排序是就地排序,且它是穩定的。
快速排序的基本思想是基於分治法,如下:(挖坑填充+分治法)
1、先從數列中取出一個數作為基準數;
2、分割槽過程,將比這個數大的數全放到它的右邊,小於或等於它的數全放到它的左邊;
3、再對左右區間重複第二步,直到各區間只有一個數。
int partition(int *arr, int size, int low, int high)
{
int value = arr[low];
while (low < high)
{
while ((low < high) && (value <= arr[high]))
high--;
arr[low] = arr[high];
while ((low<high) && (value >= arr[low]))
low++;
arr[high] = arr[low];
}
arr[low] = value;
return low;
}
void quickSort(int *arr, int size, int low, int high)
{
assert(arr != nullptr || size > 0 || low >= 0 || high<size ); //判空
if (low < high) // 遞迴返回條件:當陣列長度等於1,即low=high時,遞迴結束。
{
int index = partition(arr, size, low, high);
quickSort(arr, size, low, index - 1);
quickSort(arr, size, index + 1, high);
}
}
(1) 時間複雜度:最好時間複雜度為O(nlog2n),最壞時間複雜度為O(n2),平均時間複雜度為O(nlog2n);快速排序是所有內部排序演算法中平均效能最優的排序演算法。
以下情況快排會出現最壞的時間複雜度:
1)輸入的元素已經排序或逆向排序
2)每個劃分的一邊都沒有元素
(2) 空間複雜度:O(nlog2n)
(3) 穩定性:快速排序是一個不穩定的排序方法。
劍指offer 對公司員工的年齡進行排序,要求時間效率O(n);
公司員工的年齡有一個範圍0~99,使用輔助陣列timeofage[]來統計每個年齡出現的次數。某個年齡出現了多少次就在陣列ages中設定幾次該年齡,就相當於給陣列ages[]排序了。該方法用長度為100的整數陣列作為輔助空間換來了O(n)的時間效率。
// 給公司員工你年齡排序 O(n) 使用輔助陣列timeofage[]
void agesSort(int *ages,int size)
{
assert(nullptr != ages && size > 0); // 判空
const int oldestAge = 99; // 年齡範圍0~99
int timeofAge[oldestAge + 1]; // 儲存每個年齡出現的次數
for (int i = 0; i < oldestAge; i++) // 每個年齡出現的次數均初始化為0,以便計數
timeofAge[i] = 0;
for (int i = 0; i < size; i++)
{
int age = ages[i];
assert(age >= 0 && age <= 99); // 保證年齡在有效範圍內
timeofAge[age]++; // 累計每個年齡出現的次數
}
int index = 0; // 重新定義一個下標,對年齡進行排序
for (int i = 0; i <= oldestAge; i++) // 對年齡進行排序
{
for (int j = 0; j < timeofAge[i]; j++)
{
ages[index] = i;
index++;
}
}
}