编程语言
首页 > 编程语言> > C++ 性能骨灰级优化(推荐)

C++ 性能骨灰级优化(推荐)

作者:互联网

文章目录

一、前言

二、时间复杂度

1、定义

2、举例

	s = a[0];
	for(i = 0; i < n; ++i) {
		s += a[i];
	}
	for(i = 0; i < n; ++i) {
		for(j = 0; j < n; ++j) {
			s += a[i][j];
		}
	}
	l = 1, r = n;
	while(l <= r) {
		mid = (l + r) >> 1;
		if(a[mid] <= v) {
			r = mid + 1;
		}else {
			l = mid + 1;
		}
	}
	int fib(unsigned int n) {
		if(n <= 1) {
			return n;
		}
		return fib(n-1) + fib(n-2);
	}
int stk[MAXN], top, has[MAXN];

void dfs(int n, int depth) {
	int i;
	if (depth == n) {
		for (i = 0; i < n; ++i) {
			printf("%d", stk[i]);
		}
		puts("");
	}
	for (i = 0; i < n; ++i) {
		if (!has[i]) {
			has[i] = 1;
			stk[top++] = i+1;
			dfs(n, depth + 1);
			--top;
			has[i] = 0;
		}
	}
}

三、C++ 写法优化

1、查找时用键值对代替数组

1)哈希数组

【场景1】从长度为 n n n 的数组中查找某个数字是否存在

const int n = 100000;
void init() {
	for (int i = 0; i < n; ++i) {
		a[i] = 5 * i;
	}
}
bool find(int target) {
	for (int i = 0; i < n; ++i) {
		if (target == a[i]) {
			return true;
		}
	}
	return false;
}
int countBy() {
	int cnt = 0, m = 100000;
	while(m--) {
		if (find(6857112) ) {  // 这里举了个最坏的例子,永远找不到的情况
			++cnt;
		}
	}
	return cnt;
}

【优化1】利用哈希数组的索引来代替数组的遍历

const int n = 100000;
void init() {
	memset(hashtbl, 0, sizeof(hashtbl));
	for (int i = 0; i < n; ++i) {
		hashtbl[ 5 * i ] = 1;
	}
}
bool find(int target) {
	return hashtbl[target];
}

2)map

【场景2】从长度为 n n n 的数组中查找某个字符串是否存在

【优化2】利用 STL 的 map 来代替数组的遍历

const int n = 100000;
map<string, bool> maphash;

void init() {
	maphash.clear();
	for (int i = 0; i < n; ++i) {
		maphash[ str[i] ] = 1;
	}
}
bool find(string target) {
	return maphash.find(target) != maphash.end();
}

3)unordered_map

【优化3】利用 STL 的 unordered_map 来代替数组的遍历

4)效率排行总结

【思考题1】

	int sum = 0;
    for (int i = 0; i < n; ++i) {
        for(int j = 0; j < m; j++) {
            if(A[i] == B[j]) {
                sum += (A[i] + B[j]);
            }
        }
    }

2、关注常数优化

1)位运算

【场景3】计算某个玩家的属性的时候,有一步运算是除上2的幂取除数和余数

const int n = 3000000;
const int m = 30;

int doCount() {
	int s = 0, cnt = n;
	int pow2[MAXP] = { 1 };
	for (int i = 1; i < m; ++i) {
		pow2[i] = pow2[i - 1] * 2;
	}
	while (cnt --) {
		for (int i = 0; i < m; ++i) {
			s += rand() / pow2[i];
			s += (rand() % pow2[i]);
		}
	}
	return s;
}

【优化4】利用位运算进行常数优化

const int n = 3000000;
const int m = 30;

int doCount() {
	int s = 0, cnt = n;
	int pow2[MAXP] = { 1 };
	for (int i = 1; i < m; ++i) {
		pow2[i] = pow2[i - 1] * 2;
	}
	while (cnt --) {
		for (int i = 0; i < m; ++i) {
			s += rand() >> i;
			s += rand() & (pow2[i]-1);
		}
	}
	return s;
}

【思考题2】

int get(int x) {
	int c = 0;
	while (x % 2 == 0) {
		c++;
		x /= 2;
	}
	return (1 << c);
}

2)避免重复运算

