C#算法

C#算法

希爾排序
希爾排序是將組分段,進行插入排序.
對想提高C#語言編程能力的朋友,我們可以互相探討一下。
如:下面的程序,並沒有實現多態,來,幫它實現一下。
using System;
public class ShellSorter
{
  public void Sort(int [] list)
  {
      int inc;
      for(inc=1;inc<=list.Length/9;inc=3*inc+1);
      for(;inc>0;inc/=3)
      {
          for(int i=inc+1;i<=list.Length;i+=inc)
        {
          int t=list[i-1];
          int j=i;
          while((j>inc)&&(list[j-inc-1]>t))
          {
            list[j-1]=list[j-inc-1];
            j-=inc;
          }
          list[j-1]=t;
        }
      }
    }
}
public class MainClass
{
    public static void Main()
    {
    int[] iArrary=new int[]{1,5,3,6,10,55,9,2,87,12,34,75,33,47};
    ShellSorter sh=new ShellSorter();
    sh.Sort(iArrary);
    for(int m=0;m<=13;m++)
    Console.WriteLine("{0}",iArrary[m]); 
      }
}
已經編譯通過.
插入排序
using System;
public class InsertionSorter
{
  public void Sort(int [] list)
  {
      for(int i=1;i<list.Length;++i)
      {
          int t=list[i];
          int j=i;
          while((j>0)&&(list[j-1]>t))
          {
            list[j]=list[j-1];
            --j;
          }
        list[j]=t;
      }
    }
}
public class MainClass
{
    public static void Main()
    {
    int[] iArrary=new int[]{1,5,3,6,10,55,9,2,87,12,34,75,33,47};
    InsertionSorter ii=new InsertionSorter();
    ii.Sort(iArrary);
    for(int m=0;m<=13;m++)
    Console.WriteLine("{0}",iArrary[m]); 
      }
}
已經編譯運行通過.這太簡單了,我不做詳細介紹了.
選擇排序
using System;
public class SelectionSorter
{
  // public enum comp {COMP_LESS,COMP_EQUAL,COMP_GRTR};
  private int min;
  // private int m=0;
   public void Sort(int [] list)
   {
       for(int i=0;i<list.Length-1;++i)
       {
          min=i;
          for(int j=i+1;j<list.Length;++j)
          {
            if(list[j]<list[min])
            min=j;
          }
        int t=list[min];
        list[min]=list[i];
        list[i]=t;
     //   Console.WriteLine("{0}",list[i]);
     }
    }
}
public class MainClass
{
     public static void Main()
    {
     int[] iArrary=new int[]{1,5,3,6,10,55,9,2,87,12,34,75,33,47};
     SelectionSorter ss=new SelectionSorter();
     ss.Sort(iArrary);
     for(int m=0;m<=13;m++)
     Console.WriteLine("{0}",iArrary[m]); 
     }
}
    已經成功的編譯。
 
常見排序算法介紹
□非哥 發表於 2005-10-14 23:00:00
1、穩定排序和非穩定排序
簡單地說就是所有相等的數經過某種排序方法後,仍能保持它們在排序之前的相對次序,我們就
說這種排序方法是穩定的。反之,就是非穩定的。
比如:一組數排序前是a1,a2,a3,a4,a5,其中a2=a4,經過某種排序後爲a1,a2,a4,a3,a5,
則我們說這種排序是穩定的,因爲a2排序前在a4的前面,排序後它還是在a4的前面。假如變成a1,a4,
a2,a3,a5就不是穩定的了。2、內排序和外排序
在排序過程中,所有需要排序的數都在內存,並在內存中調整它們的存儲順序,稱爲內排序;
在排序過程中,只有部分數被調入內存,並藉助內存調整數在外存中的存放順序排序方法稱爲外排序。
3、算法的時間複雜度和空間複雜度
所謂算法的時間複雜度,是指執行算法所需要的計算工作量。
一個算法的空間複雜度,一般是指執行這個算法所需要的內存空間。
================================================================================
*/
 

/*
================================================
功能:選擇排序
輸入:數組名稱(也就是數組首地址)、數組中元素個數
================================================
*/
/*
====================================================
算法思想簡單描述:
在要排序的一組數中,選出最小的一個數與第一個位置的數交換;
然後在剩下的數當中再找最小的與第二個位置的數交換,如此循環
到倒數第二個數和最後一個數比較爲止。
選擇排序是不穩定的。算法複雜度O(n2)--[n的平方]
=====================================================
*/
void select_sort(int *x, int n)
{
int i, j, min, t;
for (i=0; i<n-1; i++) /*要選擇的次數:0~n-2共n-1次*/
{
  min = i; /*假設當前下標爲i的數最小,比較後再調整*/
  for (j=i+1; j<n; j++)/*循環找出最小的數的下標是哪個*/
  {
   if (*(x+j) < *(x+min))
   {  
    min = j; /*如果後面的數比前面的小,則記下它的下標*/
   }
  } 
 
  if (min != i) /*如果min在循環中改變了,就需要交換數據*/
  {
   t = *(x+i);
   *(x+i) = *(x+min);
   *(x+min) = t;
  }
}
}

/*
================================================
功能:直接插入排序
輸入:數組名稱(也就是數組首地址)、數組中元素個數
================================================
*/
/*
====================================================
算法思想簡單描述:
在要排序的一組數中,假設前面(n-1) [n>=2] 個數已經是排
好順序的,現在要把第n個數插到前面的有序數中,使得這n個數
也是排好順序的。如此反覆循環,直到全部排好順序。
直接插入排序是穩定的。算法時間複雜度O(n2)--[n的平方]
=====================================================
*/
void insert_sort(int *x, int n)
{
int i, j, t;
for (i=1; i<n; i++) /*要選擇的次數:1~n-1共n-1次*/
{
  /*
   暫存下標爲i的數。注意:下標從1開始,原因就是開始時
   第一個數即下標爲0的數,前面沒有任何數,單單一個,認爲
   它是排好順序的。
  */
  t=*(x+i);
  for (j=i-1; j>=0 && t<*(x+j); j--) /*注意:j=i-1,j--,這裏就是下標爲i的數,在它前面有序列中找插入位置。*/
  {
   *(x+j+1) = *(x+j); /*如果滿足條件就往後挪。最壞的情況就是t比下標爲0的數都小,它要放在最前面,j==-1,退出循環*/
  }
  *(x+j+1) = t; /*找到下標爲i的數的放置位置*/
}
}

/*
================================================
功能:冒泡排序
輸入:數組名稱(也就是數組首地址)、數組中元素個數
================================================
*/
/*
====================================================
算法思想簡單描述:
在要排序的一組數中,對當前還未排好序的範圍內的全部數,自上
而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沉,較
小的往上冒。即:每當兩相鄰的數比較後發現它們的排序與排序要
求相反時,就將它們互換。
下面是一種改進的冒泡算法,它記錄了每一遍掃描後最後下沉數的
位置k,這樣可以減少外層循環掃描的次數。
冒泡排序是穩定的。算法時間複雜度O(n2)--[n的平方]
=====================================================
*/
void bubble_sort(int *x, int n)
{
int j, k, h, t;
 
for (h=n-1; h>0; h=k) /*循環到沒有比較範圍*/
{
  for (j=0, k=0; j<h; j++) /*每次預置k=0,循環掃描後更新k*/
  {
   if (*(x+j) > *(x+j+1)) /*大的放在後面,小的放到前面*/
   {
    t = *(x+j);
    *(x+j) = *(x+j+1);
    *(x+j+1) = t; /*完成交換*/
    k = j; /*保存最後下沉的位置。這樣k後面的都是排序排好了的。*/
   }
  }
}
}
 

/*
================================================
功能:希爾排序
輸入:數組名稱(也就是數組首地址)、數組中元素個數
================================================
*/
/*
====================================================
算法思想簡單描述:
在直接插入排序算法中,每次插入一個數,使有序序列只增加1個節點,
並且對插入下一個數沒有提供任何幫助。如果比較相隔較遠距離(稱爲
增量)的數,使得數移動時能跨過多個元素,則進行一次比較就可能消除
多個元素交換。D.L.shell於1959年在以他名字命名的排序算法中實現
了這一思想。算法先將要排序的一組數按某個增量d分成若干組,每組中
記錄的下標相差d.對每組中全部元素進行排序,然後再用一個較小的增量
對它進行,在每組中再進行排序。當增量減到1時,整個要排序的數被分成
一組,排序完成。
下面的函數是一個希爾排序算法的一個實現,初次取序列的一半爲增量,
以後每次減半,直到增量爲1。
希爾排序是不穩定的。
=====================================================
*/
void shell_sort(int *x, int n)
{
int h, j, k, t;
for (h=n/2; h>0; h=h/2) /*控制增量*/
{
  for (j=h; j<n; j++) /*這個實際上就是上面的直接插入排序*/
  {
   t = *(x+j);
   for (k=j-h; (k>=0 && t<*(x+k)); k-=h)
   {
    *(x+k+h) = *(x+k);
   }
   *(x+k+h) = t;
  }
}
}

