:)不要自我懷疑 :)
最直接和原始的冒泡代碼
// 單個元素時肯定是有序的;故首元素單獨有序,從 [1,n) 爲待排元素所在的區間;
// i 控制: n-1輪冒泡(即執行一系列兩兩元素對比的操作),第 i 輪能夠選出第 i 大(或者小)的元素值,n-1輪 選出 n-1 個較大(或者小)值之後,整個序列即可有序;
for (int i = 1; i < n ; ++i) {
// j 表示: 當前輪次所需要排序(對比大小)的每1對數的索引;
for (int j = 1; j < n; ++j) {
if (arr[j - 1] > arr[j]) {// 最終結果升序,故而前大後小時執行元素交換;不取等號是爲了穩定排序;
std::swap(arr[j - 1], arr[j]);
}
}
}
雙層循環的控制表達不唯一
// 外層i 控制 n-1 輪即可;
for (int i = 0; i < n - 1; ++i) {
// j 表示: 待排序元素區間即可;
for (int j = n - 1; j > 0; --j) {
if (arr[j - 1] > arr[j]) {
std::swap(arr[j - 1], arr[j]);
}
}
}
思考:原始代碼贅餘工作問題
原始代碼的設計問題所在:
原始代碼執行了N^2次元素掃描:每一輪都從開頭掃描至末尾
每一輪冒泡都會加長有序元素區間段:
自前往後冒泡則出現在末端,自後往前則出現在前段;
代碼優化:
雙側循環可跳過對該區間元素的掃描,
僅掃描無序元素區間,這便是代碼優化的理論依據
雙層控制條件語句的優化
從前到後掃描
for (int i = 0; i < n - 1; ++i) {//冒泡次數;
for (int j = 0; j < n - 1 - i; ++j) {//自前向後冒泡比較;
//Todo ;
}
}
自後往前掃描
for (int i = 0; i < n - 1; ++i) {}
for (int j = n - 1 - i; j > 0; --j) {}//自後往前冒泡比較;
循環控制條件的總結(綜述)
無論具體循環的條件如何設置,本質依舊都是確保a的索引在[0,n)範圍;
其次要確保:
外層控制 n-1 輪冒泡,內層控制待排序元素區間即可;
使用<符號而非是<=符號時:
0與n-1配對使用,1與n配對使用.
問題之二與代碼的繼續優化
問題描述及其優化
若當前輪次的冒泡使得整個序列已經全部有序,或者待排序的數據本身就是有序的,
則後續執行的冒泡操作都是贅餘工作;
優化辦法:
引入 flag標誌即可,
記錄當前輪次冒泡是否有元素交換操作,無交換則表明已序列全部有序.
加入有序標誌的簡單冒泡
template <typename T>
void BubbleSort(T* arr, const std::size_t n) {
assert(arr);
bool notSortedFlag = true;
//也可在外層循環內部聲明 bool sortedFlag=true;之後每輪內層循環結束都判斷一次flag,若重置true爲false不被執行則說明已經有序,此時藉助break跳出循環;
for (std::size_t i = n - 1; i && notSortedFlag; --i) {
notSortedFlag = false;
for (std::size_t j = 0; j < i; ++j) {
if (arr[j] > arr[j + 1]) {
notSortedFlag = true;
std::swap(arr[j], arr[j + 1]);
}
//該部分代碼再次改進如下;
}
}
}
溫馨提示:
assert(arr);
該斷言語句需要使用頭文件(C++)
#include <cassert>
問題之三:掃描部分有序數列導致贅餘工作
之前是序列整體有序之後的贅餘工作問題;
若給出的待排序數列直接是前段或者後段部分有序的,則
也會因爲執行了對部分有序元素的區間的掃描而導致工作贅餘.
優化辦法:
引入哨兵標誌,記錄有序區間與無序區間段的邊界,
每一輪冒泡中,最後執行交換操作的索引位置即爲有序與無序的分界處.
加入有序標誌與有無序邊界記錄的進一步優化版冒泡
自左往右掃描,則左段無序、右段有序;
反之,類比可知.
template <typename T>
void BubbleSort2(T* arr, const std::size_t n) {
assert(arr);
std::size_t lastExchangeIndex = 0;
std::size_t sortedBorder = n - 1;//有序無序分界,最開始默認全無序;
//n-1次冒泡獲得n-1個最值;
for (std::size_t i = 0; i < n - 1; ++i) {//冒泡次數仍是n-1趟;
bool isSortedFlag = true;
for (std::size_t j = 0; j < sortedBorder; ++j) {
if (arr[j] > arr[j + 1]) {
isSortedFlag = false;
std::swap(arr[j], arr[j + 1]);
lastExchangeIndex = j;
}
}
sortedBorder = lastExchangeIndex;
if (isSortedFlag) { break; }//未發生交換時flag未被重置爲false,表明數列已經有序;
}
}
冒泡優化終極版本:雙端冒泡法
進一步優化的雙向冒泡法(同快排設計思想),同時刷選最大、最小值往兩端靠攏;
template <typename T>
void HeadTailBubbleSort(T* arr, const std::size_t n) {
assert(arr);
//雙端掃描,無序區間爲中間部分,且不斷縮減;
std::size_t leftIndex = 0;//該索引往左側有序;
std::size_t rightIndex = n - 1;//該索引往右側有序;
//n-1次冒泡獲得n-1個最值;
while (leftIndex < rightIndex) {
std::size_t j = rightIndex;
rightIndex = 0;
bool isSortedFlag = true;//假設有序;
for (std::size_t i = leftIndex; i < j; ++i) {
//自前向後掃描;
if (arr[i] > arr[i + 1]) {
std::swap(arr[i], arr[i + 1]);
rightIndex = i;
isSortedFlag = false;//實際無序;
}
//std::cout << "自前向後掃描: i = " << i << " j = "<< j << " maxIndex = " << maxIndex << std::endl;
//RandomArrayFuntionHelper::PrintArray(arr, n);
}
if (isSortedFlag) { break; }
isSortedFlag = true;
j = leftIndex;
leftIndex = 0;
for (std::size_t i = rightIndex; i > j; --i) {
//自後往前掃描;
if (arr[i - 1] > arr[i]) {
std::swap(arr[i - 1], arr[i]);//此處切勿出錯;
leftIndex = i;
isSortedFlag = false;
}
//std::cout << "自後往前掃描: i = " << i << " j = " << j << " minIndex = " << maxIndex << std::endl;
//RandomArrayFuntionHelper::PrintArray(arr, n);
}
if (isSortedFlag) { break; }
}
}
參考材料
冒泡算法逐步改進的思想,請參考文章:
冒泡排序的算法分析與改進
冒泡算法排序過程的圖形化演示,請參考:
排序算法過程演示
交流方式
QQ —— 2636105163(南國爛柯者)
溫馨提示:
轉載請註明出處!!
2020年3月29日 20:17:43