【场景4】循环内部大量调用同一个函数,参数也相同

    for (i = 0; i < n; ++i) {
        for(j = 0; j < m; j++) {
            int v = Cal(MAX_COUNT);
            A[i][j] = v * i + j;
        }
    }

【优化5】改变运算顺序避免重复运算

    int v = Cal(MAX_COUNT);
    for (i = 0; i < n; ++i) {
        for(j = 0; j < m; j++) {
            A[i][j] = v * i + j;
        }
    }

【思考题3】

    for (i = 0; i < n; ++i) {
        for(j = 0; j < m; j++) {
            A[i][j] = Cal(i) + Del(j);
        }
    }

3)加法代替乘法

【优化6】如果可行尽量用加法代替乘法

    int v = Cal(MAX_COUNT);
    for (i = 0; i < n; ++i) {
        for(j = 0; j < m; j++) {
        	if(!i) {
        		A[i][j] = j;
        	}else {
            	A[i][j] = (A[i-1][j] + v);
            }
        }
    }

【思考题4】

4)再次优化取模

【场景5】需要对一批数据进行求和取模

int doSumMod() {
	int s = 0;
	for (int i = 0; i < n; ++i) {
		s = (s + val[i]) % MOD;
	}
	return s;
}

【优化7】不必要时不进行取模运算

int doSumMod() {
	int s = 0;
	for (int i = 0; i < n; ++i) {
		s += val[i];
		if (s >= MOD) s %= MOD;
	}
	return s;
}

【思考题5】

	ans = -520 % 1314;  //???

5)尽量少用 #define

【场景6】求两个数字中的大者

	#define MAX(a,b) ((a)>(b)?(a):(b))
	MAX(for(int i = 0; i <100000; ++i) s += i, for(int i = 0; i <100000; ++i) s += i);

【优化8】宁以 const 或者 函数代替 #define

int Max(int a, int b){
	return a > b ? a : b;
}
template <class T>
T Max(T a, T b){
	return a > b ? a : b;
}

【思考题6】

	#define ABS(x) x<0?-x:x

6)循环终止条件

【场景7】循环的终止条件是一个表达式的时候

int For() {
	int s = 0;
	for (int i = 0; i*i < n; ++i) {
		for (int j = 0; j*j < n; ++j) {
			for (int k = 0; k*k < n; ++k) {
				// TODO
			}
		}
	}
	return s;
}

【优化9】尽量最大限度的简化循环终止条件的逻辑运算

int For2() {
	int s = 0;
	int v = sqrt(n + 1e-8);
	for (int i = 0; i < v; ++i) {
		for (int j = 0; j < v; ++j) {
			for (int k = 0; k < v; ++k) {
				// TODO
			}
		}
	}
	return s;
}

【思考题7】

	for(i = 0; i < vec.size(); i++) {
		// TODO
	}

3、分而治之

1)二分查找

【场景8】给定一个数,在一个有序数组中查找比它大的最小的数,不存在输出 -1

【优化10】利用二分查找优化有序数组的查找效率

#define MAXA 10
int bin[MAXA] = { 1, 2, 4, 6, 8, 9, 11, 88, 520, 1314 };
int BinarySearch(int val) {
	int l = 0, r = MAXA - 1;
	int m, ans = -1;
	while (l <= r) {
		m = (l + r) >> 1;
		if (bin[m] > val) {
			ans = bin[m];
			r = m - 1;
		}
		else {
			l = m + 1;
		}
	}
	return ans;
}

【思考题8】

2)二分快速幂

【场景9】计算 a b m o d    c ( a , b < 2 64 ) a^b \mod c(a,b<2^{64}) abmodc(a,b<264) ,一般用在加解密算法(例如 RSA )中

【优化11】利用递归二分的性质将线性时间复杂度转换成对数时间复杂度

4、了解 STL 的真实效率

1)vector 的插入

const int MAXDC = 100;
void DataCollect() {
	vector <int> v;
	for (int i = 0; i < MAXDC; ++i) {
		v.push_back(i);
	}
}

【优化12】对 vector 进行内存预分配

const int MAXDC = 100;
void DataCollect() {
	vector <int> v;
	v.reserve(MAXDC);
	for (int i = 0; i < MAXDC; ++i) {
		v.push_back(i);
	}
}

【优化13】小数组尽量不用 vector

