程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 深入淺出排序算法,深入淺出算法

深入淺出排序算法,深入淺出算法

編輯:關於C語言

深入淺出排序算法,深入淺出算法


    最近在忙著准備找工作,對於碼農來說,找工作之前必備的就是各種的排序算法,其中包括算法實現、復雜度分析。於是我也開始研究各種排序算法,但是看完幾遍之後發現,其原理並不復雜,於是就在思考,這些算法這麼重要,那麼它們在實際解決問題時如何來使用呢?這篇文章我就個人的理解,盡量形象、簡單的描述各種基本排序算法的原理,並對其復雜度進行了相應的分析,最後說明其在實際使用中的應用。我希望我的這篇文章盡量的通俗形象,讓我們展開想象的空間吧!

一、算法原理分析:

 1、冒泡排序:

  首先從它的名字上來理解它的原理,假設一個湖底從左到右並排了10(n)個泡泡,我們在湖底從左向右游動,每次只比較相鄰的兩個泡泡,如果左邊的比右邊的小則交換其位置,這樣游玩了一趟之後,即比較了9(n-1)次,最大的那個就得到了,這時我們將這個泡泡冒出水面,以此類推,我們需要這樣重復9(n-1)次即可(考慮最後只剩下兩個),每一次需要比較(n-i)次,j為跑了的次數。

偽代碼如下:

    for i = 1:n-1 (n-1趟)

      for j = 1:n-i(比較n-i次)

        if a[j] > a[j + 1]

          swap(a[j],a[j+1])

        endif

      endfor

    endfor

當然由於數組元素從0開始,則編程時只需要將循環的首尾均減一即可。

復雜度分析:本算法共有兩個循環,外循環共(n-1)個,內循環共(n-i)個,則總數為(n-1)+(n-2)+...+1 = [n*(n-1)]/2=O(n^2).

 2、插入排序:

    本算法也從其名字來理解,這次我們想象一下學生們排隊好了,每個學生的身高都不同,現在希望他們能夠快速的按照從矮到高的順序排好隊。我們的策略是插入的人都和隊伍中的人挨個比,如果比隊伍中的人矮,那就將隊伍中的人向後移動一個位置,一直到合適的位置將其插入進去。這時候我們假設第一個元素已經在數組中了(因為沒人和他比),這時我們只需要將剩下的n-1個人插入到隊伍中就好了。假設隊伍中已經插入了i個人,則第i+1個人需要和隊伍中的i個人比才可以。

  for i = 2:n  //插入第i個人

    key = a[i]; //設其為關鍵字用來跟每一個位置進行比較

    j = i-1;

    for j = i-1:1 //關鍵字和前面i-1個人比較

        if a[j] > key //大於關鍵字則將其向後移動一個,空一個空出來

      a[j+1] = a[j];

     else   //如果key大於隊伍中的值時就退出

      break;

     endif

    endfor

    a[j+1] = key;//將其插入到該值的後面

  endfor

復雜度分析:該算法也包含兩層循環,外層有n-1個元素,內層有i-1個元素,則共有1+2+...+n-1 = [n*(n-1)]/2 = O(n^2)

 

稍微休息一下,縱觀以上兩個算法,雖然算法不同,但是都得需要循環比較,在相對有序時,快速排序的內層循環可以快速退出,因此其效果相對好一點,而冒泡排序還是從頭比較。

 3、歸並排序:

  看了兩個循環嵌套的算法,下面看看遞歸的算法吧。歸並排序是典型的具有歸並性質的算法。先來說說它的思想,歸並,換言之就是合並,既然要合並那就得先把原來的數組分開吧。咱們還是接著上面的例子,我們先將隊伍分成兩組,然後對兩組排序,排序後進行合並。分成的兩組其實又可以繼續分為兩組並合並,因此此處就可以使用遞歸的方法。

 

Merge://合並的代碼

input:a[]

n1 = med;

n2 = n-med;

L[] = a[1:n1];

R[] = a[med+1:n];

L[n1+1] = 65535;//哨兵牌,每次到這之後只將剩下的那一摞放到數組中即可

R[n2+1] = 65535;

 

i = 1:n1;

j = 1:n2;

 for k = 1:n

  if(L[i] < R[j])

    a[k] = L[i]

    i++;

  else

    a[k] = R[j]

    j++;

  endif

