时间复杂度

在算法的分析中,语句的执行次数T(n)是一个关于n(问题规模)的一个函数。分析n的变化引起T(n)的改变,进而得到T(n)的数量级,也就是时间频率。如果存在某一个辅助函数f(n),当n趋于无穷大时,T(n)/f(n)的值为一个不为0的常数,有T(n)=O(f(n)),这就是算法的渐进时间复杂度,也就是我们常说的时间复杂度

大O表示法:用O(f(n))来体现时间复杂度的方法被称作大O表示法;
大O推导法
O(1)叫做常数阶;O(n)叫做线性阶;O(n^2)叫做平方阶。

  1. 用常数1取代运行时间中的所有加法常数。
  2. 在修改后的运行次数函数中,只保留最高阶项。
  3. 如果最高阶项存在且不是1,则去除与这个项相乘的常数。得到的结果就是大O阶。

举一个简单的例子

int i;
for(i=0;i<n;i++)//该语句的复杂度为O(n)
{cout<<i;    //该语句的复杂度为O(1);
}

这段代码的时间复杂度为O(n),其中“cout<<i”的执行次数为1,是常量阶,所以它的复杂度为O(1);“for(i=0;i<n;i++)”的执行次数是n,是线性阶,所以它的复杂度为O(n)。整段代码的复杂度为O(1*n),也就是O(n).

常见的时间复杂度:
常数阶O(1),如果算法的执行时间不随着问题规模n的增加而增长,即使算法中有上千条语句,
其执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1)。对数阶O(log2 n),线性阶O(n),线性对数阶O(n log2 n),平方阶O(n^2),立方阶O(n^3)k次方阶O(n^K),指数阶O(2^n)。其他时间复杂度都会随着n的变化慢慢变大,算法开销也越来越大  。

常见的时间复杂度所损耗时间排序:
O(1)<O(log n)<O(n)<O(nlogn)<O(n ^2)<O(n ^3)<O(2 ^n)<O(n!)<O(n ^n)

空间复杂度

空间复杂度指的是一个程序在执行时,所占有的临时内存空间大小:S(n)=O(f(n));n是问题的规模,f(n)为语句关于n所占据的内存的函数。

举个简单的例子:
交换两个变量的值,它需要定义一个临时的变量,这就造成了空间复杂度,因为是常量阶,所以它的空间复杂度为O(1).

 int i = 10, j = 100;//两个需要交换的值//交换变量的值int temp=i;//定义临时变量i=j;j=temp;

提到空间复杂度,就需要提一下递归了,在进行递归的算法的时候,每一次的递归都会使用临时的变量保存递归的信息,所以递归的方法很消耗内存,也就是空间复杂度相对较高。如果递归次数过多,会造成内存超载,无法计算得到需要的数据。所以,当循环的次数过多的时候,尽量不要使用递归方法。

插入排序

原理分析

将一个记录插入到已排好序的序列中,从而得到一个新的有序序列

将序列的第一个数据看成是一个有序的子序列,然后从第二个记录逐个向该有序的子序列进行有序的插入,直至整个序列有序

代码实现

#include<iostream>
using namespace std;void printArray(int *arr, int len)
{for (int i = 0; i < len; ++i){cout << arr[i] << " ";}cout << endl;
}void InsertSort(int *arr, int len)
{for (int i = 1; i < len; ++i){if (arr[i] > arr[i - 1]){int temp = arr[i];int j = i - 1;for (; j >= 0 && temp > arr[j]; j--)        {arr[j + 1] = arr[j];}arr[j + 1] = temp;}}
}int main()
{int arr[] = { 4,5,8,9,1,2 };int len = sizeof(arr) / sizeof(int);//通过字节计算数组大小printArray(arr, len);InsertSort(arr, len);printArray(arr, len);return 0;
}

复杂度计算

