为了加深对平均时间复杂度、最好时间复杂度、最坏时间复杂度、空间复杂度的理解,以常见的排序算法为例,分析其时间和空间复杂度。
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)