爲了加深對平均時間複雜度、最好時間複雜度、最壞時間複雜度、空間複雜度的理解,以常見的排序算法爲例,分析其時間和空間複雜度。
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)