四種簡單的排序算法

四種簡單的排序算法

我覺得如果想成為一名優秀的開發者,不僅要積極學習時下流行的新技術,比如WCF、Asp.Net MVC、AJAX等,熟練應用一些已經比較成熟的技術,比如Asp.Net、WinForm。還應該有著牢固的計算機基礎知識,比如數據結構、操作系統、編譯原理、網絡與數據通信等。有的朋友可能覺得這方面的東西過于艱深和理論化,望而卻步,但我覺得假日里花上一個下午的時間,研究一種算法或者一種數據結構,然后寫寫心得,難道不是一件樂事么?所以,我打算將一些常見的數據結構和算法總結一下,不一定要集中一段時間花費很大精力,只是在比較空閑的時間用一種很放松的心態去完成。我最不愿意的,就是將寫博客或者是學習技術變為一項工作或者負擔,應該將它們視為生活中的一種消遣。人們總是說堅持不易,實際上當你提到“堅持”兩個字之時,說明你已經將這件事視為了一種痛苦,你的內心深處并不愿意做這件事,所以才需要堅持。你從不曾聽人說“我堅持玩了十年的電子游戲”,或者“堅持看了十年動漫、電影”、“堅持和心愛的女友相處了十年”吧?我從來不曾堅持,因為我將其視為一個愛好和消遣,就像許多人玩網絡游戲一樣。

好了,閑話就說這么多吧,我們回到正題。因為這方面的著作很多,所以這里只給出簡單的描述和實現,供我本人及感興趣的朋友參考。我會盡量用C#和C++兩種語言實現,對于一些不好用C#表達的結構,僅用C++實現。

本文將描述四種最簡單的排序方法,插入排序、泡沫排序、選擇排序、希爾排序,我在這里將其稱為“簡單排序”,是因為它們相對于快速排序、歸并排序、堆排序、分配排序、基數排序從理解和算法上要簡單一些。對于后面這幾種排序,我將其稱為“高級排序”。

簡單排序

開始之前先聲明一個約定,對于數組中保存的數據,統一稱為記錄,以避免和“元素”,“對象”等名稱相混淆。對于一個記錄,用于排序的碼,稱為關鍵碼。很顯然,關鍵碼的選擇與數組中記錄的類型密切相關,如果記錄為int值,則關鍵碼就是本身;如果記錄是自定義對象,它很可能包含了多個字段,那么選定這些字段之一為關鍵碼。凡是有關排序和查找的算法,就會關系到兩個記錄比較大小,而如何決定兩個對象的大小,應該由算法程序的客戶端(客戶對象)決定。對于.NET來說,我們可以創建一個實現了IComparer<T>的類(對于C++也是類似)。關于IComparer<T>的更多信息,可以參考這篇文章《基于業務對象的排序》。最后,為了使程序簡單,對于數組為空的情況我并沒有做處理。

1.插入排序

算法思想

插入排序使用了兩層嵌套循環,逐個處理待排序的記錄。每個記錄與前面已經排好序的記錄序列進行比較,并將其插入到合適的位置。假設數組長度為n,外層循環控制變量i由1至n-1依次遞進,用于選擇當前處理哪條記錄;里層循環控制變量j,初始值為i,并由i至1遞減,與上一記錄進行對比,決定將該元素插入到哪一個位置。這里的關鍵思想是,當處理第i條記錄時,前面i-1條記錄已經是有序的了。需要注意的是,因為是將當前記錄與相鄰的上一記錄相比較,所以循環控制變量的起始值為1(數組下標),如果為0的話,上一記錄為-1,則數組越界。

現在我們考察一下第i條記錄的處理情況:假設外層循環遞進到第i條記錄,設其關鍵碼的值為X,那么此時有可能有兩種情況:

  1. 如果上一記錄比X大,那么就交換它們,直到上一記錄的關鍵碼比X小或者相等為止。
  2. 如果上一記錄比X小或者相等,那么之前的所有記錄一定是有序的,且都比X小,此時退出里層循環。外層循環向前遞進,處理下一條記錄。