endfor

MergeSort:

  q = n/2;

  MergeSort(a,1,q);

  MergeSort(a,q+1,n);

  Merge(a,1,q,n);

復雜度分析:對於遞歸調用的復雜度分析,要明白主方法:T(n)=aT(n/b)+f(n) 要掌握三種情況:1)如果f(n)比n^(log(b)a)小,則T(n) = Ot(n^(log(b)a));2)如果f(n)比n^(log(b)a)大,則T(n)= ot(f(n));3)如果f(n)和n^(log(b)a)差不多大,則T(n)= ot(n^(log(b)a) * lgn)。

在此算法中,a = 2,b = 2;故 n^(log(b)a) = n = f(n),所以滿足3),故T(n)= O(nlgn)。雖然該算法時間很快,但是需要使用兩個數組來存儲L和R,典型的空間換取時間的手法。

4、快速排序:

  聽到這個名字,相必大家很快就覺得這個算法肯定很快,事實上也確實如此。該算法也采用了分而治之以及遞歸的思想。其主要的思想是:先隨機選擇一個關鍵人物作為標准,將比他矮的放到左邊,比他高的放到右邊。然後再在剩下的兩組中按照此種方法進行遞歸。

Partition://分開確定pivot的位置,方便將其分開遞歸排序

  pivote = a[1];//將第一個人作為標准

  p_low = 1;

  for i = 2:n

    if a[i]<pivote

     swap(a[i],a[p_low]);

     p_low++;

     endif

     endfor

return p_low;

QuickSort:

  p = Partition(a,1,n);  

  QuickSort(a,1,p-1);

  QuickSort(a,p+1,n);

復雜度分析:類似於歸並排序,該算法也將原問題分成兩部分,T(n)=  T(n/p)+T(n/(1-p))+n,其最好情況T(n)=2T(n/2)+n=O(nlgn),最壞的情況是T(n)= T(n-1)+O(n),T(n) = O(n^2);而對於不均分的情況,分析得出T(n)=O(nlgn)。

                該算法雖然和快速排序算法的復雜度一樣,但是該算法在基本排好序的情況下屬於最壞的情況,因此使用時需謹慎。

5、堆排序:

  下面就來說一說堆排序,堆排序相對於上面幾種排序稍微麻煩一點,但是只要掌握其精髓,也是很容易理解的。這個舉實際的例子確實不太好舉,但是大家可以想象出一棵二叉樹。

  堆排序,主要利用了最大堆和最小堆,其本質就是棵二叉樹。

  該排序過程大概可以分成如下三步:1)建立一個二叉樹  2)將堆變為最大堆(即將最大值轉移到堆頂),從最後一個非葉子節點開始  3)將堆頂元素和最後一個元素互換,然後調整堆,使其滿足最大堆(堆長度在變)

 1)此處不需要什麼操作,只是提醒大家數組中的元素在樹中的位置為前序遍歷存儲(根左右)

2)

BuildMaxHeap(a,n):

  第一個非葉子節點為:n/2

  for i = n/2:1

    HeapAjust(a,i,n);

  end

HeapAjust(a,i):調整為最大堆

  l=LEFT(i);//左子樹

  r = RIGHT(i);//右子樹

  if l<n && a[l] > a[i]

    largest = l;

  endif

  if r<n && a[r] >a[largest]

    largest = r;

  endif

  if largest != i

    swap(a[i],a[largest]);

    HeapAjust(a,largest);

  endif

HeapSort(a,n)

  BuildMaxHeap(a,n);

  for i  = n:2

    swap(a[1],a[i];

    HeapAjust(a,1,i-1);

  endfor

復雜度分析:該算法主要包括兩部分,第一部分是堆調整O(lgn),建最大堆時,共調用(n/2)次,故其為O(nlgn).堆排序時主要包括O(nlgn)+ O(nlgn) = O(nlgn)

堆排序不需要大量的遞歸或者多維的暫存數組。這對於數據量非常巨大的序列是合適的。比如超過數百萬條記錄,因為快速排序,歸並排序都使用遞歸來設計算法,在數據量非常大的時候,可能會發生堆棧溢出錯誤。但是快速的時間一般線性增長,但是堆排序為nlgn的速度。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved