簡單的幾個排序演算法

夢在未名湖畔發表於2018-04-10

       1.首先介紹最簡答的氣泡排序演算法,顧名思義就是像水中冒泡一樣,每次把當前混亂集合中的第一個資料依次和後面的資料進行比較,然後依次往後面行進。那麼每次排一個資料,則需要n-1次排序,每次要和n-1個資料比較

void Sort(int *number,int numberSize)
{
   int i,j,temp;
   for(i = 0;i < numberSize-1;i++)                           //需要n-1趟排序(最後一個資料自然就會有順序了)
   {
      for(j = 0;j < numberSize-i-1;j++)                      //因為前面的資料和n-1個資料比較了,那麼當前資料一定和後面的有序集合的資料
	  {                                                  //已經有大小關係了,那麼只需要比較混亂集合即可
	     if(number[j] > number[j+1])                     //從小到大的排序
		 {
		        temp = number[j];
			number[j] = number[j+1];
			number[j+1] = temp;
		 }
	  }
   }
}

顯然這段程式碼的複雜度是O(n^2)時間複雜度。該冒泡演算法還可以進行一些小的優化,但是時間複雜度級別還是接近O(n^2)。

       2.直接插入排序,該演算法是基於這麼一個思想。我給定一串有序的資料,我現在要插入一個資料並且我還想保持資料的順序。那麼我採取的做法就是從前往後或者從後往前挨個比較,然後插入就行。這樣對於一串無序的數,我們可以假設第一個資料是有序的,其餘的資料屬於(n-1)次輸入的資料,也就是呼叫了n-1次插入排序。最後混亂集合就變成了有序集合。

void Sort(int *number,int numberSize)
{
   int i =0,j,Soldier;                                          //這個是存放當前的比較數,為了方便後面減少交換次數的小技巧
   for(i = 1;i < numberSize;i++)
   {
	  Soldier = number[i];
          for(j = i-1;j >= 0;j--)
	  {
	      if(Soldier < number[j])
		  {
		     number[j+1] = number[j];                  //減少交換次數
		  }
		  else
		  {
		     number[j+1] = Soldier;
			 break;
		  }
	  }
	  if(j < 0)
		  number[0] = Soldier;
   }
}

該演算法雖然時間複雜度還是在O(n^2)但是卻是進行了很多的優化,我們不妨來計算一下,假設輸入(n+1)個資料,那麼最壞的情況就是初始順序和我們要排列的順序相反,完全逆序。那麼第一次需要排列1次,第二次2次,。。。。依次類推。那麼最後是

(1+2+3+4+。。。。+n) = O(n^2);但是實際上是O(n*(n+1)/2),還是比冒泡的直接O(n^2)要快上不少的。

       3.希爾排序,其實希爾排序就是上面直接插入排序的優化版本。但是希爾演算法非常好的利用了直接插入演算法的長處,對於儘可能有序的數列進行插入排序的時候可以達到O(n)時間複雜度這個特性,所以希爾演算法就是先將資料進行分組,具體怎麼分組這個大家可以網上百度,有非常多的分組方法,因為並沒有一個定論怎麼樣分組是最優的。我們這裡採用分組數是原始資料個數不停的二分。

void FUZHU_Sort(int *number,int numberSize,int step)
{
   int i,j,k,Soldier;
   for(k = 0;k < step;k++)
   {
	   for(i = k+step;i < numberSize;i+=step)                  //這裡和直接插入排序是有區別的,因為並不是0,1,2這樣連著排序的,而是
	   {                                                       //0,0+step,0+step+step這樣排序的
		  Soldier = number[i];
		  for(j = i-step;j >= 0;j-=step)
		  {
			  if(Soldier < number[j])
			  {
				 number[j+step] = number[j];
			  }
			  else
			  {
				 number[j+step] = Soldier;
				 break;
			  }
		  }
		  if(j < 0) 
			  number[k] = Soldier;                               //多組的起始位置
	   }
   }
}

void Shell_Sort(int *number,int numberSize)
{
   int step = numberSize/2;
   while(step > 0)
   {
       FUZHU_Sort(number,numberSize,step);
	   step = step/2;
   }
}

     4.歸併排序,歸併排序的思想是,把帶排序的資料不停的折半,最後折半到每一半都只有一個資料,那麼這個資料肯定是有序的,然後再把左右的歸併,歸併就是用一個迴圈來挨個比較兩邊的資料,小(大)的進入一個臨時陣列,最後比較完畢再把臨時陣列放回去。

void Merge_Sort(int *number,int numberSize,int low,int high)
{
        if(low == high)
          return;
        int mid = (low+high)/2;
        //分治
        Merge_Sort(number,numberSize,low,mid);
        Merge_Sort(number,numberSize,mid+1,high);
        //歸併
        int i =  low,j = mid+1;
	int *temp = (int *)malloc(sizeof(int)*(high-low+1));
	int count = 0;
	while(i <= mid && j <= high)
	{
	    if(number[i] < number[j])
	      temp[count++] = number[i++];
	    else
	      temp[count++] = number[j++];
	}
	//還有沒做完的:
	while(i <= mid)
             temp[count++] = number[i++];
	while(j <= high)
	     temp[count++] = number[j++];
	for(i = low;i <= high;i++)
		number[i] = temp[i-low];
}

歸併排序的分治歸併的狀態方程:T(n) = 2 * T(n/2)+O(n)很容易推出其時間複雜度是O(nlogn)(主定理法是可以直接看出來的,詳細見前面的時間複雜度的分析。)

       5.快速排序,思想是每次選取一個基值,然後將每個數和這個數比較,大於它的放後面,小於它的放前面。然後從這個點斷開分成兩邊繼續重複這個操作,直到最後一個數。其實也是分治法。

void quick_Sort(int *number,int numberSize,int low,int high)
{
	if(low == high)
	  return;
       //先排序後分治
	int i = low,j = high;                              //首尾計數器
	int mid = (low+high)/2;
	int base_number = number[low];                     //每次以第一個作為基準
	int temp;
	while(i < j)
	{
	    while(number[j] >= base_number && j > i)
			j--;
		if(j <= i)
		  break;
		else
		{
			temp = number[j];
			number[j] = number[i];
			number[i] =temp;
		}
		while(number[i] < base_number && i < j)
			 i++;
		if(i >= j)
          break;
		else
		{
			temp = number[j];
			number[j] = number[i];
			number[i] =temp;
		}
	}
	for(int k = low;k < numberSize;k++)
	{
		if(number[k] == base_number)
			break;
	}
	quick_Sort(number,numberSize,low,k);
	quick_Sort(number,numberSize,k+1,high);
}

本段程式碼是設定了一個首位計數器,先從尾部開始,大於基數不管,一旦小於基數就和首指標指向的數交換,然後首指標開始運作,一旦首指標指向的數大於基數,那麼就和尾指標指向的數交換,然後尾指標開始運作,最後到首尾指標重合結束一次。快速排序的時間複雜度不難看出是O(nlogn)因為分治分方程基本是一樣的和前面的。

       以上都是一些簡單的常用的排序,當然都是有很大優化空間的,這些可以自己思考,然後一些稍微複雜的排序將會在後序更新。

       

相關文章