练习: 解决问题P时所需要的空间 - S(P) = c + Sp(实例特征)
什么是实例特征 -> 由于是实例,所带来的特别之处有那些?
例1:简单情况下,一个函数所需要的固定空间c 与 变化空间Sp
template<class T>
T Abc(T& a, T& b, T& c){
return a+b+b*c+(a+b-c)/(a+b)+4;
}
那些因素有可能成为实例特征: (1).数据类型T (2). abc大小
- 观察本例,我们所用的形参是引用类型,因此具体T是什么类型与调用时所消耗内存无关
因为是引用,所以实际本函数永远只是在操作着一个指针,一个指针永远是两字节
- 所以本函数在调用的时候会消耗掉6字节拿来存储a,b,c的指针,无论T到底是什么,永远6字节
- 因此由于是实例所带来的特别之处 -> 0
但是如果这里不是引用,调用的时候需要将函数拷贝进去,那么T是什么,就与函数调用时,占用多少内存相关了
- 在常规参数的情况下,因为是实例所带来的特别之处,也就是S-p(实例特征) = 3* sizeof(T)
- 同样,如果你使用doule作为T的类型,那就会造成3*8的数据空间消耗
例二:
template<class T>
int SequentialSearch(T a[], const T& x, int n){
int i;
for (i = 0; i < n && a[i] != x; i++);
if (i == n) return -1 ;
return i;
}
(1). 无论是什么类型,a[ ]作为数组传过来的永远只是一个2字节的指针,a[ ]多大都不计入本函数消耗掉的内存
(2). 同样,x作为一个引用传过来,只消耗两字节的指针内存
(3). 本例中,两个整形常量0 与 -1全都要分配两字节内存
为什么a[ ]无论多大都不计入内存呢?
- 因为该数组所需要的空间,在定义实参,也就是a[ ]被给出的时候已经分配好了,这个函数所需要的空间,只是一个地址的大小。这个时候,a[ ]无论多大都与本函数所需要的内存空间无关了。显然 Sp(n)=0;
例三: 递归函数的实例特征与函数内存消耗
template<class T>
T Rsum(T a[], int n) {
if (n > 0)
return Rsum(a, n-1) + a[n-1];
return 0;
}
本函数的实例特征是,n,不同实例所造成的差别只有数字n
递归调用函数 -> 涉及函数体内再调用函数 ->
- 函数体内调用一次函数所消耗的内存:实参所消耗的内存 + 函数返回地址所消耗的内存
- 一个数组a[ ]地址 两字节 + 整形n消耗两字节 + 返回地址两字节 -> 一次调用消耗6字节内存
递归深度n+1 因此一共会调用n+1次函数,一次6字节,因为实例不同所造成的差距6*(n+1)
时间复杂度估计
例一:
template<class T>
int Max(T a[], int n){//寻找最大元素
int pos = 0;
for (int i = 1; i < n; i++)
if (a[pos] < a[i])
pos = i;
return pos;
}
本函数中一共执行了这些比较:
- 在一个for循环里,每一回合都执行了 a[pos]与a[i]的比较,共计执行了n-1次
- for的每一个循环结束的时候i 都要与 n 进行一次比较
例二:
template <class T>
T PolyEval(T coeff[], int n, const T& x){
for ( int i = 1; i <= n; i++){
y *= x;
value += y * c [ i ] ;
}
return value;
}
- 可以模拟为这是一个循环,并且这个循环被执行了n次
- 每执行一次包含有一次加法 + 两次乘法
- 在这里面,实例特征是整数n
- 可能还包含有每次循环时,i与n做出的比较环节
例三: 利用if法判断一个元素在一个array里面的名次
template <class T>
void Rank(T a[], int n, int r[]){
for ( int i = 1; i < n; i++)
r[i] = 0; //初始化
for ( int i = 1; i < n; i++)
for ( int j = 1; j < i; j++)
if (a [j] <= a[i]) r[i] + + ;
else r[j ] + + ;
}
这样的排序行为可以理解为:
- 首先将代表每一个数排名的r[i]初始化
- 然后从两个数字开始讨论,a[i]=a[2]代表了第二个元素,如果第二个元素更大一点,给r[2]加一,代表在两个数字的排序行为里,a[2]更胜一筹
- 接下来从三个数排序里,a[1],a[2]分别与a[3]做出比较,如果a[1]更大,则在两轮比较中,r[1]会被加上两次
- 如果a[2]第二大,则说明,在两轮比较中,一轮r[2]没加,另一轮r[2]加一,最小的a[3]则在两轮比较中什么都没加
总的来说,i一共经历了四个数字,每个数字执行i-1次比较,因此一共执行了1+2+3…n-1次比较
冒泡排序:一种比较经典的排序模型
(1). 在一回合里,可以做到将最大的数放到程序的最右边
(2). 如果经历n-1回合,就可以实现对array的排序
(1). 一个回合是怎么把最大的数放到最右边的
template <class T>
void Bubble (T a[], int n){
for ( int i = 0; i < n-1; i++)
if( a[i] > a[i +1]) Swap(a[i], a[i + 1 ] ) ;
}
- 从头到尾,将数据0/1,1/2,2/3….. 比对,如果谁大就放置到右边,这样下来就可以做到最大的一定在右边
- 本函数一共经历了n-1次比对
(2). 如何做到排序整个数组的 - 将本函数执行n-1回合
template <class T>
void BubbleSort (T a[], int n){
for ( int i = n; i>1; i- -)
Bubble(a,i);
}
- 从长度n开始,逐渐从n-1,n-2……一直执行到长度为2的数组,每一个都经历了一次比对
- 所以一共经历了 1+2+…..n-1次数比对,即完成了 排序+比对 过程