数据结构
定义
- 数据结构是数据对象,以及存在于该对象的实例和组成实例的数据元素之间的各种联系。这些联系可以通过定义相关的函数来给出。
- 或者 数据结构是ADT(抽象数据类型Abstract Data Type)的物理实现。
- 定义未统一…
说明
- 数据结构是数据对象在计算机中的组织方式包括(逻辑结构,物理存储结构);
- 数据对象必定与一系列加在其上的操作相关联;
- 完成这些操作所用的方法就是算法。
抽象数据类型(Abstract Data Type)
- 数据类型
- 数据对象集
- 数据集合相关联的操作集
- 抽象:描述数据类型的方法不依赖于具体实现
- 与存放数据的机器无关
- 与数据存储的物理结构无关
- 与实现操作的算法和编程语言都无关
方法的效率
1. 解决问题方法的效率,跟数据的组织方式有关
- 例:如何在书架上摆放图书例一:如何在书架上摆放图书
2. 解决问题方法的效率,跟空间的利用效率有关
- 例: 写程序实现一个函数PrintN,使得传入一个正整数N,打印出1-N之间所有的正整数。写程序实现一个函数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;
}
//打印输出
#include <stdio.h>
void PrintN (int N);
int main ()
{
int N;
scanf("%d", &N);
PrintN(N);
return 0;
}
3. 解决问题方法的效率,跟算法的巧妙程度有关系
- 例:写程序计算给定多项式在给定点x处的值
f ( x ) = a 0 + a 1 x + ⋅ ⋅ ⋅ + a n − 1 x n − 1 + a n x n f(x) = a_0 + a_1x + ··· + a_{n-1}x^{n-1} + a_nx^n f(x)=a0+a1x+⋅⋅⋅+an−1xn−1+anxn
补充:比较函数执行时间
clock(): 捕捉从程序开始运行到clock() 被调用时所耗费的时间。这个时间单位是clock tick。常数CLK_TCK:机器时钟每秒钟所走的时钟打点数(每台机器可能不一样)。
模版:
#include <stdio.h>
#include <time.h>
clock_t start, stop;
/*clock_t是clock()函数返回的变量类型*/
double duration;
int main()
{
/*准备工作*/
start = clock();
MyFunction();
stop = clock();
duration = ((double) (stop - start))/CLK_TCK;
/*后续处理*/
return 0;
- 代码函数实现
double f1 (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 f2 (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;
}
f ( x ) = ∑ i = 0 n i ⋅ x i f(x) = \sum_{i=0}^{n}{i·x^i} f(x)=∑i=0ni⋅xi , x = 1.1;
- 函数调用
#include <stdio.h>
#include <time.h>
#include <math.h>
clock_t start, stop;
double duration;
#define MAXN 10 //多项式的最大项
#define MAXK 1e7 //被测函数最大重复调用次数
double f1(int n, double a[], double x);
double f2(int n, double a[], double x);
int main()
{
int i;
double a[MAXN];
for i (= 0; i < MAXN; i++) a[i] = (double)i;
start = clock();
for ($j = 0; $j < MAXK; $j++)
f1(MAXN - 1, a, 1.1);
stop = clock();
duration = ((double)(stop - start))/CLK_TCK;
printf("ticks1 = %n\n", (double)(stop - start));
printf("duration1 = %6.2e\n", duration);
start = clock();
f2(MAXN - 1, a, 1.1);
stop = clock();
duration = ((double)(stop - start))/CLK_TCK;
printf("ticks2 = %n\n", (double)(stop - start));
printf("duration2 = %6.2e\n", duration);
return 0;
}
“矩阵“抽象数据类型定义
算法(Algorithm)
定义
- 一个有限指令集
- 接收一些输入(有些情况下不需要输入)
- 产生输出
- 一定在有限步骤之后终止
- 每一条指令必须
- 有充分明确的目标,不可以有歧义
- 计算机能处理的范围之内
- 描述应不依赖于任何一种计算机语言以及具体的实现手段
举例说明
- 选择排序算法的伪码描述
void SelectionSort (int List[], int N)
{
/*将N个整数List[0]...List[N-1]进行非递减排序 */
for ( i = 0; i < N; i++) {
MinPosition = ScanForMin(List, i, N-1);
/* 从List[i]到List[N-1]中找到最小元,并将其位置赋给MinPosition */
Swap(List[i], List[MinPosition]);
/* 将未排序部分的最小元换到有序部分的最后位置 */
}
}
抽象:并不关系List到底是数组还是链表,也不关心Swap是用函数还是用宏去实现。
评价算法的指标
- 空间复杂度S(n) ---- 根据算法写成的程序在执行时占用的存储单元的长度。这个长度往往与输入数据的规模有关。空间复杂度过高的算法可能导致使用的内存超限,造成程序非正常中断。
- 时间复杂度T(n) ---- 根据算法写成的程序在执行时耗费时间的长度。这个长度往往也与输入数据的规模有关。时间复杂度过高的低效算法可能导致我们在有生之年都等不到运行结果。
- 递归调用
C为一次程序执行占用的空间。 - 时间复杂度计算
分析算法时关注的复杂度
- 最坏情况复杂度 T w o r s e ( n ) T_{worse}(n) Tworse(n) 一般关心这个
- 平均复杂度
T
a
v
g
(
n
)
T_{avg}(n)
Tavg(n)
T a v g ( n ) ≤ T w o r s e ( n ) T_{avg}(n) \leq T_{worse}(n) Tavg(n)≤Tworse(n)
复杂度的渐进表示法
分析时间复杂度
- 若两段算法分别有复杂度
T
1
(
n
)
=
O
(
f
1
(
n
)
)
T_1(n) = O(f_1(n))
T1(n)=O(f1(n))和
T
2
(
n
)
=
O
(
f
2
(
n
)
)
T_2(n) = O(f_2(n))
T2(n)=O(f2(n)),则
- T 1 ( n ) + T 2 ( n ) = m a x ( O ( f 1 ( n ) ) , O ( f 2 ( n ) ) ) T_1(n) + T_2(n) = max( O(f_1(n)), O(f_2(n)) ) T1(n)+T2(n)=max(O(f1(n)),O(f2(n)))
- T 1 ( n ) ∗ T 2 ( n ) = O ( f 1 ( n ) ∗ f 2 ( n ) ) T_1(n) * T_2(n) = O( f_1(n) * f_2(n)) T1(n)∗T2(n)=O(f1(n)∗f2(n))
- 若T(n) 是关于K阶多项式,那么 T ( n ) = Θ ( n k ) T(n) = \Theta(n^k) T(n)=Θ(nk)
- 一个
for
循环的时间复杂度等于循环次数乘以循环体代码的复杂度 if-else
结构的复杂度取决于if
的条件潘敦复杂度和两个分支部分的复杂度,总体复杂度取三者中的最大
应用实例
最大子列和问题
说明:
- 给定N个整数的序列${A_1, $A_2, …, A n } A_n\} An},求函数 f ( i , j ) = m a x { 0 , ∑ k = i j A k } f(i,j) = max\{0, \displaystyle\sum_{k=i}^{j}A_k\} f(i,j)=max{0,k=i∑jAk}的最大值。数据
解法:
- 算法一: 复杂度 T ( N ) = O ( N 3 ) T( N ) = O( N^3 ) T(N)=O(N3)
int MaxSubseqSum1( int A[], int N )
{ int ThisSum, MaxSum = 0;
int i, j, k;
for( i = 0; i < N; i++ ) { /* i是子列左端位置 */
for( j = i; j < N; j++ ) { /* j是子列右端位置 */
ThisSum = 0; /* ThisSum是从A[i]到A[j]的子列和 */
for( k = i; k <= j; k++ )
ThisSum += A[k];
if( ThisSum > MaxSum ) /* 如果刚得到的这个子列和更大 */
MaxSum = ThisSum; /* 则更新结果 */
} /* j循环结束 */
} /* i循环结束 */ return MaxSum;
return MaxSum;
}
- 算法二:复杂度 T ( N ) = O ( N 2 ) T( N ) = O( N^2 ) T(N)=O(N2)
int MaxSubseqSum2( int A[], int N )
{ int ThisSum, MaxSum = 0;
int i, j;
for( i = 0; i < N; i++ ) { /* i是子列左端位置 */
ThisSum = 0; /* ThisSum是从A[i]到A[j]的子列和 */
for( j = i; j < N; j++ ) { /* j是子列右端位置 */
ThisSum += A[j];
/*对于相同的i,不同的j,只要在j-1次循环的基础上累加1项即可*/
if( ThisSum > MaxSum ) /* 如果刚得到的这个子列和更大 */
MaxSum = ThisSum; /* 则更新结果 */
} /* j循环结束 */
} /* i循环结束 */
return MaxSum;
}
- 算法三:分而治之 复杂度
T
(
N
)
=
O
(
N
l
o
g
N
)
T(N) = O(NlogN)
T(N)=O(NlogN)
- 算法四:在线处理 T ( N ) = O ( N ) T(N) = O(N) T(N)=O(N)
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;
}
- ”在线“的意思是每输入一个数据就进行即时处理,在任何一个地方终止输入,算法都能正确给出当前的解。