排序算法

概述

排序分爲內部排序和外部排序,內部排序是數據記錄在內存中進行的排序,而外部排序是因爲數據量較大而不得不借助於外存來容納所有的排序記錄,在排序的過程當中須要訪問外存。前端

咱們這裏常說的排序指的是內部排序。排序算法分爲三類即插入排序、交換排序、選擇排序。算法

  1. 插入排序:直接插入排序、折半插入排序、希爾排序
  2. 交換排序:冒泡排序、快速排序
  3. 選擇排序:直接選擇排序、堆排序

衡量排序算法的幾個重要依據

穩定性

假定在待排序的記錄序列中,存在多個具備相同的關鍵字的記錄,若通過排序,這些記錄的相對次序保持不變,即在原序列中,ri=rj,且ri在rj以前,而在排序後的序列中,ri仍在rj以前,則稱這種排序算法是穩定的;不然稱爲不穩定的。shell

排序算法的穩定性是衡量排序算法好壞的很是重要的一個依據。後端

時間複雜度

概念

1) 時間頻度 一個算法執行所耗費的時間,從理論上是不能算出來的,必須上機運行測試才能知道。但咱們不可能也沒有必要對每一個算法都上機測試,只需知道哪一個算法花費的時間多,哪一個算法花費的時間少就能夠了。而且一個算法花費的時間與算法中語句的執行次數成正比例,哪一個算法中語句執行次數多,它花費時間就多。一個算法中的語句執行次數稱爲語句頻度或時間頻度。記爲T(n)。數組

2)時間複雜度 在剛纔提到的時間頻度中,n稱爲問題的規模,當n不斷變化時,時間頻度T(n)也會不斷變化。但有時咱們想知道它變化時呈現什麼規律。爲此,咱們引入時間複雜度概念。 通常狀況下,算法中基本操做重複執行的次數是問題規模n的某個函數,用T(n)表示,如有某個輔助函數f(n),使得當n趨近於無窮大時,T(n)/f(n)的極限值爲不等於零的常數,則稱f(n)是T(n)的同數量級函數。記做T(n)=O(f(n)),稱O(f(n)) 爲算法的漸進時間複雜度,簡稱時間複雜度。bash

在各類不一樣算法中,若算法中語句執行次數爲一個常數,則時間複雜度爲O(1),另外,在時間頻度不相同時,時間複雜度有可能相同,如T(n)=n2+3n+4與T(n)=4n2+2n+1它們的頻度不一樣,但時間複雜度相同,都爲O(n2)。 按數量級遞增排列,常見的時間複雜度有:常數階O(1),對數階O(log2n),線性階O(n), 線性對數階O(nlog2n),平方階O(n2),立方階O(n3),..., k次方階O(nk),指數階O(2n)。隨着問題規模n的不斷增大,上述時間複雜度不斷增大,算法的執行效率越低。 二、空間複雜度 與時間複雜度相似,空間複雜度是指算法在計算機內執行時所需存儲空間的度量。記做: S(n)=O(f(n)) 咱們通常所討論的是除正常佔用內存開銷外的輔助存儲單元規模。討論方法與時間複雜度相似,再也不贅述。markdown

3)漸進時間複雜度評價算法時間性能   主要用算法時間複雜度的數量級(即算法的漸近時間複雜度)評價一個算法的時間性能。數據結構

求解算法的時間複雜度的具體步驟

⑴ 找出算法中的基本語句;函數

  算法中執行次數最多的那條語句就是基本語句,一般是最內層循環的循環體。性能

⑵ 計算基本語句的執行次數的數量級;

  只需計算基本語句執行次數的數量級,這就意味着只要保證基本語句執行次數的函數中的最高次冪正確便可,能夠忽略全部低次冪和最高次冪的係數。這樣可以簡化算法分析,而且使注意力集中在最重要的一點上:增加率。

⑶ 用大Ο記號表示算法的時間性能。

  將基本語句執行次數的數量級放入大Ο記號中。

  若是算法中包含嵌套的循環,則基本語句一般是最內層的循環體,若是算法中包含並列的循環,則將並列循環的時間複雜度相加。例如:

  for (i=1; i<=n; i++)   x++;

  for (i=1; i<=n; i++)   for (j=1; j<=n; j++)   x++;

  第一個for循環的時間複雜度爲Ο(n),第二個for循環的時間複雜度爲Ο(n2),則整個算法的時間複雜度爲Ο(n+n2)=Ο(n2)。

  常見的算法時間複雜度由小到大依次爲:

  Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)

  Ο(1)表示基本語句的執行次數是一個常數,通常來講,只要算法中不存在循環語句,其時間複雜度就是Ο(1)。Ο(log2n)、Ο(n)、 Ο(nlog2n)、Ο(n2)和Ο(n3)稱爲多項式時間,而Ο(2n)和Ο(n!)稱爲指數時間。計算機科學家廣泛認爲前者是有效算法,把這類問題稱 爲P類問題,而把後者稱爲NP問題。