/*
================================================
功能:快速排序
輸入:數組名稱(也就是數組首地址)、數組中起止元素的下標
================================================
*/
/*
====================================================
算法思想簡單描述:
快速排序是對冒泡排序的一種本質改進。它的基本思想是通過一趟
掃描後,使得排序序列的長度能大幅度地減少。在冒泡排序中,一次
掃描只能確保最大數值的數移到正確位置,而待排序序列的長度可能只
減少1。快速排序通過一趟掃描,就能確保某個數(以它爲基準點吧)
的左邊各數都比它小,右邊各數都比它大。然後又用同樣的方法處理
它左右兩邊的數,直到基準點的左右只有一個元素爲止。它是由
C.A.R.Hoare於1962年提出的。
顯然快速排序可以用遞歸實現,當然也可以用棧化解遞歸實現。下面的
函數是用遞歸實現的,有興趣的朋友可以改成非遞歸的。
快速排序是不穩定的。最理想情況算法時間複雜度O(nlog2n),最壞O(n2)
=====================================================
*/
void quick_sort(int *x, int low, int high)
{
int i, j, t;
if (low < high) /*要排序的元素起止下標,保證小的放在左邊,大的放在右邊。這裏以下標爲low的元素爲基準點*/
{
  i = low;
  j = high;
  t = *(x+low); /*暫存基準點的數*/
  while (i<j) /*循環掃描*/
  {
   while (i<j && *(x+j)>t) /*在右邊的只要比基準點大仍放在右邊*/
   {
    j--; /*前移一個位置*/
   }
   if (i<j)
   {
    *(x+i) = *(x+j); /*上面的循環退出:即出現比基準點小的數,替換基準點的數*/
    i++; /*後移一個位置,並以此爲基準點*/
   }
   while (i<j && *(x+i)<=t) /*在左邊的只要小於等於基準點仍放在左邊*/
   {
    i++; /*後移一個位置*/
   }
   if (i<j)
   {
    *(x+j) = *(x+i); /*上面的循環退出:即出現比基準點大的數,放到右邊*/
    j--; /*前移一個位置*/
   }
  }
  *(x+i) = t; /*一遍掃描完後,放到適當位置*/
  quick_sort(x,low,i-1);  /*對基準點左邊的數再執行快速排序*/
  quick_sort(x,i+1,high);  /*對基準點右邊的數再執行快速排序*/
}
}

/*
================================================
功能:堆排序
輸入:數組名稱(也就是數組首地址)、數組中元素個數
================================================
*/
/*
====================================================
算法思想簡單描述:
堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。
堆的定義如下:具有n個元素的序列(h1,h2,...,hn),當且僅當
滿足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2)
時稱之爲堆。在這裏只討論滿足前者條件的堆。
由堆的定義可以看出,堆頂元素(即第一個元素)必爲最大項。完全二叉樹可以
很直觀地表示堆的結構。堆頂爲根,其它爲左子樹、右子樹。
初始時把要排序的數的序列看作是一棵順序存儲的二叉樹,調整它們的存儲順序,
使之成爲一個堆,這時堆的根節點的數最大。然後將根節點與堆的最後一個節點
交換。然後對前面(n-1)個數重新調整使之成爲堆。依此類推,直到只有兩個節點
的堆,並對它們作交換,最後得到有n個節點的有序序列。
從算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最後一個元素
交換位置。所以堆排序有兩個函數組成。一是建堆的***函數,二是反覆調用***函數
實現排序的函數。
堆排序是不穩定的。算法時間複雜度O(nlog2n)。
*/
/*
功能:***建堆
輸入:數組名稱(也就是數組首地址)、參與建堆元素的個數、從第幾個元素開始
*/
void sift(int *x, int n, int s)
{
int t, k, j;
t = *(x+s); /*暫存開始元素*/
k = s;  /*開始元素下標*/
j = 2*k + 1; /*右子樹元素下標*/
while (j<n)
{
  if (j<n-1 && *(x+j) < *(x+j+1))/*判斷是否滿足堆的條件:滿足就繼續下一輪比較,否則調整。*/
  {
   j++;
  }
  if (t<*(x+j)) /*調整*/
  {
   *(x+k) = *(x+j);
   k = j; /*調整後,開始元素也隨之調整*/
   j = 2*k + 1;
  }
  else /*沒有需要調整了,已經是個堆了,退出循環。*/
  {
   break;
  }
}
*(x+k) = t; /*開始元素放到它正確位置*/
}

/*
功能:堆排序
輸入:數組名稱(也就是數組首地址)、數組中元素個數
*/
void heap_sort(int *x, int n)
{
int i, k, t;
int *p;
for (i=n/2-1; i>=0; i--)
{
  sift(x,n,i); /*初始建堆*/
}
for (k=n-1; k>=1; k--)
{
  t = *(x+0); /*堆頂放到最後*/
  *(x+0) = *(x+k);
  *(x+k) = t;
  sift(x,k,0); /*剩下的數再建堆*/
}
}

void main()
{
#define MAX 4
int *p, i, a[MAX];
/*錄入測試數據*/
p = a;
printf("Input %d number for sorting :\n",MAX);
for (i=0; i<MAX; i++)
{
  scanf("%d",p++);
}
printf("\n");
/*測試選擇排序*/

p = a;
select_sort(p,MAX);
/**/

/*測試直接插入排序*/
/*
p = a;
insert_sort(p,MAX);
*/

/*測試冒泡排序*/
/*
p = a;
insert_sort(p,MAX);
*/
/*測試快速排序*/
/*
p = a;
quick_sort(p,0,MAX-1);
*/
/*測試堆排序*/
/*
p = a;
heap_sort(p,MAX);
*/
for (p=a, i=0; i<MAX; i++)
{
  printf("%d ",*p++);
}
printf("\n");
system("pause");
}
 
排序算法
                                                                                        Copyright:bamboottm.my163.com      //轉載請保留出處
