常見排序的最好、最壞和平均時間複雜度+空間複雜度分析

爲了加深對平均時間複雜度、最好時間複雜度、最壞時間複雜度、空間複雜度的理解,以常見的排序算法爲例,分析其時間和空間複雜度。

1 冒泡排序

平均時間複雜度O(n^2)
最壞時間複雜度O(n^2)
最好時間複雜度是針對改進後的冒泡排序(增設標誌位)
改進後的冒泡排序的代碼:

vector<int> bubbleSort(vector<int>arr)
{
   for(int i=0;i<arr.size()-1;i++)
   {
     bool hasSwap=false;
	 for(int j=0;j<arr.size()-1-i;j++)
	 {
	    if(arr[j]>arr[j+1])
		{
		  hasSwap=true;
		  int tmp=arr[j];
		  arr[j]=arr[j+1];
		  arr[j+1]=tmp;
		}
	 }
	 if(!hasSwap)
	   return arr;
   }
   return arr;
}

當輸入的序列本來就是順序序列,經過一輪排序即可結束冒泡排序
所以最好時間複雜度是O(n)

2 選擇排序

vector<int> selectionSort(vector<int>arr)
{
  for(int i=0;i<arr.size()-1;i++)
  {
    int current_min_index=i;
	int current_min=arr[i];
	bool needSwap=false;
    for(int j=i+1;j<arr.size();j++)
	{
	  if(arr[j]<current_min)
	  {
	     needSwap=true;
	     current_min_index=j;
		 current_min=arr[j];
	  }
	}
	if(needSwap)
	  {
	    int tmp=arr[i];
		arr[i]=arr[current_min_index];
		arr[current_min_index]=tmp;
	  }
  }
  return arr;
}

平均時間複雜度O(n^2)
最壞時間複雜度O(n^2)
和冒泡排序不同,冒泡排序經過一輪比較,如果沒有交換,則說明該序列已經有序,可以提前結束排序。而選擇排序不同,每一輪比較只是選出第i小的元素,其餘元素的大小順序並不能保證, 所有不能提前結束。
因此,選擇排序的最好時間複雜度仍爲O(n^2)

3 插入排序

平均時間複雜度是O(n^2)
最壞時間複雜度是O(n^2)
當輸入序列本就是順序序列,一共經過n-1輪排序,每輪排序都不需要移動元素,因此,最好時間複雜度是O(n)

//插入排序
vector<int> insertSort(vector<int> arr)
{
  //從第一個元素作爲基準元素,從第二個元素開始把其插到正確的位置
  for(int i=1;i<arr.size();i++)
  {  
     //如果第i個元素比前面的元素大,則不需要做改變
	 //如果第i個元素比前面的元素小,需要在前面已經排好序的序列中找到第i個元素的位置
     if(arr[i]<arr[i-1])
	 {
	   int j=i-1;
	   //因爲後面元素後移會覆蓋掉第i個元素,所以先將其保存到一個變量中
	   int waitInsertElem=arr[i]; 
	   //比第i個元素大的元素依次後移,直到找到第一個比第i個元素小的元素,在該元素後插入第i個元素
	   while(j>=0 && arr[j]>waitInsertElem)
	   {
	     arr[j+1]=arr[j];
		 j--;
	   }
	   arr[j+1]=waitInsertElem;
	 }
  }
  return arr;
}

4 歸併排序

平均時間複雜度O(nlogn)
最好和最壞時間複雜度(nlogn)

//歸併排序
void merge(vector<int> &data,int start,int mid,int end)
{
  vector<int>tmp;
  int i=start,j= mid +1;
  while(i != mid +1 && j!= end  +1)
  {
    if(data[i]<=data[j])
	  tmp.push_back(data[i++]);
	else
	  tmp.push_back(data[j++]);
  }
  while(i!= mid +1)
   tmp.push_back(data[i++]);
   
  while(j!= end +1)
   tmp.push_back(data[j++]);
   
  for(int i=0;i<tmp.size();i++)
   data[start+i]=tmp[i];
}

void merge_sort(vector<int> &data,int start,int end)
{
   if(start < end)
   {
      int mid=(start + end)/2;
      merge_sort(data,start,mid);
	  merge_sort(data,mid+1,end);
	  merge(data,start,mid,end);
   }
}

歸併排序每次遞歸需要用到一個輔助表,長度與待排序的表相等,雖然遞歸次數是O(logn),但每次遞歸都會釋放掉所佔的輔助空間,所以下次遞歸的棧空間和輔助空間與這部分釋放的空間就不相關了,因而空間複雜度還是O(n)

5 快速排序

平均時間複雜度O(nlogn)
最好時間複雜度O(nlogn)
當輸入序列是正序或者逆序,每次劃分只得到一個比上一次劃分少一個記錄的子序列,注意另一個爲空。如果遞歸樹畫出來,它就是一棵斜樹。此時需要執行n-1次遞歸調用,且第i次劃分需要經過n-i次關鍵字比較才能找到第i個記錄,也就是key的位置,因此比較次數爲n(n-1)/2,因此,最壞時間複雜度爲O(n^2)

空間複雜度主要是遞歸造成的棧空間的使用。最好情況下,遞歸樹的深度是logn,其空間複雜度是O(logn)。最壞情況下,需要進行n-1遞歸調用,其空間複雜度爲O(n)。平均情況下,空間複雜度爲O(logn)
6 堆排序
堆排序每構建或調整一次堆,就得到第i大的數。如果需要將整個序列有序,需要n-1次構建或調整堆。每次調整堆的時間複雜度是logn,所以堆排序的時間複雜度爲O(nlogn),最好、最壞和平均時間複雜度都是O(nlogn)
由於堆排序沒有使用額外的空間來存儲排序的數據,所以應該是O(1)

發佈了95 篇原創文章 · 獲贊 8 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章