排序演算法分析

来源:http://www.cnblogs.com/toocooltohavefriends/archive/2016/04/03/5349375.html
-Advertisement-
Play Games

我們都知道,在應屆面試的時候,問到最多的就是快速排序,快速排序是最經典、最常用的排序演算法,因為它的平均效率最優,也最穩定。 快速排序使用了分治的演算法思想,分治演算法本身理解起來很符合人類的思路(遞歸是很容易被理解的),其最關鍵的一步,就是劃分了,演算法導論上介紹了一種劃分方法,和我在數據結構課上學的略有 ...


我們都知道,在應屆面試的時候,問到最多的就是快速排序,快速排序是最經典、最常用的排序演算法,因為它的平均效率最優,也最穩定。

快速排序使用了分治的演算法思想,分治演算法本身理解起來很符合人類的思路(遞歸是很容易被理解的),其最關鍵的一步,就是劃分了,演算法導論上介紹了一種劃分方法,和我在數據結構課上學的略有不同,昨晚,我把它們全都用java實現了。

這個是演算法導論的版本:

 1 /**
 2      * 快速排序的劃分方法一:演算法導論版本 sit的下一個比最後一個大
 3      * 
 4      * @param begin
 5      * @param end
 6      * @return
 7      */
 8     private int QuickMergeA(int begin, int end) {
 9         System.out.print("初始:" + arr);
10         System.out.print(begin + " " + end);
11         int anchor = (int) arr.get(end);
12         int sit = begin - 1;
13         int temp;
14         for (int i = begin; i < end; i++) {
15             if ((int) arr.get(i) <= anchor) {
16                 sit++;
17                 temp = (int) arr.get(i);
18                 arr.set(i, (int) arr.get(sit));
19                 arr.set(sit, temp);
20             }
21         }
22         temp = (int) arr.get(sit + 1);
23         arr.set(sit + 1, anchor);
24         arr.set(end, temp);
25         System.out.print(arr);
26         System.out.println(sit + 1);
27         return sit + 1;
28     }

不難看出,這個版本的劃分思路是,從數組一端往另一端推進,對於比指定元素小的值,均放在左側,比指定元素大的值,放在右側。

然後我們再看我數據結構課本上學過的版本:

/**
     * 快速排序的劃分方法二:數據結構課本版本
     * 
     * @param begin
     * @param end
     * @return
     */
    private int QuickMergeB(int begin, int end) {
        System.out.print("初始:" + arr);
        System.out.print(begin + " " + end);
        int anchor = arr.get(end);
        int send = end - 1;
        int temp;
        int tbegin = begin;
        while (begin <= send) {
            while (arr.get(begin) <= anchor && begin <= end - 1) {
                begin++;
            }
            while (send >= tbegin && arr.get(send) > anchor) {
                send--;
            }
            if (begin < send) {
                temp = arr.get(begin);
                arr.set(begin, arr.get(send));
                arr.set(send, temp);
            }
            if (begin >= send) {
                temp = arr.get(begin);
                arr.set(begin, arr.get(end));
                arr.set(end, temp);
            }
        }
        System.out.print(arr);
        System.out.println(send + 1);
        return send + 1;
    }

可以看出,這種思路是兩端推進的手段,兩端同時推進,同樣是左邊存放比指定元素小的值,右邊存放比指定元素大的值。

 

這兩種方法遍曆數組的推進方法雖然不同,但是其效率是一致的,劃分一次都相當於是要遍歷一次子數組。其劃分的包裝入口也是相似的:

/**
     * 快速排序入口
     * 
     * @param begin
     * @param end
     */
    private void QuickSequence(int begin, int end) {
        // if (begin < end) {
        // int q = QuickMergeA(begin, end);
        // this.QuickSequence(begin, q - 1);
        // this.QuickSequence(q + 1, end);
        // }
        if (begin < end) {
            int q = QuickMergeB(begin, end);
            this.QuickSequence(begin, q - 1);
            this.QuickSequence(q + 1, end);
        }

    }

需要指明的是,測試上面方法的過程中,應該把arr數組設置為靜態的,在java中,實現類似於C++的地址訪問的情況,我經常使用靜態變數。

 

最古老的排序演算法是插入排序,之前學習數據結構的時候,對於各種排序演算法總是混淆,比如對於快速排序和插入排序的演算法思路都明白,但是總是把快速排序當成插入排序,插入排序當成快速排序。也許是當時兩節課學了冒泡排序、快速排序、插入排序、堆排序等太多的排序演算法吧,加上編程經驗少造成的。

 

其實插入排序很容易和快速排序區分開。因為插入排序是真的“插入”。插入排序的基礎是左側數據已經排序準確,你要做的是把下一個數插入到有序數組的指定位置,你可以腦補撲克牌。

/**
     * 插入排序
     */
    private void InsertSequence() {
        for (int i = 0; i < arr.size(); i++) {
            int p = 0;
            while (p < i && arr.get(p) < arr.get(i)) {
                p++;
            }
            if (p < i) {
                int temp = arr.get(i);
                for (int k = i; k > p; k--) {
                    arr.set(k, arr.get(k - 1));
                }
                arr.set(p, temp);
            }
        }
    }

插入排序的代碼很短,但是我們的經驗是,短的代碼代表思路簡單,不代表效率高。其實是這樣的,插入排序的效率是N^2,而快速排序的效率則是NlogN。插入排序是最朴素的排序演算法,也是相對較慢的排序演算法。

 