1、当初始序列为正序时,只需要外循环n-1次,每次进行一次比较,无需移动元素。此时比较次数(C min)和移动次数(M min)达到最小值。
C min=n-1;
M min=0;

此时时间复杂度为O(n).
2、初始序列为反序时,需要外循环n-1次,每次排序中待插入的元素都要和[0,i-1]中的i个元素进行比较且要将这i个元素后移i次,此时比较次数和移动次数达到最大值。
C min=1+2+3+4+……+n-1=(n-1)n/2;
M min=1+2+3+……+n-1=(n-1)n/2;

此时时间复杂度为O(n^2).
3、在直接插入排序中只使用了i,j,tmp这三个辅助元素,与问题规模无关,空间复杂度为O(1)

冒泡排序

概念及思路

重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端,故称为"冒泡排序"。

代码实现

#include<iostream>
using namespace std;
void BubbleSort(int *a, int size)
{for (int i = 0; i < size; i++)//外循环,循环每个元素{for (int j = 1; j < size - i; j++)//内循环进行元素的两两比较{if (a[j] < a[j - 1])//判断相邻元素并进行交换{int temp = a[j];a[j] = a[j - 1];a[j - 1] = temp;}}}
}
int main()
{int a[10] = { 2, 7, 34, 54, 12, 5, 19, 33, 88, 23 };cout << "原来的数组为:" << endl;for (int i = 0; i < 10; i++){cout << a[i] << " ";}cout << endl;BubbleSort(a, 10);cout << "冒泡排序后的数组为:" << endl;for (int i = 0; i < 10; i++){cout << a[i] << " ";}return 0;
}

时间复杂度

外循环和内循环以及判断和交换元素的时间开销。
最优的情况也就是开始就已经排序好序了,那么就可以不用交换元素了,由于外层循环为n,内层所需要循环比较的次数为(n-1)、(n-2)…1由等差数列求和得时间花销为:[ n(n-1) ] / 2;所以最优的情况时间复杂度为:O( n^2 )
最差的情况也就是开始的时候元素是逆序的,那么每一次排序都要交换两个元素,则时间花销为:[ 3n(n-1) ] / 2;(其中比上面最优的情况所花的时间就是在于交换元素的三个步骤);所以最差的情况下时间复杂度为:O( n^2 )

空间复杂度

冒泡排序的辅助变量空间仅仅是一个临时变量,并且不会随着排序规模的扩大而进行改变,所以空间复杂度为O(1)。

选择排序

void Efferve()
{int m[5] = { 12, 8, 6, 9, 10 };int max = m[0];for (int i = 0; i < 4; i++){for (int j = i; j < 4; j++){if (m[j] < m[j + 1]){max = m[j + 1];m[j + 1] = m[j];m[j] = max;}}}
}

上面的代码可以看出选择排序套用了两个循环如下:

 for (int i = 0; i < 4; i++){for (int j = i; j < 4; j++){}}

当i=0下面循环4次,每次i+1下面循环都执行次4-i次,因此上述循环次数为T=(4-1))+ (4 -2)+(4 - 3)+ 1;
T=[4*(4-1)]/2次
哪当N个数进行排序时,将进行T=[N*(N-1)]/2次,根据计算方法保留最高次N ^2,因此选择排序的时间复杂度为O(N ^2);

因为排序中始终只用到了数组大小的空间,为常数,因此空间复杂度为O(1)。

快速排序

基本思路

在一个数组中,找一个数为基准数,将这个数中所有比基准数大的数放在该数的右边,比基准数小的数放在该数的左边。

例如"6 1 2 7 9 3 4 5 10 8"这个数组
以6作为基准数,将比6小的数放在6的组左边,比6大的数放在6的右边
得到:3 1 2 5 4 6 9 7 10 8
可以看出6的左边的数都比6小,而右边的数都比6大,此时6已经归位