const int MAXDC = 100;
void DataCollect() {
	int stk[MAXDC], top = 0;
	for (int i = 0; i < MAXDC; ++i) {
		stk[top++] = i;
	}
}

2)queue 的插入和取出

【思考题9】

	string s;
	for(i = 0; i < n; ++i) {
		s += "K";
	}

5、了解底层调用

1)慎用三角函数

const int MAXPI = 100000000;
double CalcCircle() {
	double s = 0;
	for (int i = 0; i < MAXPI; ++i) {
		s += acos(-1.0) * i * i;
	}
	return s;
}

【优化14】精度允许的情况尽量用常量代替三角函数

const int MAXPI = 100000000;
double CalcCircle() {
	double s = 0;
	const double PI = 3.1415927;
	for (int i = 0; i < MAXPI; ++i) {
		s += PI * i * i;
	}
	return s;
}

【思考题10】

2)类型转换

【场景10】静态类型转换 和 动态类型转换 的时耗性

const int MAXT = 100000000;
void StaticCast_Test() {
	Inher *p = new Inher();
	for (int i = 0; i < MAXT; ++i){
		Base *pkBase = static_cast<Base*>(p);
	}
}
void Dynamic_Test() {
	Inher *p = new Inher();
	for (int i = 0; i < MAXT; ++i){
		Base *pkBase = dynamic_cast<Base*>(p);
	}
}

6、记忆化

1)记忆化搜索

【场景11】斐波那契数列的计算

	long long fib(unsigned int n) {
		if(n <= 1) {
			return n;
		}
		return fib(n-1) + fib(n-2);
	}
n n-1 n-2 n-2 n-3 n-3 n-4 n-3 n-4 n-4 n-5 n-4 n-5 n-5 n-6

【优化15】记忆化搜索将指数级时间复杂度转变为多项式级时间复杂度

	void init() {
		memset(f, -1, sizeof(f));
	}
	
	long long fib(unsigned int n) {
		if(n <= 1) {
			return n;
		}
		if(f[n] != -1) {
			return f[n];
		}
		return f[n] = fib(n-1) + fib(n-2);
	}

2)预处理

	f[0] = 0, f[1] = 1;
	for(int i = 2; i < MAXN; ++i) {
		f[i]  = f[i-1] + f[i-2];
	}

3)前缀和

【场景12】计算斐波那契数列的第L项到第R项的和(L<=R)

	f[0] = 0, f[1] = 1;
	for(int i = 2; i <= R; ++i) {
		f[i]  = f[i-1] + f[i-2];
	}
	s = 0;
	for(int i = L; i <= R; ++i) {
		s += f[i];
	}

【优化17】预处理数组前缀和可以做到询问时O(1)

	sum[0] = 0;
	for(int i = 1; i < MAXN; ++i) {
		sum[i]  = sum[i-1] + f[i];
	}
	NumberType getFib(NumberType L, NumberType R) {
		return sum[R] - sum[L-1];
	}

【思考题11】

四、优化总结回顾

【优化1】利用哈希数组的索引来代替数组的遍历
【优化2】利用 S T L STL STL 的 m a p map map 来代替数组的遍历
【优化3】利用 S T L STL STL 的 u n o r d e r e d _ m a p unordered\_map unordered_map 来代替数组的遍历
【优化4】利用位运算进行常数优化
【优化5】改变运算顺序避免重复运算
【优化6】如果可行尽量用加法代替乘法
【优化7】不必要时不进行取模运算
【优化8】宁以 c o n s t const const 或者 函数代替 # d e f i n e \#define #define
【优化9】尽量最大限度的简化循环终止条件的逻辑运算
【优化10】利用二分查找优化有序数组的查找效率
【优化11】利用递归二分的性质将线性时间复杂度转换成对数时间复杂度
【优化12】对 vector 进行内存预分配
【优化13】小数组尽量不用 v e c t o r vector vector
【优化14】精度允许的情况尽量用常量代替三角函数
【优化15】记忆化搜索将指数级时间复杂度转变为多项式级时间复杂度
【优化16】预处理能够处理所有可预见范围内的计算
【优化17】预处理数组前缀和可以做到询问时 O ( 1 ) O(1) O(1)

五、思考题答案

字数太多描述不下了,答案见下期吧

标签:map,int,骨灰级,复杂度,C++,++,100000,数组,优化
来源: https://blog.51cto.com/u_15239535/2836975