1 数据结构
1.1 查询方法
实例:二分法查询。
1.1.1 二分法定义
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
1.1.2 查找过程
首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
1.1.3 代码
Python代码:
def bin_search(data_list, val):
low = 0 # 最小数下标
high = len(data_list) - 1 # 最大数下标
while low <= high:
mid = (low + high) // 2 # 中间数下标
if data_list[mid] == val: # 如果中间数下标等于val, 返回
return mid
elif data_list[mid] > val: # 如果val在中间数左边, 移动high下标
high = mid - 1
else: # 如果val在中间数右边, 移动low下标
low = mid + 1
return # val不存在, 返回None
ret = bin_search(list(range(1, 10)), 3)
print(ret)
C/C++代码:
int bsearchWithoutRecursion(int array[],int low,int high,int target)
{
while(low<=high)
{
int mid=low+(high-low)/2;//还是溢出问题
if(array[mid]>target)
high=mid-1;
else if(array[mid]<target)
low=mid+1;
else
return mid;
}
return-1;
}
int binSearch(const int *Array,int start,int end,int key)
{
int left,right;
int mid;
left=start;
right=end;
while(left<=right)
{
mid=left+(right-left)/2;//还是溢出问题
if(key==Array[mid]) return mid;
else if(key<Array[mid]) right=mid-1;
else if(key>Array[mid]) left=mid+1;
}
return -1;
}
1.2 空间使用
实例:实现printN函数,传入正整数N,使其顺序打印1到N全部正整数。
方法一:循环实现
void printN(int N) {
int i;
for (i = 1; i <= N; i++) {
printf("%d\n", i);
}
return;
}
方法二:递归实现
void printN(int N) {
if (N) {
printN(N - 1);
printf("%d\n", N);
}
return;
}
看上去两个代码极其相似,但在测试时,如果N=100000的时候,递归函数将无法实现,这是因为递归函数对空间内存占有率过大。当内存超出指定范围的时候,编译器将直接报错,程序无法运行下去。
所以,解决问题方法的效率,也是跟空间的利用效率有关。
1.3 算法效率
实例:计算x的多项式f(x)的值。
方法一:小白写法(逐一计算法)
double f(int n, double a[], double x) {
int i;
double p = a[0];
for (i = 1; i <= n; i++)
p += (a[i] * pow(x, i));
return p;
}
方法二:专业写法(因式分解法)
double f(int n, double a[], double x) {
int i;
double p = a[n];
for (i = n; i > 0; i--)
p = a[i - 1] + x*p;
return p;
}
方法一在时间上面远远高于方法二,故而方法二比方法一高级,我们采用模板来计算二者的时间:
# include<cstdio>
# include<ctime>
clock_t start, stop;
double duration;
int main() {
// 开始计时
start = clock();
// 待测函数
MyFunction();
// 停止计时
stop = clock();
duration = ((double)(stop - start)) / CLK_TCK;
return 0;
}
注意:如果一次时间太短无法被捕捉,则可以多次重复运行该函数,最后处以次数就可以得到运行一次函数所需要的时间。
1.4 抽象数据类型
数据类型
- 数据对象集
- 数据集合相关联操作集
抽象:描述数据类型的方法不依赖于具体表现
- 与存放数据的机器无关
- 与数据存储的物理结构无关
- 与实现操作的算法和编程语言无关
2 算法
2.1 定义
-
一个有限的指令集
-
接受一些输入
-
产生输出
-
一定在有限步骤之后终止
-
每一条指令必需:
1、有充分明确的目标,不可歧义
2、计算机能处理的范围内
3、表述不依赖于任何一种语言及实现手段
如选择排序算法的伪码描述:
void SelecttionSort(int List[], int N){
for (i = 0;i < N;i++) {
MinPosition = ScanForMin(List, i, N);
swap(List[i], List[MinPosition]);
}
}
2.2 空间复杂度S(n)
失败案例:递归函数。
void printN(int N) {
if (N) {
printN(N - 1);
printf("%d\n", N);
}
return;
}
原因:在调用printN(100000)后,执行printN(99999),再执行printN(99998),直到printN(0)执行完成后,所以函数才会return,此时空间复杂度正比于输入值N。如果用循环函数实现时,则空间占有量始终为一个常量,不随N值的变化而变化。
2.3 时间复杂度T(n)
失败案例:多项式函数计算。
double f(int n, double a[], double x) {
int i;
double p = a[0];
for (i = 1; i <= n; i++)
p += (a[i] * pow(x, i));
return p;
}
原因:时间复杂度计算时加减法可以忽略不记,主要研究乘法。上述案例中,运用了pow函数,一共运用了(n + 1)* n / 2 次乘法运算,故时间复杂度为T(n) = C1 * n^2 + C2 * n 。而如果采用因式分解的话,则使用了n次乘法运算,时间复杂度为T(n) = C * n。
2.4 最坏情况复杂度和平均复杂度
我们在计算复杂度的时候经常比较最坏情况复杂度(因为在某写算法中,平均复杂度很难计算),从而来比较算法的优劣程度。
2.5 复杂度的渐进表示法
- T(n) = O(f(n)) 表示存在常数C > 0, n0 > 0 使得当 n > n0 时有T(n) <= C * f(n)
- T(n) = G(g(n)) 表示存在常数C > 0, n0 > 0 使得当 n > n0 时有T(n) >= C * g(n)
- T(n) = K(h(n)) 表示同时有 T(n) = O(h(n)) 和 T(n) = G(h(n))
2.6 实例分析
例子:给定N个整数的序列{A1,A2,…,An},求函数f(i,j)= max{0,Ai +…+ Aj} 的最大值
2.6.1 算法一:
int MaxSubseqSum1(int A[], int N)
{
int ThisSum, MaxSum = 0;
int i, j, k;
for(i = 0; i < N; i++){
for(j = i; j < N; j++){
ThisSum = 0;
for(k = i; k <= j; k++)
ThisSum += A[k];
if(ThisSum > MaxSum)
MaxSum = ThisSum;
}
}
return MaxSum;
}
算法复杂度:T ( N ) = O ( N ^ 3 )
2.6.2 算法二:
int MaxSubseqSum1(int A[], int N)
{
int ThisSum, MaxSum = 0;
int i, j, k;
for(i = 0; i < N; i++){
ThisSum = 0;
for(j = i; j < N; j++){
ThisSum += A[j];
if(ThisSum > MaxSum)
MaxSum = ThisSum;
}
}
return MaxSum;
}
算法复杂度:T ( N ) = O ( N ^ 2 )
2.6.3 算法三:分而治之
原理:将数列对半分,分别求出两边的最大子列和,然后再计算过中点线的最大子列和,依此递推。其算法复杂度推导如下:
T ( N ) = 2 * T ( N / 2 ) + C * N , 其中: T ( 1 ) = O ( 1 )
T ( N ) = 2 ^ k * O ( 1 ) + C * k * N , 其中: N / 2 ^ k = 1
T ( N ) = O ( N * log N )
代码略。
2.6.4 算法四:在线处理
int MaxSubseqSum4(int A[], int N) {
int ThisSum, MaxSum;
int i;
ThisSum = MaxSum = 0;
for (i = 0;i < N; i++) {
ThisSum += A[i];
if (ThisSum > MaxSum)
MaxSum = ThisSum;
else if (ThisSum < 0)
ThisSum = 0;
}
return MaxSum;
}
原理说明:从右向左累加一个数列的各个元素时,记录其子列的最大值。当出现子列为负数时,之后的任意数与其相加只会更小,不会更大,所以直接抛弃,从下一个数重新开始构造新的子列。
算法复杂度:T ( N ) = O ( N )