計算機處理數據包括排序、檢索(查找)、修改和刪除操作。我們研究排序算法有幾點充分理由。首先,是因爲它實際應用非常頻繁,計算機廠家…… //這個你要聽嗎? 不廢話了。
//爲了說明方便.定義如下數組:  a:array[1..10] of integer;temp: 中間變量  排序:  從大到小
l         選擇排序
1.基本的 選擇排序
  <1>基本思想
        首先從要排序的數中選擇最大的數,將它放在第一個位置,然後從剩下的數中選擇最大的數放在第二個位置,如此繼續,直到最後從剩下的兩個數中選擇最大的數放在倒數第二個位置,剩下的一個數放在最後位置,完成排序.
        下表是六個元素的排序的過程
 
        
      4    5   7   1   2   3
         ┗━━┛
          5    4   7   1   2   3
     ┗━━━━┛
          7    4   5   1   2   3
     ┗━━━━━━┛
          7    4   5   1   2   3   
         ┗━━━━━━━━━━┛    第一趟結束
      ⑦   4   5   1   2   3
              ┗━┛
      7    5   4   1   2   3
              ┗━━━┛
      7    5   4   1   2   3
          ┗━━━━━┛
      7    5   4   1   2   3
          ┗━━━━━━━┛     第二趟結束
      7    ⑤  4   1   2   3
              ┗━┛
          7    5   4   1   2   3
              ┗━━━┛
          7    5   4   1   2   3
                   ┗━━━━━┛    第三趟結束
          7    5   ④  1   2   3
                      ┗━┛
      7    5   4   2   1   3     第四趟結束
                  ┗━━━┛    
      7    5   4   ③  1   2
                      ┗━┛     第五趟結束
          7    5   4   3   ②  ①  
                  
  <2>算法實現
           for i:=1 to 9 do
             for j:=i+1 to 10 do
               if a[i]<a[j]
            begin
                  temp:=a[i];
                  a[i]:=a[j];
                  a[j]:=temp;
                end;    
    2.改進 
         以上排序方案每次交換兩個元素需要執行三個語句,過多的交換必定要花費許多時間.改進方案是在內循環的比較中找出最大值元素的下標,在內循環結束時才考慮是否要調換.
         代碼如下
              for i:=1 to 9 do
                begin
                 k:=i;   
                 for j:=i+1 to 20 do
                    if a[j]>a[k]
                      then k:=j;
                 if i<k {不可能大於}
                   then begin
                           temp:=a[i];
                           a[i]:=a[k];
                           a[k]:=temp;
                        end;
                end;
  
l         冒泡排序
1.基本的冒泡排序
    <1> 基本思想
       依次比較相鄰的兩個數,把大的放前面,小的放後面.即首先比較第1個數和第2個數,大數放前,小數放後.然後比較第2個數和第3個數......直到比較最後兩個數.第一趟結束,最小的一定沉到最後.重複上過程,仍從第1個數開始,到最後第2個數.然後......
   由於在排序過程中總是大數往前,小數往後,相當氣泡上升,所以叫冒泡排序.
       下面是6個元素的排序的過程
 
 
        4     5    7    1    2    3 
             ┗━━┛
    5     4    7    1    2    3 
         ┗━━┛
    5     7    4    1    2    3 
              ┗━━┛
    5     7    4    1    2    3 
                                         ┗━━┛
    5     7    4    2    1    3 
                                                  ┗━━┛  第一趟結束
        5     7    4    2    3    ①
   ┗━━┛
        7     5    4    2    3    1
         ┗━━┛
        7     5    4    2    3    1
              ┗━━┛
    7     5    4    2    3    1
                                         ┗━━┛       第二趟結束
    7     5    4    3    ②   1
       ┗━━┛
    7     5    4    3    2    1
         ┗━━┛
        7     5    4    3    2    1
              ┗━━┛            第三趟結束
        7     5    4    ③   2    1
   ┗━━┛
    7     5    4    3    2    1
         ┗━━┛                 第四趟結束
        7     5    ④    3   2    1
   ┗━━┛                       第五趟結束
        ⑦    ⑤   4    3    2    1
        
       
    <2> 算法實現
           for i:=1 to 9 do
            for j:=1 to 10-i do
              if a[j]<a[j+1]
                then begin
                       temp:=a[j];
                       a[j]:=a[j+1];
                       a[j+1]:=temp;
                     end;
   
        2 改進
     上例中,可以發現,第二趟結束已經排好序.但是計算機此時並不知道已經排好序.所以,還需進行一次比較,如果沒有發生任何數據交換,則知道已經排好序,可以不幹了.因此第三趟比較還需進行,第四趟、第五趟比較則不必要.
        我們設置一個布爾變量bo 來記錄是否有進行交換.值爲false 進行了比較 true 則沒有
        代碼如下
          i:=1; 
    repeat
                    bo:=true;
                    for j:=1 to 10-i
                        if a[j]<a[j+1] then
                             begin
                                 temp:=a[j];
                       a[j]:=a[j+1];
                   a[j+1]:=temp;
                   bo:=false;
                             end;
      inc(i);
                 until bo;
  
