数据结构与算法
数据结构
数据对象在计算机中的组织方式,包括逻辑结构(如线性结构、树、图等)和物理存储结构。
算法
是一个有限的指令集,它可以接受一些输入(有时不需要),产生一些输出,会在有限的步骤之后终止。
算法的指令需要满足的条件
- 有明确的目标,不可有歧义
- 在计算机能处理的范围内
一般对算法中指令的描述不依赖于具体的编程语言和实现方式(抽象)。
数据结构和算法的关系
基于特定的目的,数据对象往往会被施加操作,而完成这些操作的方法就是算法。
抽象数据类型
数据类型:包括数据对象集及其相关联的操作集。
抽象:描述数据类型的方法不依赖于具体实现,与存储机器、物理结构、实现算法、编程语言等无关。
抽象数据类型只关心是什么,不关心如何做到,以下图为例:
算法的效率
算法效率的影响因素
- 数据量
- 数据组织方式(eg. 摆书的方式)
- 空间利用方式(eg. 循环 vs 递归)
- 算法巧妙程度
算法效率的衡量标准
- 空间复杂度 :输入数据量为n时,程序执行占用存储单元的长度
- 时间复杂度 :输入数据量为n时,程序执行耗费时间的长度。
以 为例,衡量算法效率时一般考虑最坏情况复杂度 和平均复杂度 ,一般以 表示(渐进表示法)。
定义:存在常数 , ,使得当 时有 。
分析复杂度的若干技巧
- 若两段算法分别具有时间复杂度 和 ,则它们拼接和嵌套后时间复杂度 分别如下:
- for循环的时间复杂度 循环次数 循环体的复杂度
- if-else结构的时间复杂度 if条件判断部分的复杂度 各分支部分的复杂度
程序执行耗时的实测方法
#include <stdio.h>
#include <time.h> /* 包含计时所需的函数 */
/* clock()函数可以返回程序开始运行到该函数被调用时所耗费的时间,
* 单位为clock tick */
clock_t start, stop; /* clock_t为clock()函数返回的变量类型 */
double duration; /* duration记录被测函数运行时间,以秒为单位 */
int main() {
/* 此处写测试范围之前的步骤 */
start = clock(); /* 记录开始时刻(clock tick) */
MyFunction(); /* 被测函数 */
stop = clock(); /* 记录停止时刻(clock tick) */
duration = ((double)(stop - start)) / CLK_TCK;
/* 将被测函数运行经过的clock tick转化为秒 */
/* 常数CLK_TCK表示每秒钟走过的clock tick数 */
/* 此处写测试范围之后的步骤 */
return 0;
}
如要比较两个耗时很短的被测函数,需要通过反复调用被测函数(循环)来取得充分的打点数(最后将总时间除以调用的次数),否则可能因为打点数过小,两者运行时间不准确(eg. 两个函数运行一次均不足一个clock tick,则运行时间均被记录为0)。
本节要求掌握的算法
最大子列和问题
在线处理
int MaxSubseqSum1(int List[], int N) {
int i;
int thisSum = 0, maxSum = 0;
for (i = 0; i < N; i++) {
thisSum += List[i]; /* thisSum向右累加 */
if (thisSum > maxSum)
maxSum = thisSum; /* 发现更大的子列和就更新maxSum */
else if (thisSum < 0)
thisSum = 0; /* 如果thisSum已经小于0,则抛弃前面的部分重新开始累加 */
}
return maxSum;
}
分治法
/* 返回三个数中的最大值 */
int Max3(int a, int b, int c) { return a > b ? a > c ? a : c : b > c ? b : c; }
/* 分治法求最大子列和 */
int DivideAndConquer(int List[], int left, int right) {
/* 拆分成三个小问题:左右的以及横跨两边的最小子列和,最后返回三者中最大的 */
int maxLeftSum = 0, maxRightSum = 0;
int maxLeftBorderSum = 0, maxRightBorderSum = 0;
int center = (left + right) / 2;
int i;
/* 递归终止条件 */
if (left == right) {
if (List[left] < 0)
return 0;
else
return List[left];
}
/* 左右两边的最小子列和 */
maxLeftSum = DivideAndConquer(List, left, center);
maxRightSum = DivideAndConquer(List, center + 1, right);
/* 横跨两边的最小子列和 */
int thisLeftBorderSum = 0, thisRightBorderSum = 0;
/* 扫描左半边 */
for (i = center; i >= left; i--) {
thisLeftBorderSum += List[i];
if (thisLeftBorderSum > maxLeftBorderSum)
maxLeftBorderSum = thisLeftBorderSum;
}
/* 扫描右半边 */
for (i = center + 1; i <= right; i++) {
thisRightBorderSum += List[i];
if (thisRightBorderSum > maxRightBorderSum)
maxRightBorderSum = thisRightBorderSum;
}
/* 返回三种情况中最大的子列和 */
return Max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);
}
/* 将函数接口与上一种方法保持一致 */
int MaxSubseqSum2(int List[], int N) {
return DivideAndConquer(List, 0, N - 1);
}
二分查找
int BinarySearch(int List[], int N, int X) { /* N为列表长度,X为待查找的值 */
int left = 0, right = N - 1;
int mid = left + ((right - left) / 2);
/* 等同于 (left + right) / 2,防止溢出 */
while (left <= right) {
if (X == List[mid])
return mid; /* 如果找到,返回X在列表中的位置 */
else if (X > List[mid])
left = mid + 1;
else
right = mid - 1;
mid = left + ((right - left) / 2);
}
/* 如果没找到返回-1 */
return -1;
}
多项式求和
double PolynominalSum(double a[], int n, double x) {
/* a[]按指数顺序存放系数,从0开始,n表示最大的指数,x表示底数 */
double p = 0;
int i;
for (i = n; i >= 0; i--) p = p * x + a[i];
return p;
}