O(1)

Temp=i;i=j;j=temp;

以 上三條單個語句的頻度均爲1,該程序段的執行時間是一個與問題規模n無關的常數。算法的時間複雜度爲常數階,記做T(n)=O(1)。若是算法的執行時 間不隨着問題規模n的增長而增加,即便算法中有上千條語句,其執行時間也不過是一個較大的常數。此類算法的時間複雜度是O(1)。

O(n^2)

2.1. 交換i和j的內容 sum=0; (一次) for(i=1;i<=n;i++) (n次 ) for(j=1;j<=n;j++) (n^2次 ) sum++; (n^2次 ) 解:T(n)=2n^2+n+1 =O(n^2)

2.2.

for (i=1;i<n;i++)
{ 
    y=y+1;         ①   
    for (j=0;j<=(2*n);j++)    
       x++;        ②      
}          
解: 語句1的頻度是n-1
          語句2的頻度是(n-1)*(2n+1)=2n^2-n-1
          f(n)=2n^2-n-1+(n-1)=2n^2-2
          該程序的時間複雜度T(n)=O(n^2). 
複製代碼

O(n)

2.3

a=0;
b=1;                      ①
for (i=1;i<=n;i++) ②
{  
   s=a+b;&emsp;&emsp;&emsp;&emsp;③
   b=a;&emsp;&emsp;&emsp;&emsp;&emsp;④  
   a=s;&emsp;&emsp;&emsp;&emsp;&emsp;⑤
}
解: 語句1的頻度:2,        
           語句2的頻度: n,        
          語句3的頻度: n-1,        
          語句4的頻度:n-1,    
          語句5的頻度:n-1,                                  
          T(n)=2+n+3(n-1)=4n-1=O(n).
複製代碼

O(log2n)

2.4

i=1;       ①
    while (i<=n)
       i=i*2; ②
解: 語句1的頻度是1,  
          設語句2的頻度是f(n),   則:2^f(n)<=n;f(n)<=log2n    
          取最大值f(n)= log2n,
          T(n)=O(log2n )
複製代碼

O(n^3)

2.5

for(i=0;i<n;i++)
    {  
       for(j=0;j<i;j++)  
       {
          for(k=0;k<j;k++)
             x=x+2;  
       }
    }
解: 當i=m, j=k的時候,內層循環的次數爲k當i=m時, j 能夠取 0,1,...,m-1 , 因此這裏最內循環共進行了0+1+...+m-1=(m-1)m/2次因此,i從0取到n, 則循環共進行了: 0+(1-1)*1/2+...+(n-1)n/2=n(n+1)(n-1)/6因此時間複雜度爲O(n^3).
複製代碼

一個經驗規則

有以下複雜度關係

c < log2N < n < n * Log2N < n^2 < n^3 < 2^n < 3^n < n!

其中c是一個常量,若是一個算法的複雜度爲c 、 log2N 、n 、 n*log2N ,那麼這個算法時間效率比較高 ,若是是 2^n , 3^n ,n!,那麼稍微大一些的n就會令這個算法不能動了,居於中間的幾個則差強人意。

空間複雜度

相似於時間複雜度的討論,一個算法的空間複雜度(Space Complexity)S(n)定義爲該算法所耗費的存儲空間,它也是問題規模n的函數。漸近空間複雜度也經常簡稱爲空間複雜度。 空間複雜度(Space Complexity)是對一個算法在運行過程當中臨時佔用存儲空間大小的量度。一個算法在計算機存儲器上所佔用的存儲空間,包括存儲算法自己所佔用的存儲空間,算法的輸入輸出數據所佔用的存儲空間和算法在運行過程當中臨時佔用的存儲空間這三個方面。算法的輸入輸出數據所佔用的存儲空間是由要解決的問題決定的,是經過參數表由調用函數傳遞而來的,它不隨本算法的不一樣而改變。存儲算法自己所佔用的存儲空間與算法書寫的長短成正比,要壓縮這方面的存儲空間,就必須編寫出較短的算法。算法在運行過程當中臨時佔用的存儲空間隨算法的不一樣而異,有的算法只須要佔用少許的臨時工做單元,並且不隨問題規模的大小而改變,咱們稱這種算法是「就地"進行的,是節省存儲的算法,如這一節介紹過的幾個算法都是如此;有的算法須要佔用的臨時工做單元數與解決問題的規模n有關,它隨着n的增大而增大,當n較大時,將佔用較多的存儲單元。

