从零开始的数据结构
本期开始正式步入数据结构
本篇文章是学习视频中的笔记
参考视频 浙江大学数据结构(陈越、何钦铭)
基本概念
什么是数据结构
这个问题没有一个标准的定义与答案,数据结构究竟是什么,还要在学习中自己探索与感悟。下列是一些搜集到的定义供参考
“数据结构是数据对象以及存在于该对象的实例和组成实力的数据元素之间的各种联系。这些联系可以通过定义相关的函数来给出。”
——《数据结构、算法与应用》
“数据结构是ADT(抽象数据类型Abstract Date Type)的物理实现。”
——《数据结构与算法分析》
”数据结构(data structure)是计算机中存储、组织数据的方式。通常情况下,精心选择的数据结构可以带来最优效率的算法。“
——《中文维基百科》
抽象数据类型(Abstract Date Type)
数据类型
数据对象集
数据集合相关联的操作集
抽象:描述数据类型的方法不依赖于具体实现
与存放数据的机器无关
与数据存储的物理结构无关
与实现操作的算法和编程语言均无关
只描述数据对象集和相关操作集”是什么“,并不涉及”如何做到“的问题
什么是算法
算法(Algorithm)包含下列要素
1、一个有限指令集
2、接受一些输入(有些情况下不需要输入)
3、产生输出(必然)
4、在有限步骤后终止
5、每一条指令必须
1> 有充分明确的目标,不可以有歧义
2>计算机能处理的范围之内
3>描述不依赖于任何一种计算机语言以及具体的实现手段
选择排序算法的伪代码描述
void SelectionSort(int List[],int N)
{/* 将N个整数List[0]...List[N-1]进行非递减排序 */
for(i = 0;i<N;i++){
从List[i]到List[N-1]中找最小元,并将其位置赋给MinPosition;
将未排序部分的最小元换到有序部分的最后位置;
}
}
事实上这个伪代码描述的不是很清楚,比如说哪是”有序部分“?哪是”最后位置“?
我们可以用下述更清楚的伪代码
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[MinPostion]);
/* 将未排序部分的最小元换到有序部分的最后位置 */
}
}
上面这段伪代码描述一个很重要的特点,抽象,在描述算法时我们不会关心具体实现的细节
List到底是数组还是链表(虽然看上去很像数组)?
Swap用函数还是用宏去实现?
什么是好的算法
为了衡量算法的好坏,引出下面的两个指标
空间复杂度S(n)
根据算法写成的程序在执行时占用存储单元的长度。这个长度往往与输入数据的规模有关。空间复杂度过高的算法可能导致使用的内存超限,造成程序非正常中断
时间复杂度T(n)
根据算法写成的程序在执行时耗费时间的长度。这个长度也往往与输入数据的规模有关。时间复杂度度过高的低效算法可能导致我们在有生之年都等不到运行结果。
为什么会写(n),是因为这两个指标都与我们要处理的数据的规模,即n的大小直接相关。
写一个函数,能实现传入N的参数后,打印出从1到N的全部正整数,两种方法
递归函数
void PrintN(int N)
{
if(N){
PrintN(N-1);
print("%d\n",N);
}
return;
}
如果输入的N为100000
S(N) = C*N
我们会发现它在内存里面占用的空间的数量实际上与原始的N的大小成正比
当N非常大的时候,用掉了所有有限的空间,会非正常退出
循环函数
void PrintN(int N)
{
int i;
for(i=1;i<=N;i++){
printf("%d\n",i);
}
return;
}
只用了一个临时变量和一个for循环,没有涉及到程序调用,所以不管N有多大,占用的空间始终是一个固定的
不会随着N的增长而增长。
在机器中,运算乘除法比加减法要耗时多。
例如
double f(int n, double a[],double x)
{
int i;
double p = a[0];
for(i=1;i<=n;i++) //循环n次,即有n次运算
p+=(a[i]*pow(x,i)); //每次运算做i次乘法
//(pow(x,i)函数运算的是x的i次方)
return p;
}
乘法次数,(1+2+…+n)=(n^2+n)/2
T(n) = C1 · n^2 + C2 · n
double f(int n, double a[],double x)
{
int i;
double p = a[n];
for(i=n;i>0;i--) //循环n次,即有n次运算
p = a[i-1]+x*p; //每次运算做一次乘法
return p;
}
乘法次数,n
T(n) = C · n
当n很大的时候,n^2会远远超过n,即第二个程序一定会比第一个跑得快的多
我们平时一般分析的复杂度是最坏情况复杂度
复杂度的渐近表示法
我们不需要对算法做精细的分析,只需要知道它的一个增长趋势就可以了,于是引出复杂度的渐近表示法。
O(f(n))表示上界,我们在实际中一般取最小上界
Ω(g(n))表示下界,我们在实际中一般取最大下界
Θ(h(n))表示上界也表示下界
这里有一些复杂度分析小窍门
例题,下面一段代码的时间复杂度是?
if ( A > B ) {
for ( i=0; i<N; i++ )
for ( j=N*N; j>i; j-- )
A += B;
}
else {
for ( i=0; i<N*2; i++ )
for ( j=N*2; j>i; j-- )
A += B;
}
根据我们上述小窍门,为O(N^3)