主要參考:
交換
std::swap()
可以直接使用,也可以自己實現:
template <class T>
void swap(const T& a, T const& b)
{
T temp = a;
a = b;
b = temp;
}
關於const
,值得說明的是:
const T x;
定義一個常量x,其值不可修改
const T* p;
定義一個指針變量p,其指向對象*p
不可修改,指針p
可修改T const* p;
與上等同T* const p = &var;
定義一個指針變量p,其指向對象*p
可修改,指針p
不可修改const T* const p = &var;
定義一個指針變量p,其指向對象*p
和指針p
都不可修改
const T& a = var
定義一個引用變量a,其值不可修改T const& a = var
與上等同T& const a = var
無意義
bool empty() const;
const修飾表明該成員函數不修改數據成員
冒泡排序
比較n-1
趟,每趟比較n-i-1
次,每次從頭開始,大數逐次移到末尾. 下圖是每次將小數移至最前端。
template<class T>
void BubbleSort(T* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - i - 1; j++)
{
if (a[j] > a[j + 1])
swap(a[j], a[j + 1]);
}
}
}
選擇排序
減少交換次數,只操作下標
template<class T>
void selectSort(T* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int min = i;
for (int j = i + 1; j < n; j++)
{
if (a[min] > a[j])
min = j;
}
if (min != i)
swap(a[i], a[min]);
}
}
插入排序
在要排序的一組數中,假定前n-1個數已經排好序,現在將第n個數插到前面的有序數列中,使得這n個數也是排好順序的。如此反覆循環,直到全部排好順序。
template<class T>
void insertSort(T* a, int n)
{
for (int right = 1; right < n; right++)
{
int left = right - 1;
T temp = a[right];
while (temp < a[left] and left >= 0)
{
a[left + 1] = a[left];
left--;
}
int middle = left + 1;
a[middle] = temp;
}
}
快速排序 ★★★
遞歸的排序算法,平均時間 性能是目前最好的,應用最廣泛; 但需要工作棧存放遞歸調用的執行環境,平均棧深 ,是一種不穩定的排序算法.
快速排序 的思想----分治法非常實用,因此很多軟件公司的筆試面試,包括像騰訊,微軟等知名IT公司都喜歡考這個,還有大大小的程序方面的考試如軟考,考研中也常常出現快速排序的身影。
基本思想:(分治)
- 先從數列中取出一個數作爲key值;
- 將比這個數小的數全部放在它的左邊,大於或等於它的數全部放在它的右邊;
- 對左右兩個小數列重複第二步,直至各區間只有1個數。
template<class T>
void quickSort(T* a, const int left, const int right)
{
if (left >= right) return; // 待排序數列只有一個元素時,結束遞歸
else
{
// 選樞軸
if (right - left == 1);
else
{ // 中值放在左側
int middle = (left + right) / 2;
if (a[left] > a[middle])
swap(a[middle], a[left]);
if (a[middle] > a[right])
swap(a[middle], a[right]);
if (a[left] < a[middle])
swap(a[middle], a[left]);
}
int pivot = left;
// 一次劃分
int i{ left }, j{ right + 1 };
while (i < j)
{
do i++; while (a[i] < a[pivot]); // 從左掃描
do j--; while (a[j] > a[pivot]); // 從右掃描
if (i < j) swap(a[i], a[j]);
}
swap(a[pivot], a[j]); // 最左側爲初始樞軸
pivot = j;
// 遞歸
quickSort(a, left, pivot - 1);
quickSort(a, pivot + 1, right);
}
}
歸併排序
歸併排序是建立在歸併操作上的一種有效的排序算法。該算法是採用分治法的一個非常典型的應用。
首先考慮引子:如何將2個有序數列合併?這個非常簡單,只要從比較2個數列的第一個數,誰小就先取誰,取了後就比較下一個數,如果有數列爲空,那直接將另一個數列的數據依次取出即可。
引子
以下是兩個有序數組a[ ], b[ ]的歸併排序(最簡單)
解決了上面的合併有序數列問題,再來看歸併排序,其的基本思路是:將數組分成2組A,B,如果這2組組內的數據都是有序的,那麼就可以很方便的將這2組數據進行排序。如何讓這2組組內數據有序了?可以將A,B組各自再分成2組。依次類推,當分出來的小組只有1個數據時,可以認爲這個小組組內已經達到了有序,然後再合併相鄰的2個小組就可以了。這樣通過先 遞歸 的分解數列,再合併數列就完成了歸併排序。
- 遞歸式
// 一次歸併排序, 輸入爲已排序數組 a[l,...,m] a[m+1,...,n]
template <class T>
void merge(T* a, const int l, const int m, const int n)
{
// 申請輔助空間
int length = n - l + 1;
assert(length > 1); // 消除C6386警告
T* temp = new T[length];
// 遍歷等長元素
int i = l, j = m + 1, k = 0;
while (i <= m and j <= n)
{
if (a[i] <= a[j])
{
temp[k++] = a[i++];
}
else
{
temp[k++] = a[j++];
}
}
// 複製其餘元素
copy(a + i, a + m + 1, temp + k);
copy(a + j, a + n + 1, temp + k);
// 釋放輔助空間
copy(temp, temp + length, a + l);
delete[] temp;
}
template <class T>
void mergeSort(T* a, int left, int right)
{
if (left >= right)
return;
int mid = (left + right) / 2;
mergeSort(a, left, mid);
mergeSort(a, mid + 1, right);
merge(a, left, mid, right);
}
- 迭代式
以下代碼的缺點:頻繁申請/釋放內存
// 一次歸併排序, 輸入爲已排序數組 a[l,...,m] a[m+1,...,n]
template <class T>
void merge(T* a, const int l, const int m, const int n)
{
// 申請輔助空間
int length = n - l + 1;
assert(length > 1); // 消除C6386警告
T* temp = new T[length];
// 遍歷等長元素
int i = l, j = m + 1, k = 0;
while (i <= m and j <= n)
{
if (a[i] <= a[j])
{
temp[k++] = a[i++];
}
else
{
temp[k++] = a[j++];
}
}
// 複製其餘元素
copy(a + i, a + m + 1, temp + k);
copy(a + j, a + n + 1, temp + k);
// 釋放輔助空間
copy(temp, temp + length, a + l);
delete[] temp;
}
// 一趟歸併排序
template <class T>
void mergePass(T* a, int n, int h)
{
int i= 1;
while (n - i + 1 >= 2 * h)
{
merge(a, i, i + h - 1, i + 2 * h - 1);
i += 2 * h;
}
if (n - i + 1 > h)
merge(a, i, i + h - 1, n);
else
;// do nothing
}
// 歸併排序(迭代式)
template <class T>
void mergeSort(T* a, int n)
{
if (a == nullptr or n <= 0) return;
// 最初歸併長度爲1
for (int h = 1; h < n; h *= 2)
{
mergePass(a, n, h);
}
}
改進:只申請一次內存
// 一次歸併排序, 輸入爲已排序數組 a[l,...,m] a[m+1,...,n]
template <class T>
void merge(T* a, T* result, const int l, const int m, const int n)
{
// 遍歷等長元素
int i = l, j = m + 1, k = l;
while (i <= m and j <= n)
{
if (a[i] <= a[j])
{
result[k++] = a[i++];
}
else
{
result[k++] = a[j++];
}
}
// 複製其餘元素
copy(a + i, a + m + 1, result + k);
copy(a + j, a + n + 1, result + k);
}
// 一趟歸併排序
template <class T>
void mergePass(T* a, T* result, int n, int h)
{
int i = 1;
while (n - i + 1 >= 2 * h)
{
merge(a, result, i, i + h - 1, i + 2 * h - 1);
i += 2 * h;
}
if (n - i + 1 > h)
merge(a, result, i, i + h - 1, n);
else
copy(a + i, a + n + 1, result + i);
}
// 歸併排序(迭代式)
template <class T>
void mergeSort(T* a, int n)
{
if (a == nullptr or n <= 0) return;
T* temp = new T[n + 1];
int h = 1; // 最初歸併長度爲1
while (h < n)
{
mergePass(a, temp, n, h);
h *= 2;
mergePass(temp, a, n, h);
h *= 2;
}
delete[] temp;
}
基數排序(桶、箱)
基數排序(radix sort
)屬於“分配式排序”(distribution sort),又稱“桶排序”(bucket sort)或“箱排序”(bin sort),顧名思義,它是透過鍵值的部份資訊,將要排序的元素分配至某些“桶”中,藉以達到排序的作用,基數排序法屬於穩定性的排序,其時間複雜度爲O (nlog®m),其中r爲所採取的基數,而m爲堆數,在某些時候,基數排序法的效率高於其它的穩定性排序法。
基數排序是建立在“箱排序”的基礎上的。箱排序是創建數組
A[MaxValue]
;然後將每個數放到相應的位置上(例如17放在下標17的數組位置);最後遍歷數組,即爲排序後的結果。
.
存在的問題: 當序列中存在較大值時,BinSort 的排序方法會浪費大量的空間開銷。
基數排序是在BinSort的基礎上,通過基數的限制來減少空間的開銷。所謂基數,是指進制,十進制數列的基數爲10,二進制爲2,八進制爲8.
可以用鏈表或者數組來構造“箱”,這裏使用STL中的鏈表完成:
template<class T>
int maxdigit(const T a, int n)
{
int digits{ 1 }, ratio{ 10 };
for (int i = 0; i < n; ++i)
{
while (a[i] > ratio)
{
++digits;
ratio *= 10;
}
}
return digits;
}
template<class T>
void radixSort(T* a, int n)
{
std::list<T> bins[10]; // 十進制需要十個箱
int digits = maxdigit(a, n);
for (int d = 0, radix = 1; d < digits; ++d, radix *= 10)
{
// 將每個數放入對應箱中
for (int i = 0; i < n; ++i)
{
int index = (a[i] / radix) % 10;
bins[index].push_back(a[i]);
}
// 從10個箱中按順序取出,每取一個就刪除一個
for (int i = 0, j = 0; i < 10; ++i)
{
while (not bins[i].empty())
{
a[j++] = bins[i].front();
bins[i].pop_front();
}
}
}
}