基礎排序演算法詳解與優化

来源:https://www.cnblogs.com/geyouneihan/archive/2018/09/30/9733206.html
-Advertisement-
Play Games

常見的基礎排序有選擇排序、冒泡排序和插入排序。眾所周知,他們的時間複雜度是 O(n\*n)。 但是,現在要重新認識一下基礎排序演算法,尤其是“插入排序”:在近乎有序的情況下,插入排序的時間複雜度可以降低到 O(n)的程度。 因此,在處理系統日誌的任務中,因為日誌記錄是按照時間排序,但偶爾會有幾條是... ...


文章圖片存儲在GitHub,網速不佳的朋友,請看《基礎排序演算法詳解與優化》 或者 來我的技術小站 godbmw.com

1. 談談基礎排序

常見的基礎排序有選擇排序、冒泡排序和插入排序。眾所周知,他們的時間複雜度是 O(n*n)。

但是,現在要重新認識一下基礎排序演算法,尤其是“插入排序”:在近乎有序的情況下,插入排序的時間複雜度可以降低到 O(n)的程度。

因此,在處理系統日誌的任務中,因為日誌記錄是按照時間排序,但偶爾會有幾條是亂序,此時使用插入排序再好不過。而對於高級排序演算法,一個常見的優化就是利用插入排序做局部數據排序優化。

2. 演算法實現

排序演算法被封裝在了SortBase.h中的SortBase命名空間中,以實現模板化和防止命名衝突。如下圖所示:

2.1 選擇排序

假設從小到大排序,那麼,剛開始指針指向第一個數據,選擇從當前指針所指向數據到最後一個數據間最小的數據,將它放在指針位置。

指針後移一位,重覆上述步驟,直到指針移動到最後一個數據。

這種重覆保證了每次,指針前面的數據都是從小到大排好順序的數據。所以,從頭到尾掃描一遍,自然排好序了。

代碼如下:

template <typename T>
void selectionSort(T arr[], int n) {
  int minIndex = -1;
  for(int i = 0; i < n; i++) {
    minIndex = i;
    for(int j = i+1; j < n; ++j) {
      if(arr[j] < arr[minIndex]) {
        minIndex = j;
      }
    }
    swap(arr[i], arr[minIndex]);
  }
}

2.2 冒泡排序

假設排序是從小到大排序。

我一直感覺冒泡排序是和選擇排序反過來了(如果說錯請指正)。因為選擇排序是每次選擇最小的數據,放到當前指針位置;而冒泡排序是把不停交換相鄰數據,直到把最大的數據“冒泡”到應該到的位置。

優化的地方是:記錄每次交換的最後位置,在此之後的元素在下一輪掃描中均不考慮。因為交換的最後位置之後的元素已經是從小到大排序好了的。

在實現過程中,因為需要不停交換相鄰兩個數據,因此,消耗了很多額外時間。

template <typename T>
void bubbleSort(T arr[], int n) {
  int newn;
  do {
    newn = 0;
    for(int i = 1; i < n; i++) {
      if(arr[i-1] > arr[i]) {
        swap(arr[i-1], arr[i]);
        // 優化
        newn = i;
      }
    }
    n = newn; // 不再考慮 newn 後的數據
  } while (newn > 0);
}

2.3 插入排序

插入排序容易和上面兩個演算法搞混。可以類比打撲克牌時候的對撲克牌進行排序:我們會先排序前 1 張、然後是前 2 張、前 3 張 ... 一直到前 n 張。演算法實現顯然是雙重迴圈,如下所示:

template <typename T>
void insertionSort(T arr[], int n) {
  for(int i = 1; i < n; i++) {
    for(int j = i ; j > 0; j--) {
      if(arr[j - 1] > arr[j]) {
        swap(arr[j], arr[j - 1]);
      } else {
        break; // 優化:已經保證之前都是正常排序,直接跳出即可
      }
    }
  }
}

顯然,插入排序也能在局部排好序的情況下跳出迴圈(代碼中的優化),以減少演算法消耗時間。

然而上述演算法其實跑分並比不上選擇排序,因為swap(arr[j], arr[j - 1]);這行代碼交換了一次,相當於賦值 3 次,在大數據量情況下,比較消耗時間。

優化: 內層迴圈,每次保存arr[i], 在檢測到當前數據大於arr[i]的時候,後移一位當前元素arr[j] = arr[j-1];。當跳出內層迴圈時,直接將保存的arr[i]賦值給arr[j]即可。

template <typename T>
void insertionSort(T arr[], int n) {
  for(int i = 1; i < n; i++) {
    T e = arr[i];
    int j = i ;
    for(; j > 0 && arr[j-1] > e; j--) {
      arr[j] = arr[j-1];
    }
    arr[j] = e;
  }
}

3. 性能測試

首先利用 SortTestHelper::generateRandomArray函數生成大量無序隨機數據,然後進行排序和時間測定。代碼如下:

#include <iostream>
#include "SortHelper.h"
#include "SortBase.h"
#include "SortAdvance.h"

using namespace std;

int main() {
  int n = 50000, left = 0, right = n;

  int *arr = SortTestHelper::generateRandomArray<int>(n, left, right);
  int *brr = SortTestHelper::copyArray<int>(arr, n);
  int *crr = SortTestHelper::copyArray<int>(arr, n);
  SortTestHelper::testSort<int>(arr, n, SortBase::selectionSort<int>, "selection sort");
  SortTestHelper::testSort<int>(brr, n, SortBase::insertionSort<int>, "insertion sort");
  SortTestHelper::testSort<int>(crr, n, SortBase::bubbleSort<int>, "bubble sort");
  delete[] brr;
  delete[] arr;
  delete[] crr;

  return 0;
}

運行結果如下圖所示:

除了大量無序隨機數據,類似於系統日誌的數據就是基本有序的大量數據。此時,測試代碼如下:

#include <iostream>
#include "SortHelper.h"
#include "SortBase.h"
#include "SortAdvance.h"

using namespace std;

int main() {

  int n = 50000, left = 0, right = n;
  int *arr = SortTestHelper::generateNearlyOrderedArray<int>(n, 10);
  int *brr = SortTestHelper::copyArray<int>(arr, n);
  int *crr = SortTestHelper::copyArray<int>(arr, n);
  SortTestHelper::testSort<int>(arr, n, SortBase::selectionSort<int>, "selection sort");
  SortTestHelper::testSort<int>(brr, n, SortBase::insertionSort<int>, "insertion sort");
  SortTestHelper::testSort<int>(crr, n, SortBase::bubbleSort<int>, "bubble sort");
  delete[] brr;
  delete[] arr;
  delete[] crr;

  return 0;
}

如圖所示,插入排序的只用了 0.002 秒。在這種數據情況下,插入排序的時間複雜度近似 O(N),絕對快於高級排序的 O(NlogN)。除此之外,還保證了穩定性。

4. 感謝

本篇博客是總結於慕課網的《學習演算法思想 修煉編程內功》的筆記,liuyubobobo 老師人和講課都很 nice,歡迎去買他的課程。

5. 更多內容


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 山師第二周 一.高數小結 •極限的唯一性 •收斂數列的有界性 •收斂數列的保號性 •收斂數列與其子數列 3.函數極限的證明 •自變數趨於有限值時 •自變數趨於無窮時 證明思路:類比數列極限的證明,利用∣f(x)-A∣將f(x)上的極限轉化到x上。 二.C語言小結(目前為止只上了一次機子…果然得靠自學 ...
  • word轉PDF,PDF轉Image,使用oppenOffice註意事項等 ...
  • 秦始皇統一中國之後,實行“書同文,車同軌”,把貨幣和各種度量衡都統一起來,從而締造了一個秩序井然的帝國。既然統一度量衡是每個帝國都要做的事情,Java帝國也不例外,對於人生地不熟的初學者來說,只有認識了Java帝國的各種度量衡,才能更好地入鄉隨俗。 Java帝國的人名稱呼若想在一個國家與當地人溝通交 ...
  • 實驗1: 輸入以下命令,我先是使用a命令進行了輸入,並用t命令進行的單步調試。 可以發現ax,bx在不同的命令下發生了改變,而ip的值也是根據輸入指令的長度而不斷的增加。後來我又使用了g命令進行了一次執行完成(結果和單步相同)。 這裡需要註意,g的最後範圍應當是命令結束的那個地址,而不是下個地址。 ...
  • js數組的定義: 格式1:var 數組名=new Array( ); 格式2:var 數組名=new Array(長度); 格式3:var 數組名=new Array(元素1,元素2....); 格式4:var 數組名=[元素,元素2....]; 二維數組: var 數組名=new Array(ne ...
  • package com.mytripod.util; import sun.rmi.runtime.Log; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.ut... ...
  • 題意 "題目鏈接" Sol 設$sum[i]$表示$1 i$的異或和 首先把每個詢問的$x \oplus sum[n]$就變成了詢問首碼最大值 可持久化Trie樹維護首碼xor,建樹的時候維護一下每個節點被遍歷了多少次 註意設置好偏移量,不然詢問區間為$[1, 1]$的時候可能掛掉 cpp incl ...
  • 函數 一、函數的創建 簡單格式 如果沒有寫return,函數會預設返回一個none 二、函數的參數 必需參數: 調用函數時必需參數須以正確的順序傳入,調用的數量必須和聲明時的一樣。 關鍵字參數: 使用關鍵字參數允許函數調用時參數的順序與聲明時不一致,因為 Python 解釋器能夠用參數名匹配參數值。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...