1. 对于计算幂2n2^n2n的算法优化
暴力算法时间复杂度O(n)O(n)O(n)
__int64 power2BF_I(int n) //幂函数2^n算法(蛮力迭代版),n >= 0{ __int64 pow = 1; //O(1):累积器刜始化为2^0while (0 < n --) //O(n):迭代n轮,每轮都pow <<= 1; //O(1):将累积器翻倍return pow; //O(1):返回累积器
} //O(n) = O(2^r),r为输入指数n的比特位数
递归优化算法时间复杂度O(logn)O(logn)O(logn)
//优化算法时间复杂度O(logn)inline __int64 sqr(__int64 a)
{return a * a;
}
__int64 power2(int n) //幂函数2^n算法(优化递归版),n >= 0
{ if (0 == n) return 1; //递归基return (n & 1) ? sqr(power2(n >> 1)) << 1 : sqr(power2(n >> 1)); //视n的奇偶分别递归
} //O(logn) = O(r),r为输入指数n的比特位数
循环优化算法时间复杂度O(logn)O(logn)O(logn)
int quickpower(int a,int b)
{ans = 1,base = a;while (b > 0){if (b & 1)ans *= base; //记录当b为奇数时少乘的一个abase *= base; b >>= 1;}return ans;
}
2. 对于Fibonacci数列的算法优化
简单递归算法时间复杂度O(2n)O(2^n)O(2n),空间复杂度O(n)O(n)O(n)
__int64 fib(int n)
{return(2 > n) ? (__int64)n : fib(n - 1) + fib(n - 2);
}
线性递归版算法时间复杂度O(n)O(n)O(n),空间复杂度O(n)O(n)O(n)
__int64 fib(int n, __int64& prev)
{if (0 == n){prev = 1;return 0;}else{__int64 prePrev;prev = fib(n - 1, prePrev);return prePrev + prev;}
}
基于动态规划优化时间复杂度O(n)O(n)O(n),空间复杂度O(1)O(1)O(1)
}*/
__int64 fib(int n)
{__int64 f = 0, g = 1;while (0 < n--){g += f; //第k+1项f = g - f; //第k项}return f;
}
3. 对于二分法查找算法的进一步思考
<1>.A版本的二分法查找
int BinarySearch_A(int arr[], int num, int low, int high)
{while (low < high){int mid = (low + high) >> 1;//右移运算符 相当于除以2if (num < arr[mid])high = mid;else if (num > arr[mid])low = mid;elsereturn mid; //成功查找提前终止}return -1; //查找失败
}
缺点:①有三个分支(if elseif else)每个分支的比较运算占用一定的时间可以优化
②有多个命中元素时,不能保证返回秩最大者(即序号最大)
③失败时,简单地返回-1,而不能指示失败位置
<2>.B版本的二分法查找——从三分支到两分支
int BinarySearch_B(int arr[], int num, int low, int high)
{while (1 < high - low)//每次迭代仅需要一次比较判断,有两个分支;成功查找不能提前终止{int mid = (low + high) >> 1;(num < arr[mid]) ? high = mid : low = mid;//经比较后确定深入[low,mid)或[mid,high)}return(num == arr[low]) ? low : -1;
}
优点:相比于A版本的二分法由三分支到两分支,优化了时间
缺点:①有多个命中元素时,不能保证返回秩最大者(即序号最大)②失败时,简单地返回-1,而不能指示失败位置 ③成功查找不能提前终止
<3>.C版本的二分法查找——解决A版本的所有缺点
int BinarySearch_C(int arr[], int num, int low, int high)
{while (low < high){int mid = (low + high) >> 1;(num < arr[mid]) ? high = mid : low = mid + 1; //比较后深入[low,mid)或(mid,high)}return --low;//返回low-1的目的对于[0,low)内的数不大于num而low可能大于num但是low-1一定为num(如果能够查找成功)而且保证返回秩最大者(即序号最大)
}
优点:①三分支到两分支优化了时间②有多个命中元素时,保证返回秩最大者(即序号最大)③查找失败时,能够返回失败位置
缺点:成功查找不能提前终止
对二分法进一步的优化可能引出了一个问题——我们为什么要求有多个命中元素时,保证返回秩最大者(即序号最大)?这到底能带给我们什么好处?
以有序向量的插入操作为例,若通过查找操作不仅能够确定可行的插入位置,而且能够在同时存在多个可行位置时保证返回其中的秩最大者,则不仅可以尽可能低减少需移动的后继元素,(插入元素的原理:①先保证数组空间不能够overflow②将插入位置的后继元素向后移动一个一个赋值③将元素插入)更可保证重复的元素按其插入的相对次序排列。对于向量的插入排序等算法的稳定性而言,这一性质更是至关重要。
4. Stack中的逆波兰表达式(RPN)
<1> RPN特点:既不必在事先做任何约定,更无需借助括号强制改变优先级
<2> 如何计算RPN?
简单总结一下:将所有操作数一一压入栈中,如果遇到操作符xxx则就从栈中弹出运算符xxx所需数目的操作数,然后实时对xxx操作符的运算,最终将新结果重新压栈(!!!阶乘运算符需要一个操作数,其他+−×÷+-×÷+−×÷运算符一般需要两个操作数)
<3> 如何手工将中缀表达式(infix)转化为RPN表达式亦称作后缀表达式(postfix)?
①用括号显示地表示优先级 ②将运算符移到对应右括号后(表示该运算符优先级的括号)③抹去所有括号 ④稍加整理
2020/2/20学习清华大学邓俊辉老师的《数据结构C++语言版》笔记,方便复习也供大家参考,如果有错误麻烦在评论指出,互相学习进步!本文代码全部来源于《数据结构C++语言版》