算法實現(C#)

public class SortAlgorithm {
??? // 插入排序
??? public static void InsertSort<T, C>(T[] array, C comparer)
??????? where C:IComparer<T>
??? {??????????
??????? for (int i = 1; i <= array.Length - 1; i++) {
??????????? //Console.Write("{0}: ", i);
??????????? int j = i;
??????????? while (j>=1 && comparer.Compare(array[j], array[j - 1]) < 0) {
??????????????? swap(ref array[j], ref array[j-1]);
??????????????? j--;
??????????? }
??????????? //Console.WriteLine();
??????????? //AlgorithmHelper.PrintArray(array);
??????? }
??? }

??? // 交換數組array中第i個元素和第j個元素
??? private static void swap<T>(ref T x,ref T y) {
??????? // Console.Write("{0}<-->{1} ", x, y);
??????? T temp = x;
??????? x = y;
??????? y = temp;
??? }
}

上面Console.WriteLine()方法和AlgorithmHelper.PrintArray()方法僅僅是出于測試方便,PrintArray()方法依次打印了數組的內容。swap<T>()方法則用于交換數組中的兩條記錄,也對交換數進行了打印(這里我注釋掉了,但在測試時可以取消對它們的注釋)。外層for循環控制變量i表示當前處理第i條記錄。

public class AlgorithmHelper {
??? // 打印數組內容
??? public static void PrintArray<T>(T[] array) {
??????? Console.Write("?? Array:");
??????? foreach (T item in array) {
??????????? Console.Write(" {0}", item);
??? ??? }
??????? Console.WriteLine();
??? }
}

// 獲得Comparer,進行比較
public class ComparerFactory {
??? public static IComparer<int> GetIntComparer() {
??????? return new IntComparer();
??? }

??? public class IntComparer : IComparer<int> {
??????? public int Compare(int x, int y) {
??????????? return x.CompareTo(y);
??????? }
??? }
}

上面這段代碼我們創建了一個ComparerFactory類,它用于獲得一個IntComparer對象,這個對象實現了IComparer<T>接口,規定了兩個int類型的關鍵碼之間比較大小的規則。如果你有自定義的類型,比如叫MyType,只需要在ComparerFactory中再添加一個類,比如叫MyTypeComparer,然后讓這個類也實現IComparer<T>接口,最后再添加一個方法返回MyTypeComparer就可以了。

輸出演示(C#)

接下來我們看一下客戶端代碼和輸出:

static void Main(string[] args) {
??? int[] array = {42,20,17,13,28,14,23,15};
??? //int[] array = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
??? AlgorithmHelper.PrintArray(array);

??? SortAlgorithm.InsertSort
??????? (array, ComparerFactory.GetIntComparer());
}

算法實現(C++)

// 對int類型進行排序
class IntComparer{
??? public:
??????? static bool Smaller(int x, int y){
??????????? return x<y;
??????? }
??????? static bool Equal(int x, int y){
??????????? return x==y;
??????? }
??????? static bool Larger(int x, int y){
??????????? return x>y;
??????? }
};

// 插入排序
template <class T, class C>
void InsertSort(T a[], int length){
??? for(int i=1;i<=length-1;i++){
??????? int j = i;
??????? while(j>=1 && C::Smaller(a[j], a[j-1])){
??????????? swap(a[j], a[j-1]);
??????????? j--;
??????? }
??? }
}

2.冒泡排序

算法思想

如果你從沒有學習過有關算法方面的知識,而需要設計一個數組排序的算法,那么很有可能設計出的就是泡沫排序算法了。因為它很好理解,實現起來也很簡單。它也含有兩層循環,假設數組長度為n,外層循環控制變量i由0到n-2遞增,這個外層循環并不是處理某個記錄,只是控制比較的趟數,由0到n-2,一共比較n-1趟。為什么n個記錄只需要比較n-1趟?我們可以先看下最簡單的兩個數排序:比如4和3,我們只要比較一趟,就可以得出3、4。對于更多的記錄可以類推。

數組記錄的交換由里層循環來完成,控制變量j初始值為n-1(數組下標),一直遞減到1。數組記錄從數組的末尾開始與相鄰的上一個記錄相比,如果上一記錄比當前記錄的關鍵碼大,則進行交換,直到當前記錄的下標為1為止(此時上一記錄的下標為0)。整個過程就好像一個氣泡從底部向上升,于是這個排序算法也就被命名為了冒泡排序。

我們來對它進行一個考察,按照這種排序方式,在進行完第一趟循環之后,最小的一定位于數組最頂部(下標為0);第二趟循環之后,次小的記錄位于數組第二(下標為1)的位置;依次類推,第n-1趟循環之后,第n-1小的記錄位于數組第n-1(下標為n-2)的位置。此時無需再進行第n趟循環,因為最后一個已經位于數組末尾(下標為n-1)位置了。

算法實現(C#)

// 泡沫排序
public static void BubbleSort<T, C>(T[] array, C comparer)
??? where C : IComparer<T>
{
??? int length = array.Length;

??? for (int i = 0; i <= length - 2; i++) {
??????? //Console.Write("{0}: ", i + 1);
??????? for (int j = length - 1; j >= 1; j--) {
??????????? if (comparer.Compare(array[j], array[j - 1]) < 0) {
??????????????? swap(ref array[j], ref array[j - 1]);
??????????? }
??????? }
??????? //Console.WriteLine();
??????? //AlgorithmHelper.PrintArray(array);
??? }
}

輸出演示(C#)

static void Main(string[] args) {
??? int[] array = {42,20,17,13,28,14,23,15};
??? AlgorithmHelper.PrintArray(array);

??? SortAlgorithm.BubbleSort
??????? (array, ComparerFactory.GetIntComparer());
}

算法實現(C++)

// 冒泡排序
template <class T, class C>
void BubbleSort(T a[], int length){
??? for(int i=0;i<=length-2;i++){
??????? for(int j=length-1; j>=1; j--){
??????????? if(C::Smaller(a[j], a[j-1]))
??????????????? swap(a[j], a[j-1]);
??????? }
??? }
}

3.選擇排序

算法思想

選擇排序是對冒泡排序的一個改進,從上面冒泡排序的輸出可以看出,在第一趟時,為了將最小的值13由數組末尾冒泡的數組下標為0的第一個位置,進行了多次交換。對于后續的每一趟,都會進行類似的交換。

選擇排序的思路是:對于第一趟,搜索整個數組,尋找出最小的,然后放置在數組的0號位置;對于第二趟,搜索數組的n-1個記錄,尋找出最小的(對于整個數組來說則是次小的),然后放置到數組的第1號位置。在第i趟時,搜索數組的n-i+1個記錄,尋找最小的記錄(對于整個數組來說則是第i小的),然后放在數組i-1的位置(注意數組以0起始)。可以看出,選擇排序顯著的減少了交換的次數。

需要注意的地方是:在第i趟時,內層循環并不需要遞減到1的位置,只要循環到與i相同就可以了,因為之前的位置一定都比它小(也就是第i小)。另外里層循環是j>i,而不是j>=i,這是因為i在進入循環之后就被立即保存到了lowestIndex中。

算法實現(C#)

public static void SelectionSort<T, C>(T[] array, C comparer)
??? where C : IComparer<T>
{
??? int length = array.Length;
??? for (int i = 0; i <= length - 2; i++) {
??????? Console.Write("{0}: ", i+1);
??????? int lowestIndex = i;??????? // 最小記錄的數組索引
??????? for (int j = length - 1; j > i; j--) {
??????????? if (comparer.Compare(array[j], array[lowestIndex]) < 0)
??????????????? lowestIndex = j;
??????? }
??????? swap(ref array[i], ref array[lowestIndex]);
??????? AlgorithmHelper.PrintArray(array);
??? }
}

輸出演示(C#)

static void Main(string[] args) {
??? int[] array = {42,20,17,13,28,14,23,15};
??? AlgorithmHelper.PrintArray(array);

??? SortAlgorithm.SelectionSort
??????? (array, ComparerFactory.GetIntComparer());
}

算法實現(C++)

// 選擇排序
template <class T, class C>
void SelectionSort(T a[], int length) {
??? for(int i = 0; i <= length-2; i++){
??????? int lowestIndex = i;
??????? for(int j = length-1; j>i; j--){
??????????? if(C::Smaller(a[j], a[lowestIndex]))
??????????????? lowestIndex = j;
??????? }
??????? swap(a[i], a[lowestIndex]);
??? }
}

4.希爾排序

希爾排序利用了插入排序的一個特點來優化排序算法,插入排序的這個特點就是:當數組基本有序的時候,插入排序的效率比較高。比如對于下面這樣一個數組:

int[] array = { 1, 0, 2, 3, 5, 4, 8, 6, 7, 9 };

插入排序的輸出如下:

可以看到,盡管比較的趟數沒有減少,但是交換的次數卻明顯很少。希爾排序的總體想法就是先讓數組基本有序,最后再應用插入排序。具體過程如下:假設有數組int a[] = {42,20,17,13,28,14,23,15},不失一般性,我們設其長度為length。

第一趟時,步長step = length/2 = 4,將數組分為4組,每組2個記錄,則下標分別為(0,4)(1,5)(2,6)(3,7);轉換為數值,則為{42,28}, {20,14}, {17,23}, {13,15}。然后對每個分組進行插入排序,之后分組數值為{28,42}, {14,20}, {17,23}, {13,15},而實際的原數組的值就變成了{28,14,17,13,42,20,23,15}。這里要注意的是分組中記錄在原數組中的位置,以第2個分組{14,20}來說,它的下標是(1,5),所以這兩個記錄在原數組的下標分別為a[1]=14;a[5]=20。

第二趟時,步長 step = step/2 = 2,將數組分為2組,每組4個記錄,則下標分別為(0,2,4,6)(1,3,5,7);轉換為數值,則為{28,17,42,23}, {14,13,20,15},然后對每個分組進行插入排序,得到{17,23,28,42}{13,14,15,20}。此時數組就成了{17,13,23,14,28,15,42,20},已經基本有序。

第三趟時,步長 step=step/2 = 1,此時相當進行一次完整的插入排序,得到最終結果{13,14,15,17,20,23,28,42}。

算法實現(C#)

// 希爾排序
public static void ShellSort<T, C>(T[] array, C comparer)
??? where C : IComparer<T>
{
??? for (int i = array.Length / 2; i >= 1; i = i / 2) {
??????? Console.Write("{0}: ", i);
??????? for (int j = 0; j < i; j++) {
??????????? InsertSort(array, j, i, comparer);
??????? }
??????? Console.WriteLine();
??????? AlgorithmHelper.PrintArray(array);
??? }
}

// 用于希爾排序的插入排序
private static void InsertSort<T, C>
??? (T[] array, int startIndex, int step, C comparer)
??? where C : IComparer<T>
{
??? for (int i = startIndex + step; i <= array.Length - 1; i += step) {
??????? int j = i;
??????? while(j>= step && comparer.Compare(array[j], array[j - step]) <0 ){
??????????? swap(ref array[j], ref array[j - step]);
??????????? j -= step;
??????? }
??? }
}

注意這里插入排序InsertSort()方法的參數,startIndex是分組的起始索引,step是步長,可以看出,前面的插入排序只是此處step=1,startindex=0的一個特例

輸出演示(C#)

static void Main(string[] args) {
??? int[] array = {42,20,17,13,28,14,23,15};
??? AlgorithmHelper.PrintArray(array);

??? SortAlgorithm.ShellSort
??????? (array, ComparerFactory.GetIntComparer());
}

算法實現(C++)

// 希爾排序
template<class T, class C>
void ShellSort(T a[], int length){
??? for(int i = length/2; i >= 1; i = i/2 ){
??????? for(int j = 0; j<i; j++){
??????????? InsertSort<T, C>(&a[j], length-1, i);
??????? }
??? }
}

// 用于希爾排序的插入排序
template<class T, class C>
void InsertSort(T a[], int length, int step){
??? for(int i = step; i<length; i+= step){
??????? int j = i;
??????? while(j>=step && C::Smaller(a[j], a[j-step])){
??????????? swap(a[j], a[j-step]);
??????????? j-=step;
??????? }
??? }
}

對于上面三種算法的代價,插入排序、冒泡排序、選擇排序,都是Θ(n2),而希爾排序略好一些,是Θ(n1.5),關于算法分析,大家感興趣可以參考相關書籍。這里推薦《數據結構與算法分析(C++版)第二版》和《算法I~IV(C++實現)——基礎、數據結構、排序和搜索》,都很不錯,我主要也是參考這兩本書。

感謝閱讀,希望這篇文章可以給你帶來幫助。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/280850.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/280850.shtml
英文地址,請注明出處:http://en.pswp.cn/news/280850.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Xampp修改默認端口號

為什么80%的碼農都做不了架構師&#xff1f;>>> Xampp默認的端口使用如下&#xff1a; Httpd使用80端口 Httpd_ssl使用443端口 Mysql使用3306端口 ftp使用21端口 但是&#xff0c;在如上端口被占用的情況下&#xff0c;我們可以通過修改xampp默認端口的方法&…

為什么csrss進程有三個_什么是客戶端服務器運行時進程(csrss.exe),為什么在我的PC上運行它?...

為什么csrss進程有三個If you have a Windows PC, open your Task Manager and you’ll definitely see one or more Client Server Runtime Process (csrss.exe) processes running on your PC. This process is an essential part of Windows. 如果您使用的是Windows PC&…

使用c#的 async/await編寫 長時間運行的基于代碼的工作流的 持久任務框架

持久任務框架 &#xff08;DTF&#xff09; 是基于async/await 工作流執行框架。工作流的解決方案很多&#xff0c;包括Windows Workflow Foundation&#xff0c;BizTalk&#xff0c;Logic Apps, Workflow-Core 和 Elsa-Core。最近我在Dapr 的倉庫里跟蹤工作流構建塊的進展時&a…

bat批處理筆記

變量 1.CMD窗口變量&#xff0c;變量名必須用單%引用&#xff08;即&#xff1a;%variable&#xff09; 外部變量&#xff0c;是系統制定的&#xff0c;只有9個&#xff0c;專門保存外部參數的&#xff0c;就是運行批處理時加的參數。只有 %1 %2 %3 %4 ...... %9。 在bat內直…

多目標跟蹤(MOT)論文隨筆-SIMPLE ONLINE AND REALTIME TRACKING (SORT)

轉載請標明鏈接&#xff1a;http://www.cnblogs.com/yanwei-li/p/8643336.html 網上已有很多關于MOT的文章&#xff0c;此系列僅為個人閱讀隨筆&#xff0c;便于初學者的共同成長。若希望詳細了解&#xff0c;建議閱讀原文。 本文是使用 tracking by detection 方法進行多目標…

明日大盤走勢分析

如上周所述&#xff0c;大盤在4與9號雙線壓力下&#xff0c;上攻乏力。今天小幅下跌0.11%&#xff0c;漲511&#xff0c;平76&#xff0c;跌362&#xff0c;說明個股還是比較活躍&#xff0c;而且大盤上漲趨勢未加改變&#xff0c;只是目前攻堅&#xff0c;有點缺乏外部的助力。…

android EventBus 3.0 混淆配置

2019獨角獸企業重金招聘Python工程師標準>>> https://github.com/greenrobot/EventBus 使用的這個庫在github的官網README中沒有寫明相應混淆的配置. 經過對官網的查詢&#xff0c;在一個小角落還是被我找到了。 -keepattributes *Annotation* -keepclassmembers …

dotnet-exec 0.11.0 released

dotnet-exec 0.11.0 releasedIntrodotnet-exec 是一個 C# 程序的小工具&#xff0c;可以用來運行一些簡單的 C# 程序而無需創建項目文件&#xff0c;讓 C# 像 python/nodejs 一樣簡單&#xff0c;而且可以自定義項目的入口方法&#xff0c;支持但不限于 Main 方法。Install/Upd…

C# 讀取文件內容/輸出txt log

逐行讀 jsonString string.Empty;if (File.Exists(jsonFile)){StreamReader sr new StreamReader(jsonFile, Encoding.UTF8);string line string.Empty;while ((line sr.ReadLine()) ! null){jsonString line;}sr.Close();} 全讀取 string text File.ReadAllText("…

樹形dp-CF-337D. Book of Evil

題目鏈接&#xff1a; http://codeforces.com/problemset/problem/337/D 題目大意&#xff1a; 給一棵樹&#xff0c;m個點&#xff0c;一個距離d&#xff0c;求有多少個點A,使得A到所有的m個點距離都不超過d. 解題思路&#xff1a; 樹形dp. 有兩種方法可以解&#xff1a; 1、類…

運行時獲取類庫信息

運行時獲取類庫信息Intro在我們向別的開源項目提 issue 的時候&#xff0c;可能經常會遇到別人會讓我們提供使用的版本信息&#xff0c;如果別的開源項目類庫集成了 source link&#xff0c;我們可以從程序集信息中獲取到版本以及對應的 commit 信息&#xff0c;這樣我們就可以…

Oracle數據表中輸入引號等特殊字符

Oracle輸入特殊字符的特殊方法: UPDATE BOOKMARK SET BM_VALUEq/ --在這里寫下需要輸入的內容&#xff08;可以包括引號、回車等特殊的符號&#xff09;,所見即所得 / -- WHERE BM_NAMEXX

xbox360鏈接pc_如何將實時電視從Xbox One流式傳輸到Windows PC,iPhone或Android Phone

xbox360鏈接pcSet up your Xbox One’s TV integration and you can do more than just watch TV on your Xbox: you can also stream that live TV from your Xbox to a Windows 10 PC, Windows phone, iPhone, iPad, or Android device over your home network. 設置Xbox One…

PS2019工具介紹筆記(一)

通用快捷鍵 ALT鼠標滾輪放大縮小 空格按左鍵 移動圖片 一、新建 PPI 顯示器72PPI 印刷(國際通用分辨率)300PPI 海報高清寫真96-200PPI 大型噴繪25-50PPI 顏色模式 RGB(紅綠藍) CMYK(青洋紅黃黑)印刷業 二、移動工具 ctrlT 圖形自由變換 alt…

WPF ABP框架更新日志(最新2022-11月份)

更新說明本次更新內容包含了WPF客戶端以及Xamarin.Forms移動端項目, 更新內容總結如下:WPF 客戶端修復啟動屏幕無法跳轉異常修復添加好友異常修復托盤圖標狀態更新異常優化好友發送消息時狀態檢測更新聊天窗口UI風格更新好友列表得頭像顯示更新聊天窗口消息日期分組顯示更新系統…

JSONObject和JSONArray 以及Mybatis傳入Map類型參數

import org.json.JSONArray;import org.json.JSONObject;將字符串轉化為JSONArray JSONArray jsonArray new JSONArray(deviceInfo); //注意字符串的格式將JSONArray轉化為JSONObject類型 JSONObject jsonObject jsonArray.getJSONObject(0);將值存入Map Map<String,S…

十月cms_微軟十月更新失敗使整個PC行業陷入困境

十月cmsMicrosoft still hasn’t re-released Windows 10’s October 2018 Update. Now, PC manufacturers are shipping PCs with unsupported software, and Battlefield V is coming out next week with real-time ray-tracing technology that won’t work on NVIDIA’s RT…

ab 測試工具

ab&#xff0c;即Apache Benchmark&#xff0c;在Apache的安裝目錄中找到它。安裝目錄/bin/ab.exe。ab -n 數字 -c 數字 url路徑我們對位于本地Apache服務器上、URL為localhost/index.php的頁面進行壓力測試。測試總次數為1000&#xff0c;并發數為100(相當于100個用戶同時訪問…

bat批處理筆記(二)

eof 是“end of file”的縮寫 在批處理作用主要有二&#xff1a; 1、在無call的情況下&#xff0c;會直接退出批處理&#xff0c;此時等同于exit 2、在call的情況下&#xff0c;會中止call&#xff0c;繼續執行其他命令 echo off call :str1 pause goto :eof echo //此行代…

讓Visual Studio 2013為你自動生成XML反序列化的類

Visual Sutdio 2013增加了許多新功能&#xff0c;其中很多都直接提高了對代碼編輯的便利性。如&#xff1a; 1. 在代碼編輯界面的右側滾動條上顯示不同顏色的標簽&#xff0c;讓開發人員可以對所編輯文檔的修改、查找、定位情況一目了然。而不用像往常一樣上下不停地拖動滾動條…