具体步骤为:

  1. 先找一个基准数(一般为第一个),然后从右边开始向左找,找到第一个小于基准值的数,然后从左开始找,找到第一个小于基准值的数,然后进行交换,一直到左边和右边相遇时,则将基准数与找到的位置进行交换
  2. 以基准数为分界线,划分为左右2个数组,再以步骤1进行递归
  3. 当递归的数组中无法再继续递归时,循序则结束,此时的数组则已经完成了排序

代码实现

void QuickSort(int *n, int left, int right)
{if (left > right)return ;int temp = n[left];                   //temp中存的数为基准数int i = left, j = right;int t;while (i != j){//一定要先从右向左找while (i < j && n[j] >= temp)j--;while (i < j && n[i] <= temp)i++;if (i < j){t = n[i];n[i] = n[j];n[j] = t;}}n[left] = n[i];n[i] = temp;QuickSort(n, left, i - 1);         //处理左边的数QuickSort(n, i + 1, right);            //处理右边的数
}

复杂度计算

空间复杂度:logn
主要是由于递归造成的栈空间的使用,
最好的情况下其树的深度为:log2(n)
空间复杂度为 O(logn)
而最坏的情况下:需要n-1次调用,每2个数都需要交换,此时退化为冒泡排序
空间复杂度为 O(n)
平均时间复杂度为:O(logn)

时间复杂度:O(nlogn)
由于快速排序用到了递归调用,因此计算其时间复杂度也需要用到递归算法计算
递归算法的时间复杂度公式:T[n] = aT[n/b] + f(n)
**

最优情况下时间复杂度
快速排序最优的情况就是每一次取到的元素都刚好平分整个数组

此时的时间复杂度公式则为:T[n] = 2T[n/2] + f(n);T[n/2]为平分后的子数组的时间复杂度,f[n] 为平分这个数组时所花的时间
第一次递归:
T[n] = 2T[n/2] + n;
第二次递归:
令 n = n/2
= 2^2 T[ n/ (2^2) ] + 2n
第三次递归:
令:n = n/(2^2)
= 2^3 T[ n/ (2^3) ] + 3n

第m次递归:
令:n = n/( 2^(m-1) )
= 2^m T[1] + mn

当最后平分的不能再平分时,也就是说把公式一直往下跌倒,到最后得到T[1]时,说明这个公式已经迭代完了(T[1]是常量了)。
得到:T[n/ (2^m) ] = T[1] ===>> n = 2^m ====>> m = logn;
T[n] = 2^m T[1] + mn ;其中m = logn;
T[n] = 2^(logn)T[1]+lognn = n +nlogn
又因为当n >= 2时:nlogn >= n (也就是logn > 1),所以取后面的 nlogn;

综上所述:快速排序最优的情况下时间复杂度为:O( nlogn )

最差情况下时间复杂度
最差的情况就是每一次取到的元素就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次都排好一个元素的顺序)
此时的时间复杂度为:T[n] = n * (n-1) = n^2 + n;
综上所述:快速排序最差的情况下时间复杂度为:O( n^2 )

平均时间复杂度
快速排序的平均时间复杂度也是:O(nlogn)

参考:
https://blog.csdn.net/not_in_mountain/article/details/77976743

本博文由小队成员(Wu_0526,qq_49032326,ly1196324806,SamGeren,qq_39124199)共同编写