3.再次改進
   如果說是有20個元素.數據序列是8,3,4,9,7再後跟着15個大於9且已經排好序的數據.在第三趟後算法終止.總共做了19+18+17=54次比較使得絕大多數已排好序的數據在一遍掃描後足以發現他們是排好序的情況下仍然被檢查3遍.
    我們改進如下
   flag:=10;
                 while flag>0 do
                    begin
                k:=flag-1;
                flag:=0;
                       for i:=1 to k do
                          if a[i]<a[i+1] then
                             begin
                                temp:=a[i];
                                a[i]:=a[i+1];
                         a[i+1]:=temp;
                                flag:=i;
                             end; 
                    end; 

        改進的冒泡算法對上述數據進行的比較次數是19+4+2=24. 
    
l         希爾排序
  <1> 基本思想
        希爾排序法是1959年由D.L.Shell提出來的,又稱減少增量的排序。下表是以八個元素排序示範的例子.在該例中,開始時相隔4個成分,分別按組進行排序,這時每組2個成分,共4組; 然後相隔2個成分,在按組排序......最後,對所有相鄰成分進行排序.
   可參閱<<計算機程序設計技巧??第三卷排序查找

          <2> 算法實現
           j:=10;
           i:=1;
          while j>1 do
            begin
              j:=j div 2;
              repeat
                alldone:=true;
                for index:=1 to 10-j do  
                   begin
                     i:=index+j;
                     if a[index]<a[i] then
                       begin
                        temp:=a[index];
                        a[index]:=a[i];
                        a[i]:=temp;
                        alldone:=false;
                       end;   
                   end;
              until alldone
            end;  
                         //說句實話,這個很少有人用.:(  當然我也不會,書上抄的
l         插入排序
      <1> 基本思想
                        //對不起,我沒書.所以是我自己講.我很菜.不要介意 
      插入排序的思想就是讀一個,排一個.  //也許是這樣,起碼我是這麼認爲的:)
     將第1個數放入數組的第1個元素中,以後讀入的數與已存入數組的數進行比較,確定它在從大到小的排列中應處的位置.將該位置以及以後的元素向後推移一個位置,將讀入的新數填入空出的位置中.
    <2> 算法實現  {加了讀入語句}
         procedure insert(x,num:integer);
         var
          i,pos:integer;
          search:boolean;      
         begin
          pos:=1;
          search:=true;
          while search and (pos<=num ) do
            if x>a[pos]
               then search:=fasle
               else inc(pos); 
                 for i:=num downto pos do
                    a[i+1]:=a[i];
                 a[pos]:=x;
                 num:=num+1;   
         end;
          num:=0 {當前數組的長度}
          for i:=1 to 10 do
           begin
             read(x);
             intert(x,num)         
                    end;
     
l         合併排序
      <1> 基本思想
       合併排序的算法就是二分法。
   分解:將n個元素分解成各含 一半元素的子序列。
   解決:用合併排序法對兩個子序列遞歸地排序。
   合併:合併兩個已排序的子序列排序結果。
   在對子序列排列時,當其長度爲1時遞歸結束,因爲單個元素被認爲是已排好序的.合併排序的.合併排序的關鍵步驟在於合併目前產生的兩個已排好序的子序列:
A[p..q] 和 A[q+1…r];
   將它們合併成一個已排好序的子序列A[p..r]. 我們引入一個輔助過程merge(A,p,q,r)來完成這一項合併工作,其中A是數組,p,q,r是下標.
    <2> 算法實現          

procedure merge( p,q,r:integer);
var
  i,j,t:integer;
  it:array[1..10] of integer;
begin
  t:=p; i:=p; j:=q+1;
  while t<=r do
   begin
     if (i<=q) and ((j>j) or (a[i]<=a[j]))
       then begin
              it[t]:=a[i]; inc(i);
            end
       else begin
              it[t]:=a[j]; inc(j);
            end;
     inc(t);
    end;
for i:=p to r do a[i]:=t[i];
end;
procedure merge_sort(p,r:integer);
var q:integer;
begin
  if p<>r then begin
                 q:=(p+r-1) div 2 ;
                 merge_sort(p,q);
                 merge_sort(q+1,r);
                 merge(p,q,r);
               end; 
end;
begin
  merge_sort(1,10);
end.
l         快速排序
           <1> 基本思想
        快速排序的基本思想是基於分治策略的。對於輸入的子序列L[p..r],如果規模足夠小則直接進行排序,否則分三步處理:
            分解(Divide):將輸入的序列L[p..r]劃分成兩個非空子序列L[p..q]和L[q+1..r],使L[p..q]中任一元素的值不大於L[q+1..r]中任一元素的值。
            遞歸求解(Conquer):通過遞歸調用快速排序算法分別對L[p..q]和L[q+1..r]進行排序。
           合併(Merge):由於對分解出的兩個子序列的排序是就地進行的,所以在L[p..q]和L[q+1..r]都排好序後不需要執行任何計算L[p..r]就已排好序。
       這個解決流程是符合分治法的基本步驟的。因此,快速排序法是分治法的經典應用實例之一。
      我不太熟悉  請點這裏看看Starfish寫的   (感謝Starfish 提供)

