今天我們來看下排序,那麼什麼是排序呢?排序是計算機內部經常進行的一種操作,其目的是將一組“無序”的數據元素調整爲“有序”的數據元素。那麼排序的數學定義時什麼呢?如下
下來我們來看一個概念:排序的穩定性。什麼是排序的穩定性呢?它是指如果在序列中有兩個數據元素 r[i] 和 r[j],它們的關鍵字 k[i] == k[j] ,且在排序之前,對象 r[i] 排在 r[j] 前面;如果在排序之後,對象 r[i] 仍在 r[j] 的前面,則稱這個排序方法是穩定的,否則稱這個排序方法是不穩定的。
下來我們來看看多關鍵字排序。這個就是在排序時需要比較的關鍵字多餘一個,那麼是什麼意思呢?就是當排序結果首先按關鍵字 1 進行排序,當關鍵字 1 相同時按關鍵字 2 進行排序;...;當關鍵字 n-1 相同時按關鍵字 n 進行排序。對於多關鍵字排序,我們只需要在比較操作時同時考慮多個關鍵字即可。下來我們通過一個示例代碼來進行分析
#include <iostream> #include "Object.h" using namespace std; using namespace DTLib; struct Test : public Object { int key1; int key2; Test(int k1, int k2) { key1 = k1; key2 = k2; } bool operator ==(const Test& t) { return (key1 == t.key1) && (key2 == t.key2); } bool operator !=(const Test& t) { return !(*this == t); } bool operator >(const Test& t) { return (key1 > t.key1) || ((key1 == t.key1) && (key2 > t.key2)); } bool operator <=(const Test& t) { return !(*this > t); } bool operator <(const Test& t) { return (key1 < t.key1) || ((key1 == t.key1) && (key2 < t.key2)); } bool operator >=(const Test& t) { return !(*this < t); } }; int main() { Test t1(3, 4); Test t2(3, 5); Test t3(2, 4); Test t4(1, 2); cout << "t1 < t2 : " << (t1 < t2) << endl; cout << "t3 > t4 : " << (t3 > t4) << endl; return 0; }
下來我們來看看輸出結果
在上面的示例中,我們看到排序中的關鍵操作:比較和交換。比較是指任意兩個數據元素通過比較操作確定先後次序;交換是指數據元素之間需要交換才能得到預期結果。那麼我們在進行排序的時候怎麼進行判斷這個排序是優是劣呢?從下面三個方面來進行判斷。1、時間性能:關鍵性能差異體現在比較和交換的數量;2、輔助存儲空間:爲完成排序操作所需要額外的存儲空間,必要時可以“空間換時間”;3、算法的實現複雜性:過於複雜的排序法可能影響可讀性和可維護性。
下來我們來看看 DTLib 庫中的排序類的設計,如下
我們來基於上面的排序類來進一步實現選擇排序和插入排序。
1、選擇排序:它的基本思想是每次(例如第 i 次,i = 0, 1, ..., n-2)從後面 n-i 個待排的數據元素中選出關鍵字最小的元素,作爲有序元素序列第 i 個元素。第 i 次選擇排序示例如下
我們來看看具體是怎麼實現的,如下所示
我們看到是從第一個開始,選出最小的數據元素,後面以此類推,直至最後全部排序完畢。下來我們來具體看看源碼是怎樣實現的
#ifndef SORT_H #define SORT_H #include "Object.h" namespace DTLib { class Sort : public Object { private: Sort(); Sort(const Sort&); Sort& operator= (const Sort&); template <typename T> static void Swap(T& a, T& b) { T c(a); a = b; b = c; } public: template < typename T > static void Select(T array[], int len, bool min2max = true) { for(int i=0; i<len; i++) { int min = i; for(int j=i; i<len; j++) { if(min2max ? (array[min] > array[j]) : (array[min] < array[j])) { min = j; } } if( min != i ) { Swap(array[i], array[min]); } } } }; } #endif // SORT_H
測試代碼如下
#include <iostream> #include "Sort.h" using namespace std; using namespace DTLib; int main() { int array[] = {3, 2, 4, 1, 5}; Sort::Select(array, 5); for(int i=0; i<5; i++) { cout << array[i] << endl; } }
我們來看看運行結果
我們在代碼中默認是從小到大的進行排序,我們在選擇排序時,再輸入第三個參數 false,看看它是否是從大到小進行排序的
通過上面的排序可知,在排完序後,數據元素的位置已經改動。因此,選擇排序是不穩定排序。
2、插入排序:它的基本思想是當插入第 i(i >= 1) 個數據元素時,其那面的 V[0], V[1], ..., V[i-1] 已經排好序;這時,用 V[i] 的關鍵字與 V[i-1],V[i-2],...,V[0] 的關鍵字進行比較,找到位置後將 V[i] 插入,原來位置上的對象向後順移。第 i 次插入排序示例如下
我們來看看具體是怎麼實現的,如下所示
最後的結果爲
我們看到它是通過比較來得出具體位置的。那麼我們在下面的代碼中直接從後向前來進行比較,具體源碼如下
template < typename T > static void Insert(T array[], int len, bool min2max = true) { for(int i=1; i<len; i++) { int k = i; T e = array[i]; for(int j=i-1; (j>=0) && (min2max ? (array[j]>e) : (array[j]<e)); j--) { array[j+1] = array[j]; k = j; } if( k != i ) { array[k] = e; } } }
我們來看看使用 Insert 排序方法是否和之前使用 Select 排序方法實現的效果是一樣的,結果如下(還是加上 false 參數)
我們看到效果是完全一樣的。那麼根據上面的方法可知,在進行插入排序時,數據元素的相對順序不需要改動,因此插入排序是穩定排序。通過對選擇排序和插入排序的學習,總結如下:1、排序是數據元素從無序到有序的過程;2、排序具有穩定性,是選擇排序算法的因素之一;3、比較和交換是排序的基本操作,多關鍵字排序與單關鍵字排序無本質區別;4、排序的時間性能是區分排序算法好壞的主要因素;5、選擇排序每次選擇未排元素中的最小元素,插入排序每次將第 i 個元素插入前面 i-1 個已排元素中;6、選擇排序是不穩定的排序法,插入排序是穩定的排序方法;7、選擇排序和插入排序的時間複雜度爲 O(n2)。