如當一個算法的空間複雜度爲一個常量,即不隨被處理數據量n的大小而改變時,可表示爲O(1);當一個算法的空間複雜度與以2爲底的n的對數成正比時,可表示爲0(10g2n);當一個算法的空I司複雜度與n成線性比例關係時,可表示爲0(n).若形參爲數組,則只須要爲它分配一個存儲由實參傳送來的一個地址指針的空間,即一個機器字長空間;若形參爲引用方式,則也只須要爲其分配存儲一個地址的空間,用它來存儲對應實參變量的地址,以便由系統自動引用實參變量。

簡單排序算法

直接插入排序

1)描述

  1. 直接插入排序就是第 i 趟把第 i 個元素放到前面已經排好序的序列中去
  2. 重複上述操做。 n 個元素共需 n-1 趟掃描,每趟將一個元素插入到它前面的子序列中

2)複雜度分析

  • 時間複雜度:最好狀況 o(n) (當數據序列已排序);最壞狀況 o(n * n)(數據序列反序); 隨機(n * n)
  • 空間複雜度: o(1)
  1. 穩定性

直接插入排序是穩定的。

4)C 語言實現

void printSort(int table[],int size)
{
    for (int i = 0; i<size; i++) {
         printf("%d\t",table[i]);
    }
    printf("\n");
}
void insertSort(int table[],int size)
{
    
    for(int i=1;i<size;i++){//n-1趟掃描
        int temp=table[i],j;//每趟將table[i]插入到前面排序序列中
        for(j=i-1;j>=0&&temp<table[j];j--)
            table[j+1]=table[j];//將較大元素向後移動
        table[j+1]=temp;//temp到達插入位置
    }
    printSort(table,size);
}
複製代碼

希爾排序

1) 描述

  1. 將一個數據序列分紅若干組,每組由若干相隔一段距離的元素組成,這段距離稱爲增量,在一組內採用直接插入排序算法進行排序
  2. 當增量爲 1 時,只有一組,元素是整個序列,在進行一次直接插入排序便可

2) 複雜度分析

  • 時間複雜度:時間複雜度比較複雜,具體時間取決於具體增量的序列
  • 空間複雜度:o(1)
  1. 穩定性

穩定性不肯定

4)C 語言實現

void shellSort(int table[],int size)
{
    for(int delta=size/2;delta>0;delta/=2)//進行若干趟掃描,控制增量,增量減半
        for(int i=delta;i<size;i++){//一趟分若干組,每組進行直接插入排序
            int temp=table[i],j;
            for(j=i-delta;j>=0&&temp<table[j];j-=delta)
                table[j+delta] = table[j];
            table[j+delta] = temp;
        }
    printSort(table,size);
}
複製代碼

冒泡排序

1)描述

比較兩個相鄰元素的關鍵值,若是反序,則交換。若按升序排序,每一趟將被掃描的數據序列中的最大元素交換到最後位置,就像氣泡從水裏冒出同樣。

2)複雜度分析

  • 時間複雜度: o(n)~o(n * n)
  • 空間複雜度: o(1)
  1. 穩定性

穩定

4) C語言代碼

void bubleSort(int table[],int size)
{
    int exchange = 1;
    for(int i=1;i<size&&exchange==1;i++){//有交換時才進行下一趟排序
        exchange = 0;//假定元素沒有交換
        for(int j=0;j<size-i;j++)
            if(table[j]>table[j+1]){
                int temp = table[j];
                table[j] = table[j+1];
                table[j+1] = temp;
                exchange = 1;
            }
    }
    printSort(table,size);
}
複製代碼

快速排序

1)描述

在數據序列中選擇一個值做爲比較的基準值,每趟從數據序列的兩端開始交替進行,將小於基準值的元素交換到序列前端,大於基準值的元素交換到序列後端,介於二者之間的位置則稱爲基準值的最終位置。同時序列被劃分紅兩個子序列,再用一樣的方法對了兩個子序列進行排序,知道子序列長度爲1,則完成排序。

2)複雜度分析

  • 時間複雜度:最好狀況-每趟排序將序列分紅長度相近的兩個子序列。 o(nlog2n) ;最壞狀況:每趟排序將序列分紅長度差別很大的兩個子序列 o(n*n)
  • 空間複雜度:最好狀況 o(log2n) ;最壞狀況 o(n)

3) 穩定性

不穩定

  1. C語言代碼
void quicklySort(int[],int,int);
	
	void quickSort(int table[],int size)
	{
	    quicklySort(table,0,size-1);
	    printSort(table, size);
	}
	
	void quicklySort(int table[],int begin,int end)
	{
	    if(begin<end){
	        int i=begin,j=end;
	        int vot=table[i];//基準值
	        while(i!=j){//一趟排序
	            while(i<j&&vot<=table[j])
	                j--;
	            if(i<j)//若是有小元素(當有table[j]<vot的時候,該條件恆成立)
	                table[i++]=table[j];//把後面的小元素向前移動
	            while(i<j&&table[i]<=vot)
	                i++;
	            if(i<j)
	                table[j--]=table[i];//把大元素向後移動
	        }
	        table[i]=vot;//基準值到達最終位置
	        quicklySort(table,begin,j-1);//前段子序列進行排序
	        quicklySort(table,i+1,end);//後端子序列進行排序
	    }
	}
	