學數據結構的課程時,我非常喜歡堆排序。或許是因為它是最形象的排序演算法。其實,堆排序利用特有的數據結構,高效的做了這麼一件事,每次找出最小的數,然後從數組中刪掉這個數,繼續查找。

 1 private void MaintainHeap(int length) {
 2         int temp;
 3         for (int i = length / 2 - 1; i >= 0; i--) {
 4             if ((arr.get(i) < arr.get((i + 1) * 2 - 1))
 5                     || ((i + 1) * 2 < length && arr.get(i) < arr
 6                             .get((i + 1) * 2))) {
 7                 if ((i + 1) * 2 < length) {
 8                     if (arr.get((i + 1) * 2) < arr.get((i + 1) * 2 - 1)) {
 9                         temp = arr.get(i);
10                         arr.set(i, arr.get((i + 1) * 2 - 1));
11                         arr.set((i + 1) * 2 - 1, temp);
12                     } else {
13                         temp = arr.get(i);
14                         arr.set(i, arr.get((i + 1) * 2));
15                         arr.set((i + 1) * 2, temp);
16                     }
17 
18                 } else {
19                     temp = arr.get(i);
20                     arr.set(i, arr.get((i + 1) * 2 - 1));
21                     arr.set((i + 1) * 2 - 1, temp);
22                 }
23 
24             }
25         }
26 
27         System.out.println(arr);
28     }
29 
30     private void Heapsequence(int length) {
31         for (int i = length; i > 0; i--) {
32             this.MaintainHeap(i);
33             int temp = arr.get(0);
34             arr.set(0,arr.get(i - 1));
35             arr.set(i - 1, temp);
36         }
37     }

堆排序的重要步驟是堆的維護。對於最大堆而言,要確保特定個元素構建的堆中,每個節點的值都大於其孩子節點,這通常的方法是,從最後一個有子節點的節點開始,遍歷到根節點,對於這其間的每一個結點,如果其子節點存在大於當前節點的節點,則把最大的子節點同當前節點交換,並同時維護交換後的子節點的狀態(這個子節點的子節點可能在交換後比子節點的值大)。

 

除了上面的排序演算法,我們常見的還有冒泡排序之類。同時,我在演算法導論上,還接觸過計數排序、基數排序、桶排序等線性排序演算法。因為其思路比較簡單,所以這裡只給出一般思路:

計數排序顧名思義,使用了一個計數數組,對於每一個值,初始計數為0,每新增一個,該計數就增加一,最後形成一個排序序列。計數排序的效率一般,但是由於其穩定,常常作為其它排序演算法基本過程的演算法使用。

基數排序是另外一種比較有意思的排序演算法,它先從最低位對數據進行排序,然後再依次上升到最高位,而對每個數位的排序,他通常使用計數排序。

桶排序是效率最高的排序演算法,但是它對數據要求較高,同時是一種以空間換時間的排序演算法。它的方案是先new一個足夠大的數組,然後對待排序數組遍歷,將新數組鍵等於待排序數組中值的位置置為一(多個則繼續增一),然後遍歷新數組,即可得到待排序數組的順序。這種演算法不適合離散性數據,也不適合不知道範圍的數據排序。

 

以上。

 


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

-Advertisement-
Play Games
更多相關文章
  • 今天有意的在博客園裡面搜索了一下 Z.ExtensionMethods 這個擴展類庫,確發現只搜到跟這個真正相關的才兩篇博文而已,我都點進去看了一下,也都只是提到而已,沒有專門介紹,才引起我寫這篇文檔。 一. Z.ExtensionMethods 介紹 Z.ExtensionMethods 是國外( ...
  • ...
  • 2016-04-03 實現對二維數組排序❖ 對購物車商品表格實現:按數量,按單價分別降序/升序排序。❖ 查閱參考手中, usort( )函數的說明。 要求效果圖如下: 註:此處下方實際應有四個按鈕,分別控制四種不同的排序,因為一些特殊的原因無法給出。 實現代碼如下: sort-cart.php: 這 ...
  • 環境:windows 7 64位;python2.7;IDE pycharm2016.1 功能: 批量下載百度貼吧某吧某頁的所有帖子中的所有圖片 使用方法: 1.安裝python2.7,安裝re模塊,安裝urllib2模塊 2.複製以下源代碼保存為tbImgiDownloader.py文件 3.打開 ...
  • 一、刪除首碼 '*' 1 #include<iostream> 2 #include<cstdio> 3 4 using namespace std; 5 6 //主函數 7 int main() 8 { 9 char chr[20],*b,*p; //字元串緩衝區;字元串頭指針;字元串臨時指針 1 ...
  • 在使用Json傳值並且使用@RequestBody註解的時候需要註意一些問題: 第一條容易理解,因為RequestBody就是request的inputStream,這個流在第一次使用該註解後會關閉,後面的都會報錯(stream closed)。 第二條如果沒有包含前臺傳來的欄位,就會報錯:Unre ...
  • 簡介:本文主要講:函數的定義,外部參數的用處,無返回類型的三種函數定義方式 閉包的定義,閉包的概念和用法,尾隨閉包的寫法,解除迴圈引用的方法 一、函數: 代碼實現 函數的定義 格式 func 函數名(行參列表) -> 返回值 {代碼實現} 調用 let result = 函數名(值1, 參數2: 值 ...
  • c 和 c++ 最大的特點就是對記憶體的自由操作,數據類型,其實都是對記憶體的一種解釋方式。C語言中常用的一個技巧就是尾隨數據,網路編程中經常會用到這個特性, 特別是以前寫完成埠的時候,這個特性肯定是會用到,跟IOCP的API特性相關。c++中也有類似的new也可以使用。 e1:尾隨記憶體與指針解釋 輸 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...