常见排序的最好、最坏和平均时间复杂度+空间复杂度分析

为了加深对平均时间复杂度、最好时间复杂度、最坏时间复杂度、空间复杂度的理解,以常见的排序算法为例,分析其时间和空间复杂度。

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)

发布了90 篇原创文章 · 获赞 8 · 访问量 8242

猜你喜欢

转载自blog.csdn.net/weixin_43854189/article/details/102994782