数据结构与算法入门
基本概念
数据:描述客观事物的数值、字符各种符号的集合。
数据项:具有原子性的不可分割的最小数据单位
数据元素:数据的基本单位,数据集合的个体
数据对象:性质相同的数据元素的集合,数据的子集
数据结构:相互之间存在一种或多宗特定关系的数据元素的集合
数据的逻辑结构:数据结构的逻辑层面
数据的存储结构:数据结构的物理层面
数据结构=逻辑结构+存储结构+(在存储结构上的)运算/操作
数据结构类型
数据的逻辑结构
线性结构和非线性结构
线性结构:有且只有一个起点和重点和唯一的直接前驱和后继
案例:冰糖葫芦,排队取餐
非线性结构:多个直接前驱和后继
常见的:树(二叉树等),图(网等)
树:单位的组织架构、文件系统
图:交通线路图,地图
集合结构,线性结构,树状结构,网状结构
表和树是最常用的两种高效数据结构
集合结构:数学中的集合。确定性,唯一性,无序性。
该结构的数据之间的关系是:属于同一个集合,元素关系很弱。
线性结构:元素之间一对一的线性关系的数据结构。
树状结构:除了开头,其他都有唯一的直接前驱和多个后继元素。
特点:一对多
网状结构:每个元素都是多个前驱后继
特点:多对多
数据的存储结构
常见的:顺序存储,链式存储,索引存储,散列存储
顺序存储结构
把逻辑上相邻的节点存储在物理位置上相邻的存储单元中。
通常顺序存储结构是借助数组来描述的,是一块连续的存储空间。
优点:节省存储空间,采用这种方法时,可实现对节点的随机存取。
缺点:插入删除需要移动元素,效率低,固定空间,导致空闲浪费。
链式存储结构
不连续的存储空间,每个存储节点对应一个需要存储的数据元素。
每个节点是由数据域和指针组成。
逻辑上相邻,物理上不必相邻。
优点:插入、删除灵活,不必移动元素,不会闲置空间。
缺点:存储密度小,查找慢。
索引存储结构
除建立存储节点信息外,还建立附加的索引来表来标识节点的地址。
案例:图书、字典的目录就是索引。
散列存储结构
根据节点的关键字直接计算出该节点的存储地址HashSet HashMap
一种神器的结构,添加、查询速度无比的快。
案例:线性表 = 顺序表 + 链表
同一逻辑结构可以对应多种存储结构。
同样的运算,在不用的存储结构中,其实现的过程是不一样的。
算法:
指令的集合。为了解决特定问题而规定的一系列操作。
特性:输入、输出、可行性、有穷性、确定性
通俗的说:算法就是阶梯的过程。
案例:求0+1+2+3+...+10000=?
算法1、依次相加 ,使用while,for,do-while实现
算法2、递归实现
算法3、高斯解法:首尾相加*50,梯形或者三角形公式完成。
梯形:(1+10000)*10000/2 或 三角形:100*101/2
算法的优劣依据:复杂度(时间和空间)
时间复杂度:计算机的工作量。
空间复杂度:计算机所耗内存空间。
时间复杂度
时间频度:一个算法中的语句执行的次数,表示为T(n),n表示问题的规模。
一个算法话费的时间与算法中语句执行的次数成正比
时间复杂度:问题的规模,而不是具体的次数。就是时间频度去掉低阶项和首项常数,简称T(n) = O(f(n))
f(n)是T(n)的同数量级函数
O表示最坏情况下的时间复杂度
案例:
某两个算法的时间频度是:
T(n) = 10000n2+10n+6
T(n) = 10n2+10n+6
时间复杂度都是:
T(n) = O(n2)
因为平均时间复杂度和最坏时间复杂度大部分算法的结果都一样,所以讨论最坏时间复杂度,因为平均时间复杂度难算。
Ο(欧米可荣)符号给出了算法时间复杂度的上界(最坏情况 <=)
比如:T(n) =O(n2)
Ω(欧米伽)符号给出了时间复杂度的下界(最好情况 >=)
比如:T(n) =Ω(n2)
而Θ(西塔)给出了算法时间复杂度的精确阶(最好和最坏是同一个阶 =)
比如:T(n) =Θ(n2)
时间复杂度的计算:
找出算法中的基本语句,如:循环体
计算基本语句的执行次数的数量级;最高次幂,忽略系数。
重点分析:增长率
用大O表示算法的时间性能。将数量级放入大O
案例:
简单语句:
int count = 0;
时间复杂度为:T(n) = O(1);
10000个简单语句也是T(n) = O(1);因为是常数,不是无穷大的n
单循环语句:
int n=10;
int count=0;
for (int i=1; i<=n; i++){
count++;
}
时间复杂度为:T(n) = O(n);
(2)
int n=10;
int count=0;
for (int i=1; i<=n; i*=2){
count++;
}
结果:1 2 4 8 16 32 64 128 256...
230 = 210*210*210 = 1024*1024*1024 = 1073741824 ≈1000*1000*1000=10亿
时间复杂度为:T(n) = O(log2n);
嵌套循环
(1)
int n=8, count=0;
for (int i=1; i<=100n; i++)
for (int j=1; j<=10n; j++)
count++;
}
}
时间复杂度:O(n2)=内循环n次*外循环n次
(2)
int n=8, count=0;
for (int i=1; i<=100n; i*=2)
for (int j=1; j<=10n; j++)
count++;
}
}
时间复杂度:O(n*log2n)=内循环n次*外循环log2n次
(3)
int n=8, count=0;
for (int i=1; i<=100n; i++)
for (int j=1; j<=i; j++)
count++;
}
}
时间复杂度:O(n2)=1+2+3+4....+n=(1+n)*n/2
常用的时间复杂度:
常数阶O(1)
对数阶O(log2n)
线性阶O(n)
线性对数阶O(n*log2n)
平方阶O(n2)
立方阶O(n3)
...
k次方阶O(nk)
指数阶O(2n)
阶乘阶O(n!)
上面各种时间复杂度级别,执行效率越来越低。
差异:例:对数阶O(log2n) 和 线性阶O(n) ,当n=10的8次方时,此时是1亿次,一个是8次
空间复杂度
存储量包括:
自身
输入数据
辅助变量
输出数据所占空间只取决于问题本身,和算法无关,只需要分析除输入和程序之外的辅助变量所占的空间。
简称:S(n) = O(g(n))
(1)
int fun(int n){
int i,j,k,s;
s=0;
for (i=0;i<=n;i++)
for (j=0;j<=i;j++)
for (k=0;k<=j;k++)
s++;
空间复杂度:S(n) = O(1),就是4个变量占的空间
递归
void fun(int a[],int n,int k) {
int i;
if (k==n-1){
for (i=0;i<n;i++){}
printf("%d\n",a[i]); //执行n次
}
}else{
for (i=k;i<n;i++)
a[i]=a[i]+i*i;
fun(a,n,k+1);
}
}
空间复杂度:S(n) = O(n),调用1次开辟一个空间。