複製代碼

直接選擇排序

1)描述

第一趟從n個元素的數據中選出關鍵字最小或最大的元素放到最前或最後位置,下一趟再從n-1個元素……。直接選擇排序算法可用順序表和單鏈表實現。

2)複雜度分析

  • 時間複雜度:o(n*n)
  • 空間複雜度:o(1)

3)穩定性

不穩定

  1. 代碼
void selectSort(int table[],int size)
	{
	    for(int i=0;i<size-1;i++){
	        int min=i;
	        for(int j=i+1;j<size;j++)
	            if(table[j]<table[min])
	                min=j;
	        if(min!=i){
	            int temp=table[i];
	            table[i]=table[min];
	            table[min]=temp;
	        }
	    }
	    printSort(table, size);
	}
複製代碼

堆排序

1)描述

  1. 堆:這裏的堆不是數據結構裏面的堆棧,而是一種數據結構。堆能夠視爲一顆徹底二叉樹,徹底二叉樹的一個優秀的性質是除了最底層以外,每一層都是滿的,這使得堆能夠利用數組來表示,每個節點對應數組中的一個元素。二叉堆通常分爲兩種,最小堆和最大堆。
  2. 最大堆:堆中每一個父節點的值都大於等於其孩子節點,這樣的堆就是一個最大堆。 最小堆:對中的每一個父節點都小於等於其孩子節點,這樣的堆就是一個最小堆
  3. 堆排序:堆排序分兩個階段。首先將一個數據序列建成堆序列,根節點值是最小(大)值;而後採用選擇排序思路,每趟將根結點值交換到最後,再將其他值調整成堆,依次重複,直到排序完成

2) 複雜度分析

  • 時間複雜度:o(n*log2n)
  • 空間複雜度:o(1)

3)穩定性

不穩定

4) 代碼

void sift(int[],int,int);
void heapSort(int table[],int size)
{
    int n=size;
    for(int j=n/2-1;j>=0;j--)
        //建立最小堆
        sift(table,j,n-1);
    for(int j=n-1;j>0;j--){
        //每趟將最小值交換到後面,再調整成堆
        int temp=table[0];
        table[0]=table[j];
        table[j]=temp;
        sift(table,0,j-1);
    }
}

void sift(int table[],int begin,int end)
{
    int i=begin,j=2*i+1;//i爲子樹的根,j爲i節點的左孩子
    int temp=table[i];//得到第i個元素的值
    while(j<=end){
        //沿較小值孩子節點向下篩選
        if(j<end&&table[j]>table[j+1])
            //數組元素比較
            j++;  //j爲左右孩子的較小者
        if(temp>table[j]){
            //若父母結點值較大
            table[i]=table[j];
            //孩子節點中的較小者上移
            i=j;  //i,j向下一層
            j=2*i+1;
        }
        else break;
    }
    table[i]=temp;//當前子樹的原根值調整後的位置
}

void merge(int X[],int Y[],int m,int r,int n)
{
    //一次歸併
    int i=m,j=r,k=m;
    int xLength = sizeof(X)/sizeof(X[0]);
    while(i<r&&j<r+n&&j<xLength){
        //將X中兩個相鄰子序列歸併到Y中
        if(X[i]<X[j])
            //較小值複製到Y中
            Y[k++]=X[i++];
        else Y[k++]=X[j++];
        while(i<r)  //將前一個子序列剩餘元素複製到Y中
            Y[k++]=X[i++];
        while(j<r+n&&j<xLength)
            //將後一個子序列剩餘元素複製到Y中
            Y[k++]=X[j++];
    }
}

void mergepass(int X[],int Y[],int n) //一趟歸併
{
    int i=0;
    int xLength = sizeof(X)/sizeof(X[0]);
    while(i<xLength-2*n+1){
        merge(X,Y,i,i+n,n);
        i+=2*n;
    }
    if(i+n<xLength)
        merge(X,Y,i,i+n,n);
    //再一次歸併
    else
        for(int j=i;j<xLength;j++)
            //將X剩餘元素複製到Y中
            Y[j]=X[j];
}

void mergeSort(int X[])
{
    int xLength = sizeof(X)/sizeof(X[0]);
    int Y[] = new int[xLength];
    int n=1;
    while(n<xLength){
        mergepass(X,Y,n);
        n*=2;
        if(n<xLength){
            mergepass(Y,X,n);
            n*=2;
        }
    }
}
複製代碼
相關文章
相關標籤/搜索