排序
基本概念
本章會介紹並且實現,常用的幾種排序算法及其思想,但是關於排序除了時間複雜度和空間複雜度這兩個衡量算法的基本標準外,還引入了穩定的概念。
穩定
如果一個排序算法排序結束後,表中相同大小的元素依然可用保持和排序前一樣的相對順序則稱該排序是穩定的,反之是不穩定的。
舉個例子,以下這個序列中,出現了相同的元素3,爲了便於區分,我將其中一個用紅色標記出來。
如果我們進行了某種排序算法將其進行了排序,並且排序結果如下。
我們發現結果中紅色的3與黑色的3相比排序前的序列依然保持着黑色在前紅色在後的相對順序,此時我們則稱這個排序是穩定的,但是如果排序後有可能會使任意一組相同元素的相對順序出現了改變,則稱其是不穩定的。
注意穩定度是一個排序的基本性質,一次結果是穩定的不能說排序是穩定的,必須保證每次都是穩定的纔可以說這個排序是穩定的,這就需要根據排序的思想來判斷其是否穩定。
冒泡排序
冒泡排序屬於交換排序的一種,以升序排序爲例,就是根據序列中兩個記錄鍵值的比較結果來對換這兩個記錄在序列中的位置,將鍵值較大的記錄向序列的尾部移動,鍵值較小的記錄向序列的前部移動。這樣的過程十分類似於水泡上浮冒泡的過程,所以成爲冒泡排序。
#include <iostream>
#include <vector>
void BubbleSort(std::vector<int>& arr)
{
int n = arr.size();
bool flag = true;
for(int i = 0; i < n - 1; i++)
{
if(flag == false)
{
break;
}
flag = false;
for(int j = 0; j < n - i - 1; j++)
{
if(arr[j] > arr[j + 1])
{
flag = true;
std::swap(arr[j], arr[j + 1]);
}
}
}
}
int main()
{
std::vector<int> arr = {1, 1, 2, 2, 99, 3, 3, 4, 5, 88, 2};
BubbleSort(arr);
for(const auto& e : arr)
{
std::cout << e << " ";
}
}
1 1 2 2 2 3 3 4 5 88 99
冒泡排序十分容易理解,但它是一種十分低效的算法,其時間複雜度爲ON^2
,空間複雜度爲O1
,但是該算法是穩定的。
直接插入排序
直接插入排序屬於插入排序的一種,插入排序是將序列分爲兩部分,一部分爲已經有序部分,一部分爲未有序部分。遍歷未有序部分將該部分第一個元素插入到有序序列中的合適位置,遍歷完畢則完成了排序。
但是在實際實現時爲了簡化操作,具體流程爲將未有序部分與有序部分最後一個比較,如果不滿足排序要求則交換,繼續與倒數第二個元素比較以此類推,直到滿足排序順序爲止。
#include <iostream>
#include <vector>
void InsertSort(std::vector<int>& arr)
{
if(arr.size() <= 1)
{
return;
}
int n = arr.size();
for(int i = 1; i < n; i++)
{
int j = i - 1;
while(arr[j] > arr[j + 1] && j >= 0)
{
std::swap(arr[j], arr[j + 1]);
j--;
}
}
}
int main()
{
std::vector<int> arr = {1, 1, 99, 23, 32, 2, 2, 4, 5};
InsertSort(arr);
for(const auto& e : arr)
{
std::cout << e << " ";
}
}
1 1 2 2 4 5 23 32 99
直接插入排序也是十分容易理解的排序,但是同樣的效率十分低下,其時間複雜度爲ON^2
,空間複雜度爲O1
,但是它同樣是穩定的。
選擇排序
選擇排序同樣是將序列分爲兩個部分,一個部分爲有序部分,一個爲無序部分,以升序爲例,選擇排序每次都從無序部分中選一個最小的放到有序序列的末尾,當無序部分全部變爲有序部分則排序結束。
#include <iostream>
#include <vector>
void SelectSort(std::vector<int>& arr)
{
int n = arr.size();
for(int i = 0; i < n - 1; i++)
{
int min = i;
for(int j = i + 1; j < n; j++)
{
if(arr[j] < arr[min])
{
min = j;
}
}
if(min != i)
{
std::swap(arr[i], arr[min]);
}
}
}
int main()
{
std::vector<int> arr = {1, 2, 3, 19, 10, 5, 2, 5, 7};
SelectSort(arr);
for(const auto& e : arr)
{
std::cout << e << " ";
}
}
1 2 2 3 5 5 7 10 19
選擇排序也是十分容易理解但是效率較低的排序方式,其時間複雜度爲ON^2
,空間複雜度爲O1
,但是它也是穩定的。
希爾排序
希爾排序是插入排序的升級版本。因爲插入排序對於越有序的序列所用排序時間越短,時間複雜度越低的特性,希爾排序旨在使用插入排序使每次排序的序列要麼足夠短,要麼就幾乎已經有序,來極大程度節省時間,提高效率。
希爾排序思想是將序列通過間隔分爲若干子序列,對這些子序列先進行排序,每次減小間隔直到1,就完全變成了直接插入排序,不過此時序列已經幾乎有序,可用大大提高直接插入排序的效率。
#include <iostream>
#include <vector>
void InsertSort(int gap, std::vector<int>& arr)
{
int n = arr.size();
for(int i = gap; i < n; i++)
{
for(int j = i - gap; j >= 0; j -= gap)
{
if(arr[j + gap] >= arr[j])
{
break;
}
else
{
std::swap(arr[j + gap], arr[j]);
}
}
}
}
void ShellSort(std::vector<int>& arr)
{
for(int i = arr.size() / 2; i >= 1; i /= 2)
{
InsertSort(i, arr);
}
}
int main()
{
std::vector<int> arr = {1, 2, 10, 3, 4, 7, 2, 9, 3, 4, 99, 5};
ShellSort(arr);
for(const auto& e : arr)
{
std::cout << e << " ";
}
}
1 2 2 3 3 4 4 5 7 9 10 99
希爾排序是直接插入排序的升級版本,因此它利用自己本身的特性提高了插入排序的效率,平均情況下,它的時間複雜度可用達到ONlogN~ON^2
,空間複雜度爲O1
,但是希爾排序是不穩定的。
堆排序
堆排序時利用二叉樹的順序結構建成二叉堆,利用堆進行排序的排序算法,算法分爲兩個部分。
首先要對序列建堆,建堆思路及從最後一個父結點開始向下調整,直到第一個父結點爲止。
建好堆後將堆頂元素彈出,即將堆頂元素與最後一個元素交換,然後彈出最後一個元素,然後對堆頂進行一次向下調整,此爲一次排序,不斷重複進行排序,直到堆爲空爲止,則排序完成。
#include <iostream>
#include <vector>
//堆排序最簡單的實現方法就是使用STL中的優先級隊列
//優先級隊列內部自動維護了一個堆,可用很輕鬆的幫助我們完成堆排序
//但是爲了更好的瞭解算法思想這裏我們手動維護這個堆
void AdjustDown(int start, int end, std::vector<int>& arr)
{
int parent = start;
int child = parent * 2 + 1;
while(child < end)
{
if(child + 1 < end && arr[child + 1] > arr[child])
{
child += 1;
}
if(arr[child] <= arr[parent])
{
break;
}
else
{
std::swap(arr[child], arr[parent]);
parent = child;
child = parent * 2 + 1;
}
}
}
void HeapSort(std::vector<int>& arr)
{
//建堆
int n = arr.size();
for(int i = (n / 2 - 1); i >= 0; i--)
{
AdjustDown(i, n, arr);
}
//排序
while(n > 0)
{
std::swap(arr[0], arr[n - 1]);
n--;
AdjustDown(0, n, arr);
}
}
int main()
{
std::vector<int> arr = {1, 4, 5, 3, 4, 22, 33, 5, 2};
HeapSort(arr);
for(const auto& e : arr)
{
std::cout << e << " ";
}
}
1 2 3 4 4 5 5 22 33
堆排序的效率較高,並且序列越無序則效率越高,它的時間複雜度爲ONlogN
,空間複雜度爲O1
,但它是不穩定的。
歸併排序
歸併排序的思想是將序列首先拆分成若干不可再分的子序列,然後再將它們一一合併達到有序的效果。
#include <iostream>
#include <vector>
//左閉右開區間
void Merge(std::vector<int>& arr, int start, int mid, int end)
{
std::vector<int> arr1, arr2;
for(int i = start; i < mid; i++)
{
arr1.push_back(arr[i]);
}
for(int i = mid; i < end; i++)
{
arr2.push_back(arr[i]);
}
int i = 0, j = 0, k = start;
while(i < arr1.size() && j < arr2.size())
{
if(arr1[i] <= arr2[j])
{
arr[k] = arr1[i];
i++;
k++;
}
else
{
arr[k] = arr2[j];
j++;
k++;
}
}
while(i < arr1.size())
{
arr[k] = arr1[i];
i++;
k++;
}
while(j < arr2.size())
{
arr[k] = arr2[j];
j++;
k++;
}
}
void MergeSort(std::vector<int>& arr, int start, int end)
{
if(end - start > 1)
{
int mid = (start + end) >> 1;
MergeSort(arr, start, mid);
MergeSort(arr, mid, end);
Merge(arr, start, mid, end);
}
}
int main()
{
std::vector<int> arr = {1, 2, 22, 1, 5, 4, 55, 44, 3};
MergeSort(arr, 0, arr.size());
for(const auto& e : arr)
{
std::cout << e << " ";
}
}
1 1 2 3 4 5 22 44 55
對其進行改進可以寫成以下的樣子。
#include <iostream>
#include <vector>
//左閉右開區間
void Merge(std::vector<int>& arr, int start, int mid, int end)
{
std::vector<int> arr1, arr2;
for(int i = start; i < mid; i++)
{
arr1.push_back(arr[i]);
}
//for(int i = mid; i < end; i++)
//{
// arr2.push_back(arr[i]);
//}
int i = 0, j = mid, k = start;
while(i < arr1.size() && j < end)
{
if(arr1[i] <= arr[j])
{
arr[k] = arr1[i];
i++;
k++;
}
else
{
arr[k] = arr[j];
j++;
k++;
}
}
while(i < arr1.size())
{
arr[k] = arr1[i];
i++;
k++;
}
//while(j < arr2.size())
//{
// arr[k] = arr2[j];
// j++;
// k++;
//}
}
void MergeSort(std::vector<int>& arr, int start, int end)
{
if(end - start > 1)
{
int mid = (start + end) >> 1;
MergeSort(arr, start, mid);
MergeSort(arr, mid, end);
Merge(arr, start, mid, end);
}
}
int main()
{
std::vector<int> arr = {1, 2, 22, 1, 5, 4, 55, 44, 3};
MergeSort(arr, 0, arr.size());
for(const auto& e : arr)
{
std::cout << e << " ";
}
}
1 1 2 3 4 5 22 44 55
歸併排序相比內排序更廣泛適用於外排序中,在需要有大量數據進行排序的時候需要使用外排序局部讀入內存再進行合併的方式進行排序。它的時間複雜度爲ONlogN
,空間複雜度爲ON
,並且它是穩定的。
快速排序
快速排序是基本排序算法中最快的排序,他和歸併算法一樣用了分治的思想。它的思路是每次將序列的第一個元素排到合適的位置,使該元素前面的元素都不大於它,後面的元素都不小於它,然後遞歸整理前面的元素和後面的元素,使所有元素前面的元素都不大於它,後面的元素都不小於它即完成有序。
#include <iostream>
#include <vector>
//左閉右開區間
void QuickSort(std::vector<int>& arr, int start, int end)
{
if(end - start <= 1)
{
return;
}
int temp = arr[start];
int ptr1 = start;
int ptr2 = end - 1;
while(ptr1 < ptr2)
{
while(arr[ptr2] >= temp && ptr1 < ptr2)
{
ptr2--;
}
if(ptr1 >= ptr2)
{
break;
}
arr[ptr1] = arr[ptr2];
while(arr[ptr1] <= temp && ptr1 < ptr2)
{
ptr1++;
}
if(ptr1 >= ptr2)
{
break;
}
arr[ptr2] = arr[ptr1];
}
arr[ptr1] = temp;
QuickSort(arr, start, ptr1);
QuickSort(arr, ptr1 + 1, end);
}
int main()
{
std::vector<int> arr = {3, 1, 2, 55, 2, 4, 88, 22, 1, 3};
QuickSort(arr, 0, arr.size());
for(const auto& e : arr)
{
std::cout << e << " ";
}
}
1 1 2 2 3 3 4 22 55 88
快排使最快的基本排序算法,並且同樣的是越有序則排序越快。它的時間複雜度爲ONlogN
,空間複雜度爲OlogN~ON
,但它是不穩定的。