选择排序,插入,快排,冒泡排序的时间空间复杂度详解相关推荐

  1. 编程算法总结(冒泡排序,选择排序,快排)

    项目需要,自己上学的时候接触过一些算法,我记得当时算法那门考了系里最高分,98分,想着没什么用呢,谁知道这两天就用到了,项目中涉及到了排序,我就重温了一下算法,说到算法,就我个人而言,第一就是想到了冒 ...

  2. 02_Python算法+数据结构笔记-冒泡排序-选择排序-插入排序-快排-二叉树

    b站视频:路飞IT学城 清华计算机博士带你学习Python算法+数据结构_哔哩哔哩_bilibili 文章目录 #11 排序介绍 #12 冒泡排序介绍 #13 冒泡排序 #14 选择排序 #15 插入 ...

  3. 快排亲兄弟:快速选择算法详解

    后台回复进群一起刷力扣???? 点击下方卡片可搜索文章???? 读完本文,可以去力扣解决如下题目: 215.数组中的第 K 个最大元素(Medium) 快速选择算法是一个非常经典的算法,和快速排序算法 ...

  4. 2(3).选择排序_快排(线性表)

    2019独角兽企业重金招聘Python工程师标准>>> #define maxn 100000 #include<cstdio> #include<cstring& ...

  5. 排序算法 | 快排、冒泡、堆排、归并、基数、递归、希尔、计数

    文章目录 写在前面 排序 1. 基数排序`稳定` 2. 归并排序`稳定`merge sort 3. 快速排序`不稳定`quick sort 4. 堆排序`不稳定`heap sort 大根堆 小根堆 5 ...

  6. 数据结构—排序算法总结(插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、合并排序、计数排序)

    *排序 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作. 稳定性 在待排序的数组中,如果出现多个相同的关键字,例如:98751555512,中出现重复的数字,在 ...

  7. 20191007:选择排序,插入排序,冒泡排序详解

    选择排序,插入排序,冒泡排序详解 描述 图例 代码实现 描述 选择排序:将要排序的对象分作两部份,一个是已排序的,一个是未排序的,从后端未排序部份选择一个最小值,并放入前端已排序部份的最后一个. 插入 ...

  8. 排序(下)---快排、归并

    排序(下)-快排.归并 快速排序 采用分治的思想:每次选区一个数作为基准,让整个数据中凡是大于此数的放在此数的右边,小于此数的放在此数的左边,然后进行对此数的两边进行选基准,如上方式的分法,递归下去, ...

  9. 排序算法--快排的优化

    排序算法–快排的优化 下面是我写的一种快排: #include <iostream> #include <stdlib.h>using namespace std;void P ...

最新文章

  1. 【学习笔记】超简单的快速傅里叶变换(FFT)(含全套证明)
  2. 压缩感知专题笔记——目录
  3. 小白入门angular-cli的第一次旅程(学习目标 1.路由的基础知识 参数订阅写法)
  4. python为运行为何出现乱码_解决执行python脚本出现乱码的问题
  5. PST文件的读取(待整理)
  6. SpringBoot整合Shiro实现登录认证和授权CHCache
  7. Yarn 和 Npm 命令行切换 摘录
  8. Hbuilder 跳转另一个app
  9. 用户界面设计实验指导书
  10. 各种开源数据库同步工具汇总
  11. attiny85(digispark)零延迟启动探究
  12. JavaScript的toast
  13. java小球下落_java基础-小球下落问题
  14. 30分钟学会shapely空间几何分析
  15. 数据结构(二): 链表篇
  16. 基于随机森林算法的贷款违约预测模型研究(Give me some credit)
  17. 用HTML制作一首诗
  18. 通信原理-第9章-数字信号的最佳接收
  19. 贸然用string比较的后果
  20. 帝国cms首页写php,帝国cms常用标签汇总

热门文章

  1. 渗透学习之PHP--webshell
  2. android 动画 空白页,Android WebView打开网页一片空白的解决方法
  3. BootStrap Validator 版本差异问题导致的submitHandler失效问题的解决方法
  4. 空间两个直线之间的距离和公垂线
  5. 华为云服务器被曝异常,罗永浩退网不退直播,知网开放个人查重服务,今日更多大新闻在此...
  6. 微信支付服务商家 微信支付在海外发展
  7. 套在 360 黑匣子外面的黑盒子:你被技术型枪稿吓到了么?
  8. 你做这样一个网站需要多少钱?
  9. 信息奥赛课课通(C++)军训排队
  10. Java输入字符串输出成语_java - 在Java中,你怎么把这种模式/成语? - SO中文参考 - www.soinside.com...