常見的排序算法分析

1、冒泡排序。冒泡排序執行的時間取決於比較的趟數,在最好的情況下,待排序的記錄是正序,算法只執行一趟,進行了n-1次關鍵碼的比較,不需要移動記錄,時間複雜度是O(N);

最壞的情況下,是倒序,每次比較只有一個最大的記錄被交換到最終的位置,故算法執行了n-1趟,每次比較的次數是n-i,關鍵碼比較的次數是n(n-1)/2.。

平均情況的時間複雜度與最壞的情況是同一個數量級的。

空間複雜度o(1).  它是穩定的排序算法。

代碼如下:

void buffersort1(int array[],int len)
{
bool exchange;//記錄是否有交換
for (int i = 0; i < len-1; i++)//外層循環比較的趟數N
{
exchange = false;
for (int j = 0; j < len-i-1;j++)//內層循環每趟需要比較的次數
{
if (array[j]>array[j + 1])
{
int t = array[j];
array[j] = array[j+1];

array[j+1] = t;

                       exchange = true;

               }

}
if (exchange == false)
return;

}

2、快速排序

快速排序算法也是基於一種交換的排序算法,是對冒泡排序的一種改進。在冒泡中,記錄的比較和移動是在相鄰的位置進行的,記錄每次交換隻能後移一個位置,因而中的比較和移動的次數比較多。在快速排序中,它的思想是記錄的比較和移動都是從兩端向中間位置進行的,關鍵碼較大的記錄一次就能從前面移動到後面,關鍵小的記錄一次就能從後面移動到前面。記錄移動的距離較遠,從而減少了總的比較次數和交換的次數。

首先選擇一個軸值,將待排序的記錄劃分爲獨立兩部分,左側的記錄都是小於軸值的,右側的記錄都是大於軸值的。

int partion(int arr[], int low, int high)
{
	while (low < high)
	{
		while (low < high&&arr[low] <= arr[high])
			high--;
		if (low < high)
		{
			int t = arr[low];
			arr[low] = arr[high];
			arr[high] = t;
			low++;
		}
		while (low < high&&arr[low] <= arr[high])
			low++;
		if (low < high)
		{
			int t = arr[low];
			arr[low] = arr[high];
			arr[high] = t;
			high--;
		}
	}
	return low;
}
void sort(int array[],int low,int high)
{
	if (low < high)
	{
		int pos = partion(array, low, high);
		sort(array, low, pos - 1);
		sort(array, pos + 1, high);
	}


}

快速排序的趟數取決於遞歸的深度,最好的情況下是每次劃分對一個記錄定位後,該記錄的左側和右側序列的長度一樣,在具有N個記錄的序列中,對一個記錄定位需要對整個序列進行掃面一遍,則所需要的時間爲O(N).每次劃分的時間爲o(log2n),因此總的時間複雜度是nlog2n.

最壞的情況是,待排序的序列是正序或者倒序,每次劃分只得到有個比上次少一個記錄的子序列,另一個爲空。此時必須進行N-1次遞歸才能吧所有記錄定位,每次需要比較的次數是n-i,因此時間複雜度是o(N2).平均情況和最壞的情況是一個數量級的。

空間複雜度最好的情況是(log2n),最壞的情況是o(n-1),平均情況是o(log2n).

快速排序是一個不穩定的排序算法。

3、簡單選擇排序

簡單選擇排序算法的思想是在第I趟排序中找出最小的元素,並和第I個記錄進行交換。因此選擇排序算法與序列的初始狀態是無關的,無論待排序的序列是什麼樣子的,每趟排序都是比較N-I次。總的比較次數是O(N2),這是選擇排序算法最好。最壞。平均的時間複雜度。它是不穩定的排序算法。

空間複雜度是o(1).

void selectsort(int r[],int n)
{
	int j = 0;
	for (int i = 0; i < n; i++)
	{
		int index = i;
		
		for (j = i + 1; j < n; j++)
		{
			if (r[j]<r[index])index = j;
		}
		if (index != i)
		{
			int t = r[i];
			r[i] = r[index];
			r[index] = t;
		}
	}
	
}

4、堆排序算法

堆排序算法是簡單選擇算法的一種改進,改進的着眼點是:如何減少關鍵碼的比較次數。簡單選擇排序在一趟排序中僅選出最小的關鍵碼,沒有把一趟比較的結果保存下來,因而記錄比較的次數就多了。堆排序在選出最小碼的同時,也找出較小的關鍵碼,也找出較小的關鍵碼,從而較少了比較的次數。

大根堆的定義:每個節點的值都大於等於其左右孩子的值。堆頂節點是所有節點的最大值。

堆排序思想:首先將待排序的序列建立一個大根堆,然後將堆頂元素與最後一個元素交換,再不斷調整堆的過程。堆排序的運行時間主要消耗在初始建堆的過程和不斷調整堆的過程。

首先是將待排序的序列建立一個堆,初始堆

void max_heapmdf(int arr[],int k,int n)//  調整堆
{
	int i = k;
	int j = 2 * k + 1;
	while (j < n)
	{
		if ((j+1 < n) && (arr[j] < arr[j + 1]))
		{
			j = j + 1;
		}
		if (arr[i]>arr[j])
			break;
		else
		{
			swap(arr[i],arr[j]);
			i = j;
			j = 2 * i + 1;
		}
		
	}
}
void buildheapmax(int arr[],int n)//建立初始堆
{
	for (int i = n / 2 - 1; i >= 0; i--)
	{
		max_heapmdf(arr, i, n);
	}

}
void heapsort1(int arr[],int n)
{   
	buildheapmax(arr, n);
	for (int i = 1; i < n; i++)
	{
		swap(arr[0],arr[n-i]);
		max_heapmdf(arr, 0, n-i);
	}
}

初始堆的建立需要O(N),第I次取堆頂元素重建堆的時間是需要O(log2i),並且需要去N-1次,因此總的時間複雜度是o(nlog2n).向下調整堆的時間與樹高有關。堆排序的平均時間、最壞、最好時間都一樣。對,記錄的初始狀態無關。

空間複雜度O(1).

5、插入算法

思想是給每個元素找位置,將每一個記錄插入到已經排好序的序列中。即是將排序序列分成兩組,有序組和無序組,每次從無序組中取一個元素,與有序數組中每個元素進行比較,找到合適的位置,並插入到有序數組中。一般將第一個元素視爲有序數組。


void insertsort(int arr [],int len)
{
   for(int i=1;i<len;i++)
   {
      int k=i;
      int temp=arr[k];
      for(int j=i-1;(j>=0)&&arr[j]>temp;j--)
     {   arr[j+1]=arr[j];
       k=j;
     }arr[k]=temp;
    }

}

最好的情況是正序,需要比較次數是n-1次,因此時間複雜度是o(n).最壞的情況是逆序,時間複雜度是o(n2);空間複雜度是 o(1).

直接插入排序算法是一種穩定的排序算法,當序列中基本有序或者待排序記錄比較少時,它是最佳的排序算法。

6、希爾排序

思想:對直接插入的改進,先將整個待排序記錄分割成若干個子序列,在每個子序列中分別進行直接插入排序,待整個序列基本有序時,再對全體記錄進行一次直接插入排序。

void shellsort(int array[], int len)
{
	int gap = len;
	for (gap = gap / 2; gap >0; gap = gap / 2)//注意gap 的條件,gap>0 或者gap>=1
	{
		for (int i = gap; i<len; i += gap)
		{
			int k = i;
			int temp = array[k];
			for (int j = i - gap; j >= 0 && array[j] > temp; j = j - gap)
			{
				array[j + gap] = array[j];
				k = j;
			}
			array[k] = temp;
		}
	}

}

時間複雜度是在o(n2)與o(nlog2n)之間,當n在某個特定範圍內,希爾排序的時間複雜度約爲o(logn1.3).空間複雜度是o(1).是不穩定的排序算法。

7.歸併排序

歸併排序通常採用2-路歸併排序算法。採用分治法。







void Merge(int arr,int low,int mid,int high)
{int i=low;
int j=mid+1;
int k=0; 
int *temp=new int [hih-low+1];
if(!temp)
{cout<<"申請失敗";
exit(0);
}
while(i<=mid&&j<=high)
{ if(arr[i]<arr[j])
  {temp[k++]=arr[i++];
  }
else
  {temp[k++]=arr[j++];
  }
}
while(i<=mid)
{
  temp[k++]=arr[i++];
}
while(j<=high)
{
  temp[k++]=arr[j++];
}
for(int i=low,k=0;i<=high;i++,k++)
{arr[i]=temp[k];
}
delete []temp;
}
void Msort(int arr[],int low,int high)
{
  if(low<high)
{
   int mid=(low+high)/2;
   Msort(arr,low,mid);
  Msort(arr,mid+1,high);
  Merge(arr,low,high,mid);
}
}

一趟歸併排序需要將待排序序列掃描一遍,其時間性能是o(n),整個歸併排序需要進行log2n趟,因此總的時間複雜度是o(nlog2n).這是其平均/最好、最壞的時間複雜度。

空間複雜度是o(n).是一種穩定的排序算法。

8、桶排序

將數組分配到有限數量的桶子裏,每個桶子再個別排序。這時可用冒泡、快排、等。

下列中:將數據作爲桶(數組)的下標存儲,適合於數據值的範圍比較小;比如:求在1G大小的字符串中,出現次數最多的那個字符;因爲字符char的邊界是[0...255],就可以將所有的字符遍歷、存儲到[0...255]桶中(數組下標).

  假設有n個數字,有m個桶,如果數字是平均分佈的,則每個桶裏面平均有n/m個數字。如果  

  對每個桶中的數字採用快速排序,那麼整個算法的複雜度是  

  O(n   +   m   *   n/m*log(n/m))   =   O(n   +   nlogn   -   nlogm)  

  從上式看出,當m接近n的時候,桶排序複雜度接近O(n)  


基數排序:
可以理解爲對桶排序的擴充,將數據分別按個位、十位、百位...分別放入buckets[0...9][num]的桶中。
即:先將個位數存儲到[0...9]的桶中,個位數將排好序
再按照個位數排好的序,將十位數存儲到[0...9]的桶中,十位數排好序(個位數上一輪已經排好序)...
再百位,千位...每次放入桶中後,桶的順序、桶內數據的順序是排好的。
例如:
下例中數組:
int array[] = {2, 343, 342, 1, 123, 43, 4343, 433, 687, 654, 3};
按個位依次排序(到桶中)
0|
1|1,
2|2,342
3|343,123,43,4343,433,3
4|654 
5| 
6| 
7|687 
8| 
9|
按值的順序存入到array[] = {1,2,342,343,123,43,4343,433,3,654,687};
由此可見,個位數是排好序的;

再將array[] = {1,2,342,343,123,43,4343,433,3,654,687}按十位數依次排序
0|1,2,3 
1| 
2|123  
3|433 
4|342,343,43,4343 
5|654  
6| 
7| 
8|687 
9|
按值的順序存入到array[] = {1,2,3,123,433,342,343,43,4343,654,687}
由此可見,個位數、十位數是排好序的;

再將array[] = {1,2,3,123,433,342,343,43,4343,654,687}按百位數依次排序
0|1,2,3,43 
1|123  
2| 
3|342,343,4343   
4|433  
5|   
6|654,687 
7| 
8|  
9|
按值的順序存入到array[] = {1,2,3,43,123,342,343,4343,433,654,687}
由此可見,個位數、十位數、百位數是排好序的; 

再將array[] = {1,2,3,43,123,342,343,4343,433,654,687}按千位數依次排序
0|1,2,3,43,123,342,343,433,654,687 
1| 
2| 
3|    
4|4343   
5|   
6|  
7| 
8|  
9|
按值的順序存入到array[] = {1,2,3,43,123,342,343,433,654,687,4343}

這時整個數組已經按升序排序完成。

基數排序的時間複雜度,假設待排序的關鍵碼由d 個組成,每個關鍵碼的取值範圍爲m個,則基數排序的時間複雜度是o(d*(n+m))。空間複雜度是 o(m) .它是穩定的排序算法。其中d是位數, n是關鍵碼個數,m是基數。

總結:

(1)在最好的情況下,直接插入排序和冒泡排序是最快的;在平均情況下,快速排序是最快的;在最壞的情況下,堆排序和歸併排序是對快的。

(2)當N比較大,關鍵碼分佈隨機,且對穩定性不做要求,採用快排;

(3)當N比較大,內存空間允許,且要求穩定,採用歸併;

(4)當N比較大,關鍵碼分佈可能出現正序或者逆序,且對穩定性不做要求,採用堆排序或者歸併。

(5)當B比較大,而只要找出最小前幾個記錄,採用堆排序或者簡繁選擇排序。

(6)當N比較小,記錄基本有序,且要求穩定,採用直接插入。

(7)當待排記錄個數比較小,記錄所含數據項較多,所佔的空間較大,採用簡單選擇。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章