排序算法是一種基本並且常用的算法。由於實際工作中處理的數量巨大,所以排序算法 對算法本身的速度要求很高。 而一般我們所謂的算法的性能主要是指算法的複雜度,一般用O方法來表示。在後面我將 給出詳細的說明。
       對於排序的算法我想先做一點簡單的介紹,也是給這篇文章理一個提綱。 我將按照算法的複雜度,從簡單到難來分析算法。 第一部分是簡單排序算法,後面你將看到他們的共同點是算法複雜度爲O(N*N)(因爲沒有使用word,所以無法打出上標和下標)。 第二部分是高級排序算法,複雜度爲O(Log2(N))。這裏我們只介紹一種算法。另外還有幾種 算法因爲涉及樹與堆的概念,所以這裏不於討論。 第三部分類似動腦筋。這裏的兩種算法並不是最好的(甚至有最慢的),但是算法本身比較 奇特,值得參考(編程的角度)。同時也可以讓我們從另外的角度來認識這個問題。現在,讓我們開始吧:
一、簡單排序算法
由於程序比較簡單,所以沒有加什麼註釋。所有的程序都給出了完整的運行代碼,並在我的VC環境
下運行通過。因爲沒有涉及MFC和WINDOWS的內容,所以在BORLAND C++的平臺上應該也不會有什麼
問題的。在代碼的後面給出了運行過程示意,希望對理解有幫助。
1.冒泡法:
這是最原始,也是衆所周知的最慢的算法了。他的名字的由來因爲它的工作看來象是冒泡:
#include <iostream.h>
void BubbleSort(int* pData,int Count)
{
int iTemp;
for(int i=1;i<Count;i++)
       {
         for(int j=Count-1;j>=i;j--)
                {
                  if(pData[j]<pData[j-1])
                            {
                              iTemp = pData[j-1];
                              pData[j-1] = pData[j];
                              pData[j] = iTemp;
                            }
                }
       }
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
BubbleSort(data,7);
for (int i=0;i<7;i++)
            cout<<data[i]<<" ";
cout<<"\n";
}
倒序(最糟情況)
第一輪:10,9,8,7->10,9,7,8->10,7,9,8->7,10,9,8(交換3次)
第二輪:7,10,9,8->7,10,8,9->7,8,10,9(交換2次)
第一輪:7,8,10,9->7,8,9,10(交換1次)
循環次數:6次
交換次數:6次
其他:
第一輪:8,10,7,9->8,10,7,9->8,7,10,9->7,8,10,9(交換2次)
第二輪:7,8,10,9->7,8,10,9->7,8,10,9(交換0次)
第一輪:7,8,10,9->7,8,9,10(交換1次)
循環次數:6次
交換次數:3次
上面我們給出了程序段,現在我們分析它:這裏,影響我們算法性能的主要部分是循環和交換, 顯然,次數越多,性能就越差。從上面的程序我們可以看出循環的次數是固定的,爲1+2+...+n-1。 寫成公式就是1/2*(n-1)*n。 現在注意,我們給出O方法的定義:
若存在一常量K和起點n0,使當n>=n0時,有f(n)<=K*g(n),則f(n) = O(g(n))。(呵呵,不要說沒 學好數學呀,對於編程數學是非常重要的!!!)
現在我們來看1/2*(n-1)*n,當K=1/2,n0=1,g(n)=n*n時,1/2*(n-1)*n<=1/2*n*n=K*g(n)。所以f(n) =O(g(n))=O(n*n)。所以我們程序循環的複雜度爲O(n*n)。 再看交換。從程序後面所跟的表可以看到,兩種情況的循環相同,交換不同。其實交換本身同數據源的 有序程度有極大的關係,當數據處於倒序的情況時,交換次數同循環一樣(每次循環判斷都會交換), 複雜度爲O(n*n)。當數據爲正序,將不會有交換。複雜度爲O(0)。亂序時處於中間狀態。正是由於這樣的 原因,我們通常都是通過循環次數來對比算法。
2.交換法:
交換法的程序最清晰簡單,每次用當前的元素一一的同其後的元素比較並交換。
#include <iostream.h>
void ExchangeSort(int* pData,int Count)
{
int iTemp;
for(int i=0;i<Count-1;i++)
          {
            for(int j=i+1;j<Count;j++)
                     {
                       if(pData[j]<pData[i])
                                 {
                                   iTemp = pData[i];
                                   pData[i] = pData[j];
                                   pData[j] = iTemp;
                                 }
                     }
         }
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
ExchangeSort(data,7);
for (int i=0;i<7;i++)
              cout<<data[i]<<" ";
cout<<"\n";
}
倒序(最糟情況)
第一輪:10,9,8,7->9,10,8,7->8,10,9,7->7,10,9,8(交換3次)
第二輪:7,10,9,8->7,9,10,8->7,8,10,9(交換2次)
第一輪:7,8,10,9->7,8,9,10(交換1次)
循環次數:6次
交換次數:6次
其他:
第一輪:8,10,7,9->8,10,7,9->7,10,8,9->7,10,8,9(交換1次)
第二輪:7,10,8,9->7,8,10,9->7,8,10,9(交換1次)
第一輪:7,8,10,9->7,8,9,10(交換1次)
循環次數:6次
交換次數:3次
從運行的表格來看,交換幾乎和冒泡一樣糟。事實確實如此。循環次數和冒泡一樣 也是1/2*(n-1)*n,所以算法的複雜度仍然是O(n*n)。由於我們無法給出所有的情況,所以 只能直接告訴大家他們在交換上面也是一樣的糟糕(在某些情況下稍好,在某些情況下稍差)。
3.選擇法:
現在我們終於可以看到一點希望:選擇法,這種方法提高了一點性能(某些情況下) 這種方法類似我們人爲的排序習慣:從數據中選擇最小的同第一個值交換,在從省下的部分中 選擇最小的與第二個交換,這樣往復下去。
#include <iostream.h>
void SelectSort(int* pData,int Count)
{
int iTemp;
int iPos;
for(int i=0;i<Count-1;i++)
            {
              iTemp = pData[i];
              iPos = i;
              for(int j=i+1;j<Count;j++)
                           {
                             if(pData[j]<iTemp)
                                        {
                                          iTemp = pData[j];
                                          iPos = j;
                                         }
                           }
              pData[iPos] = pData[i];
              pData[i] = iTemp;
             }
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
SelectSort(data,7);
for (int i=0;i<7;i++)
            cout<<data[i]<<" ";
cout<<"\n";
}
倒序(最糟情況)
第一輪:10,9,8,7->(iTemp=9)10,9,8,7->(iTemp=8)10,9,8,7->(iTemp=7)7,9,8,10(交換1次)
第二輪:7,9,8,10->7,9,8,10(iTemp=8)->(iTemp=8)7,8,9,10(交換1次)
第一輪:7,8,9,10->(iTemp=9)7,8,9,10(交換0次)
循環次數:6次
交換次數:2次
其他:
第一輪:8,10,7,9->(iTemp=8)8,10,7,9->(iTemp=7)8,10,7,9->(iTemp=7)7,10,8,9(交換1次)
第二輪:7,10,8,9->(iTemp=8)7,10,8,9->(iTemp=8)7,8,10,9(交換1次)
第一輪:7,8,10,9->(iTemp=9)7,8,9,10(交換1次)
循環次數:6次
交換次數:3次
遺憾的是算法需要的循環次數依然是1/2*(n-1)*n。所以算法複雜度爲O(n*n)。 我們來看他的交換。由於每次外層循環只產生一次交換(只有一個最小值)。所以f(n)<=n 所以我們有f(n)=O(n)。所以,在數據較亂的時候,可以減少一定的交換次數。
4.插入法:
插入法較爲複雜,它的基本工作原理是抽出牌,在前面的牌中尋找相應的位置插入,然後繼續下一張
#include <iostream.h>
void InsertSort(int* pData,int Count)
{
int iTemp;
int iPos;
for(int i=1;i<Count;i++)
             {
              iTemp = pData[i];
              iPos = i-1;
              while((iPos>=0) && (iTemp<pData[iPos]))
                              {
                                pData[iPos+1] = pData[iPos];
                                iPos--;
                              }
              pData[iPos+1] = iTemp;
            }
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
InsertSort(data,7);
for (int i=0;i<7;i++)
            cout<<data[i]<<" ";
cout<<"\n";
}
倒序(最糟情況)
第一輪:10,9,8,7->9,10,8,7(交換1次)(循環1次)
第二輪:9,10,8,7->8,9,10,7(交換1次)(循環2次)
第一輪:8,9,10,7->7,8,9,10(交換1次)(循環3次)
循環次數:6次
交換次數:3次
其他:
第一輪:8,10,7,9->8,10,7,9(交換0次)(循環1次)
第二輪:8,10,7,9->7,8,10,9(交換1次)(循環2次)
第一輪:7,8,10,9->7,8,9,10(交換1次)(循環1次)
循環次數:4次
交換次數:2次
上面結尾的行爲分析事實上造成了一種假象,讓我們認爲這種算法是簡單算法中最好的,其實不是, 因爲其循環次數雖然並不固定,我們仍可以使用O方法。從上面的結果可以看出,循環的次數f(n)<= 1/2*n*(n-1)<=1/2*n*n。所以其複雜度仍爲O(n*n)(這裏說明一下,其實如果不是爲了展示這些簡單 排序的不同,交換次數仍然可以這樣推導)。現在看交換,從外觀上看,交換次數是O(n)(推導類似 選擇法),但我們每次要進行與內層循環相同次數的‘=’操作。正常的一次交換我們需要三次‘=’ 而這裏顯然多了一些,所以我們浪費了時間。
最終,我個人認爲,在簡單排序算法中,選擇法是最好的。
二、高級排序算法:
高級排序算法中我們將只介紹這一種,同時也是目前我所知道(我看過的資料中)的最快的。 它的工作看起來仍然象一個二叉樹。首先我們選擇一箇中間值middle程序中我們使用數組中間值,然後 把比它小的放在左邊,大的放在右邊(具體的實現是從兩邊找,找到一對後交換)。然後對兩邊分別使 用這個過程(最容易的方法——遞歸)。
1.快速排序:
#include <iostream.h>
void run(int* pData,int left,int right)
{
int i,j;
int middle,iTemp;
i = left;
j = right;
middle = pData[(left+right)/2]; //求中間值
  do{
        while((pData[i]<middle) && (i<right))//從左掃描大於中值的數
                       i++;
        while((pData[j]>middle) && (j>left))//從右掃描大於中值的數
                       j--;
        if(i<=j)//找到了一對值
                      {
                       //交換
                        iTemp = pData[i];
                        pData[i] = pData[j];
                        pData[j] = iTemp;
                        i++;
                        j--;
                      }
}while(i<=j);//如果兩邊掃描的下標交錯,就停止(完成一次)
//當左邊部分有值(left<j),遞歸左半邊
if(left<j)
          run(pData,left,j);
//當右邊部分有值(right>i),遞歸右半邊
if(right>i)
          run(pData,i,right);
}
void QuickSort(int* pData,int Count)
{
run(pData,0,Count-1);
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
QuickSort(data,7);
for (int i=0;i<7;i++)
           cout<<data[i]<<" ";
cout<<"\n";
}
這裏我沒有給出行爲的分析,因爲這個很簡單,我們直接來分析算法:首先我們考慮最理想的情況
1.數組的大小是2的冪,這樣分下去始終可以被2整除。假設爲2的k次方,即k=log2(n)。
2.每次我們選擇的值剛好是中間值,這樣,數組纔可以被等分。
第一層遞歸,循環n次,第二層循環2*(n/2)......
所以共有n+2(n/2)+4(n/4)+...+n*(n/n) = n+n+n+...+n=k*n=log2(n)*n
所以算法複雜度爲O(log2(n)*n)
其他的情況只會比這種情況差,最差的情況是每次選擇到的middle都是最小值或最大值,那麼他將變 成交換法(由於使用了遞歸,情況更糟)。但是你認爲這種情況發生的機率有多大??呵呵,你完全 不必擔心這個問題。實踐證明,大多數的情況,快速排序總是最好的。 如果你擔心這個問題,你可以使用堆排序,這是一種穩定的O(log2(n)*n)算法,但是通常情況下速度要慢 於快速排序(因爲要重組堆)。
三、其他排序
1.雙向冒泡:
通常的冒泡是單向的,而這裏是雙向的,也就是說還要進行反向的工作。 代碼看起來複雜,仔細理一下就明白了,是一個來回震盪的方式。 寫這段代碼的作者認爲這樣可以在冒泡的基礎上減少一些交換(我不這麼認爲,也許我錯了)。 反正我認爲這是一段有趣的代碼,值得一看。
#include <iostream.h>
void Bubble2Sort(int* pData,int Count)
{
int iTemp;
int left = 1;
int right =Count -1;
int t;
do {
        //正向的部分
       for(int i=right;i>=left;i--)
                   {
                     if(pData[i]<pData[i-1])
                                  {
                                    iTemp = pData[i];
                                    pData[i] = pData[i-1];
                                    pData[i-1] = iTemp;
                                    t = i;
                                  }
                   }
        left = t+1;
        //反向的部分
        for(i=left;i<right+1;i++)
                   {
                     if(pData[i]<pData[i-1])
                                   {
                                     iTemp = pData[i];
                                     pData[i] = pData[i-1];
                                     pData[i-1] = iTemp;
                                     t = i;
                                   }
                   }
        right = t-1;
     }while(left<=right);
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
Bubble2Sort(data,7);
for (int i=0;i<7;i++)
             cout<<data[i]<<" ";
cout<<"\n";
}
2.SHELL排序
這個排序非常複雜,看了程序就知道了。 首先需要一個遞減的步長,這裏我們使用的是9、5、3、1(最後的步長必須是1)。 工作原理是首先對相隔9-1個元素的所有內容排序,然後再使用同樣的方法對相隔5-1個元素的排序,以次類推。
#include <iostream.h>
void ShellSort(int* pData,int Count)
{
int step[4];
step[0] = 9;
step[1] = 5;
step[2] = 3;
step[3] = 1;
int i,Temp;
int k,s,w;
for(int i=0;i<4;i++)
           {
             k = step[i];
             s = -k;
            for(int j=k;j<Count;j++)
                       {
                         iTemp = pData[j];
                         w = j-k;//求上step個元素的下標
                         if(s ==0)
                                     {
                                       s = -k;
                                       s++;
                                       pData[s] = iTemp;
                                     }
                        while((iTemp<pData[w]) && (w>=0) && (w<=Count))
                                     {
                                       pData[w+k] = pData[w];
                                       w = w-k;
                                     }
                        pData[w+k] = iTemp;
                     }
          }
}
void main()
{
int data[] = {10,9,8,7,6,5,4,3,2,1,-10,-1};
ShellSort(data,12);
for (int i=0;i<12;i++)
            cout<<data[i]<<" ";
cout<<"\n";
}
呵呵,程序看起來有些頭疼。不過也不是很難,把s==0的塊去掉就輕鬆多了,這裏是避免使用0 步長造成程序異常而寫的代碼。這個代碼我認爲很值得一看。 這個算法的得名是因爲其發明者的名字D.L.SHELL。依照參考資料上的說法:“由於複雜的數學原因 避免使用2的冪次步長,它能降低算法效率。”另外算法的複雜度爲n的1.2次冪。同樣因爲非常複雜並 “超出本書討論範圍”的原因(我也不知道過程),我們只有結果了.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章