数据结构与算法笔记(一) 程序性能分析
程序性能:一个程序对内存和时间的需要。要对数据结构和算法给予评价,就必须能够计算程序性能
1. 用操作数和执行步数估计程序的运行时间
2. 用符号法描述程序在最好,最坏,平均情况下的运行时间。
确定程序性能: 分析方法, 实验方法
知道两个概念:
空间复杂度:程序运行时所需的内存空间的大小
时间复杂度:运行程序所需要的时间
空间复杂度的组成:
1.指令空间:编译之后的程序指令所需要的存储空间
2.数据空间: 所有常量和变量所需要的存储空间
3.环境栈空间:环境栈用来保存暂停的函数和方法在恢复运行时所需要的信息。
任意程序所需要的空间可以表示为: c+S, 其中c是固定部分,S是可变部分
实例分析: 递归函数实现n个元素所有排列的输出
例子:
顺序查找(非递归)
template<typename T>
int sequenceSearch(T list[], int n, const T& key)
{// list[] 查找的列表, n列表长度, key查找的元素for(int i=0; i<n && list[i]!=key; i++); // 注意这里的写法,将判断条件直接放到for里面if(i==n) return -1;else return i; //返回index
}
这个程序的空间复杂度: S=0, (需要的参数已经在函数外定义) int i; 呢
顺序查找(递归)
template<typename T>
int sequenceSearch(T list[], int n, const T& key)
{// list[] 查找的列表, n列表长度, key查找的元素if(n<1) return -1; // 停止条件if(list[n-1]==key) return n-1; // 满足匹配条件sequenceSearch(list, n-1, key); // 递归调用
}
空间复杂度: 递归的深度为:max{n, 1},每次递归栈需要保存返回地址(4字节), S = 4*max{n,1}
时间复杂度:
程序需要的时间T:
T = 编译时间 + 运行时间
主要关注运行时间Tp(n), n为问题的实例特征
计数操作:
选择一种或多种关键操作(+-*/),确定每一种操作进行的次数
例如:
多项式求和: P(x)=C0+C1*X+C2*X^2+...Cn*X^n // 按照常规方法
template<typename T>
T polyEval(T coeff[], int n, const T& x)
{// coeff 多项式系数 n多项式阶数 x取值T y = 1;T value = coeff[0];for(int i=0; i<n; i++){y = y*x;value = value + y*coeff[i];} return value;
}
时间复杂度:在n次的for循环中,每次进行2次乘法和一次加法,总共进行2n次乘法和n次加法。
Horner分解多项式计算:
P(x) = 5*x^3-4*x^2+x+7=((5*x-4)*x+1)*x+7;
template<typename T>
T polyEval(T coeff[], int n, const T& x)
{// horner法则分解多项式 // coeff 多项式系数[a0,a1,a2...an] n多项式阶数 x取值T value = coeff[n];for(int i=1; i<=n; i++){value = value*x + coeff[n-i];} return value;
}
时间复杂度:在n次的for循环中,每次进行1次乘法和1次加法,总共进行n次乘法和n次加法。因此这种方法计算多项式更快
元素在序列中的名次计算:
【4,3,9,3,9】一个元素的名词等于比它小的元素的个数加它左边出现的与它相同的元素个数
template<typename T>
int rank(T a[], int n, T r[])
{// 结果早r[]中返回 for(int i=0; i<n; i++){r[i] = 0; // 初始化 } for(int i=1; i<n; i++){for(int j=0; j<i; j++) // 注有这里的实现方法; {if(a[i]>=a[j]) r[i]++;else r[j]++; }}
}
计数排序:在得到r[ ]数组的基础上,对a进行排序
template<typename T>
int rank(T a[], int n, T r[])
{// 结果早r[]中返回 for(int i=0; i<n; i++){r[i] = 0; // 初始化 } for(int i=1; i<n; i++){for(int j=0; j<i; j++) // 注有这里的实现方法; {if(a[i]>=a[j]) r[i]++;else r[j]++; }} *u = new T [n];for(int i=0; i<n; i++){u[r[i]] = a[i];} // 把u中的数组放回a中for(int i=0; i<n; i++){a[i] = u[i];} delete [] u; // 删除为u分配的内存
}
冒泡排序bulbSort
一次冒泡的过程,数列的最大元素一定会移动到数列的尾部,这与选择排序中寻找序列最大值并将其放到队尾的操作一致,接着对前n-1个元素进行相同的操作:
#include <iostream>
#include <string>using namespace std;template<typename T>
void bulbSort(T list[], int n)
{for(int i=n; i>0; i--) // 对前i个元素进行操作 {for(int j=0; j<i-1; j++) // 一次冒泡过程 {if(list[j]>list[j+1]) // 冒泡 {T temp = list[j+1];list[j+1] = list[j];list[j] = temp; } } }
}int main(int argc, char *argv[])
{cout << "Hello" << endl;int a[] = {1,5,2,9,5,6,4,3};// selectSort(a, 8);bulbSort(a, 8); for(int i=0; i<8; i++){cout << a[i] << " ";}cout << endl;return 0;
}
运行结果:
一次冒泡如果有n个元素,则需要n-1次的比较,n次冒泡一共需要比较的次数是:1+2+3...+(n-1)=n(n-1)/2
最好最坏和平均操作技术
因为操作技术并不一定单纯是问题的规模n的函数,例如bulbSort()函数,一次冒泡进行比较的次数与数组的整体值有关,交换次数可能在0到(n-1)之间。 平均操作技计数不好确定,集中分析最好和最坏操作计数。
例如:
在有序数组中插入元素:从数组右侧开始一次进行比较
template<typename T>
void insert(T list[], int& n, T key)
{// 假设数组a的容量大于nfor(int i=n-1; i>=0&&list[i]>=key; i--) // 注意这的条件 a[i+1] = a[i];a[i+1] = key;n++; // 数组a增加一个元素
}
分析比较次数 : 平均比较次数n/2 + n/(n+1)
在上面的选择排序中,如果序列已经是有序的,程序还是会进行比较,为了避免不必要的比较,可以在每次寻找最大元素的时候,检查序列是否有序:
#include <iostream>
#include <string>using namespace std;template<typename T>
void selectSort(T list[], int n)
{bool sorted=false; // 序列检查标志位for(int size=n; size>1&&!sorted; size--){int indexOfmax = 0;sorted = true;for(int i=1; i<size; i++){if(list[indexOfmax]<=list[i])indexOfmax = i;elsesorted = false; // 避免对有序的序列还进行比较 }T temp = list[indexOfmax];list[indexOfmax] = list[size-1];list[size-1] = temp; }
}int main(int argc, char *argv[])
{cout << "Hello" << endl;int a[] = {1,5,2,9,5,6,4,3};// selectSort(a, 8);selectSort(a, 8); for(int i=0; i<8; i++){cout << a[i] << " ";}cout << endl;return 0;
}
步数:
上面的操作计数中使用特定操作的计数来估算程序的时间复杂度。而在步数(step-count)中,将对程序的所有操作部分都进行统计。
一步(a step)是一个计算单位,他独立于选定的实例特征。
一个程序步: 可以定义为一个语法或者语义上的程序片段,该片段的执行时间独立于时间特征。
不是可以告诉我们随着实例特征的变化,程序的执行时间是如何变化的。
重点:学会剖析法分析程序的步数!
-------------------------------------------------------------------------------------------------------
当实例特征n很大的时候,需要使用渐进记法。
1. 了解各种记法
1 常量
logn 对数
n 线性
nlogn n倍对数
n^2 平方
n^3 立方
2^n 指数
n! 阶乘
大小顺序:
1 < longn < n < nlogn < n^2 < n^3 < 2^n < n!
表示f(n)渐进小于g(n)
表示f(n)渐进大于g(n)
表示f(n)渐进等于g(n)
时间复杂度实际上是衡量执行时间的变化趋势(变化率),当输入规模n趋于大规模时,执行时间的变化趋势。
--------------------------------------------------------------------------------
性能测量
程序的性能不仅依赖操作类型和数量,而且依赖数据和指令的内存模式。
计算机内存有等级之分,如L1高速缓存,L2高速缓存,主存。内存时间不同,访问模式也就不同
性能测量关注的是一个程序实际需要的时间和空间。
运行空间:
1.指令空间和静态分配的数据空间由编译器编译时确定,大小可以用操作系统指令得到。
2.递归栈空间和动态分配的空间可以通过分析的方法计算
测量运行时间,需要一个定时机制:可以使用c++函数clock()函数来测量,在头文件time.h中定义了常数CLOCK_PER_SEC,记录每秒流逝的 “滴答“ 数,并转换成秒数。CLOCK_PER_SEC=1000, 滴答一次等于一毫秒
选择排序程序:
#include <iostream>
#include <string>
#include <time.h>using namespace std;template<typename T>
void insertionSort(T list[], int n) // 注意选择排序的思路
{for(int i=1; i<n; i++) // 从a[i](i=1;i<n)开始,向前遍历 {T temp = list[i]; // a[i]插入a[0: i-1] int j;for(j=i-1; j>=0&&temp<list[j]; j--) // 数组元素后移 {list[j+1] = list[j];}list[j+1] = temp; // list[j+1] }
}int main(int argc, char *argv[])
{int a[1000], step = 10;cout << "The worst case time , in mill-second is " << endl;cout << endl << "Time" << endl;double clock_per_sec = static_cast<double>(CLOCKS_PER_SEC);for(int n=0; n<=1000; n+=step){// 用最坏测试数据初始化long int numberOfRepeat = 0;clock_t start_time = clock();do{for(int k=0; k<n; k++){a[k] = n-k;} insertionSort(a, n);numberOfRepeat++;}while(clock()-start_time<1000);double elapsee_time = (clock() - start_time)/clock_per_sec;cout << n << '\t' << numberOfRepeat << '\t' << elapsee_time/numberOfRepeat*1000 << endl;if(n==100) step = 100;}return 0;
}
测量的结果:
因为执行一次对于clock()来说时间太短,不便于测量,所以需要执行多次才能够进行测量
数据结构与算法笔记(一) 程序性能分析相关推荐
- 数据结构与算法笔记 - 绪论
数据结构与算法笔记 - 绪论 1. 什么是计算 2. 评判DSA优劣的参照(直尺) 3. 度量DSA性能的尺度(刻度) 4. DSA的性能度量的方法 5. DSA性能的设计及其优化 x1. 理论模型与 ...
- 数据结构与算法笔记(十六)—— 二叉搜索树
一.二叉搜索树定义 二叉搜索树(Binary Search Tree),又名二叉排序树(Binary Sort Tree). 二叉搜索树是具有有以下性质的二叉树: 若左子树不为空,则左子树上所有节点的 ...
- 数据结构与算法笔记(十五)—— 散列(哈希表)
一.前沿 1.1.直接寻址表 当关键字的全域U比较小时,直接寻址是一种简单而有效的技术.假设某应用要用到一个动态集合,其中每个元素都有一个取自全域U={0,1,-,m-1)的关键字,此处m是一个不很大 ...
- 数据结构与算法笔记(青岛大学王卓老师视频)
写在前面的话: 因为在学习数据结构之前,学习过一年的算法,所以有一些基础,一些我觉得 没必要的代码或知识就没写上,记得多是一些知识点,写的可能对于别人来说 很难接受,望谅解.我学习算法是在Acwing ...
- JS数据结构与算法 笔记
JS数据结构与算法笔记 前言:不定时更新说明 1. 栈(Stack) 1.1 基于数组实现栈 1.2 基于对象实现栈 1.3 基于链表实现栈 1.4 栈的简单应用 1.4.1 字符串中的括号匹配问题 ...
- 数据结构与算法笔记(王卓网课+教材+大话数据结构)
数据结构与算法笔记(王卓网课+教材+大话数据结构) ##最新整理!!! 顺序存储结构的线性表P10-P21 顺序线性表的代码实现 链式线性表笔记 串笔记 绪论.算法(P1-P9)1.4数据起源结构 数 ...
- 明翰数据结构与算法笔记V0.8(持续更新)
文章目录 前言 数据结构 `线性表` `数组` `链表` `栈与队列` [串/字符串] 树 并查集 `二叉树` [二叉排序树/二叉搜索树] `红黑树` 红黑树操作 霍夫曼树 `堆` [大/小]根堆 可 ...
- Java版数据结构与算法笔记
文章目录 一.数据结构与算法概述及题目 1.数据结构和算法的关系 2.线性结构与非线性结构 Ⅰ-线性结构 Ⅱ-非线性结构 3.经典面试题 Ⅰ-字符串匹配问题: Ⅱ-汉诺塔游戏 Ⅲ-八皇后问题: Ⅳ-马 ...
- 清华大学博士讲解Python数据结构与算法笔记
清华大学博士讲解Python数据结构与算法的笔记 Notion 连接: https://fine-individual-553.notion.site/Python-2bf3c87f1c504d758 ...
最新文章
- DevExpress v18.1新版亮点——WinForms篇(七)
- 每隔k次反转一次 链表_leetcode 25 K个一组翻转链表(c++)
- 多线程,你觉得你安全了?(线程安全问题)
- swiper 滚回第一个数据_名企必备的数据分析基础技能:Python大法(一)
- jquery mobile资源
- 从Firefox控制您喜欢的音乐播放器
- openglshader实现虚拟场景_虚拟演播室设计原则
- cent7中kickstart
- excel if in函数_Office教程:Excel函数AVERAGE与IF函数的组合标记平均值
- 阿里天池用Pandas揭秘美国选民的总统喜好附加题
- Python 学习笔记【12】字典
- 如何将文件地址转为url_如何快速替换WordPress站点新旧URL地址?
- 这份好用视频拼接的软件干货分享,值得收藏
- meta分析学习分享
- 凤凰网php,凤凰网房产频道招聘 web 前端工程师、PHP 工程师 15-25k,欢迎简历来砸~...
- 电脑N卡跟A卡是什么,有区别么?
- IDEA 断点出现 no executable code found at line
- Bert源代码(二)模型
- 初探 Redis 客户端 Lettuce:真香!
- 3.计算机网络——加密,数字签名,数字证书
热门文章
- springCloud工作笔记090---SpringCloud_redis配置多数据源_redis多数据源
- C++读取Json生成随机名字
- Php 小视频存储,用PHP保存远程视频到本机电脑的小插件
- 群晖安装pdo_mysql_使用docker方式部署禅道
- 随想录(常用的音视频、图像库)
- c语言 error2050,c语言程序设计20509new.doc
- 猴子爬山编程java_Java趣味编程100例
- 计算机专业最新研究领,「金仕教育」打码秃头?CS计算机专业研究方向与研究领域介绍...
- python用scrapy爬虫豆瓣_Python-用Scrapy爬取豆瓣电影
- python 数据驱动接口自动化框架_利用Python如何实现数据驱动的接口自动化测试...