【数据结构】1.数据结构及算法的入门
数据结构的介绍
1.数据的分类:
1.数据项:数据的最小单位,具有原子性
2.数据元素:数据的基本单位,由若干个数据项组成,计算机通常将其当成一个整体处理
3.数据对象:是由性质相同的数据源的集合,是数据的子集
4.数据:描述客观事物的数值、字符以及能输入机器且能被处理的各种符号集合
2.数据结构:是带有结构特性的数据元素的集合,它研究的是数据的逻辑结构和数据的物理结构以及它们之间的相互关系,
并对这种结构定义相适应的运算,设计出相应的算法,并确保经过这些运算以后所得到的新结构仍保持原来的结构类型。
数据结构=逻辑结构+物理结构+操作
逻辑结构:线性结构(线性表,栈队列,串和数组),非线性结构(集合,树,图)
物理结构:顺序存储,链式存储,索引存储,散列存储
操作:检索,排序,插入,删除,修改等
3.逻辑结构和物理结构
1.逻辑结构:数据元素之间的逻辑关系,与实现无关
1.集合结构:数学集合,具有:确定性,唯一性,无序性
2.线性结构:有且只有一个开始结点和一个终端结点,并且每个结点最多只有一个前驱结点,最多只有一个后继结点
3.非线性结构:一个结点元素可能对应多个直接前驱和多个直接后继
线性结构是一对一的关系,集合无关系,树为一对多的关系,图为多对多的关系
主要为线性结构和树结构
2.物理结构(存储结构):数据的存储结构主要包括数据元素本身的存储以及数据元素之间关系表示,是数据的逻辑结构在计算机中的表示。
1.顺序存储:把逻辑上相邻的节点存储在物理位置上相邻的存储单元中,结点之间的逻辑关系由存储单元的邻接关系来体现。
由此得到的存储结构为顺序存储结构,通常顺序存储结构是借助于计算机程序设计语言(例如C/C++)的数组来描述的。
(数据元素的存储对应于一块连续的存储空间,数据元素之间的前驱和后续关系通过数据元素,在存储器中的相对位置来反映)
优点:
1、节省存储空间,因为分配给数据的存储单元全用存放结点的数据(不考虑c/c++语言中数组需指定大小的情况),结点之间的逻辑关系没有占用额外的存储空间。
2、采用这种方法时,可实现对结点的随机存取,即每一个结点对应一个序号,由该序号可以直接计算出来结点的存储地址。
缺点:
1、插入和删除操作需要移动元素,效率较低。
2、必须提前分配固定数量的空间,如果存储元素少,可能导致空闲浪费。
2.链式存储:数据元素的存储对应的是不连续的存储空间,每个存储节点对应一个需要存储的数据元素。每个结点是由数据域和指针域组成。
元素之间的逻辑关系通过存储节点之间的链接关系反映出来。逻辑上相邻的节点物理上不必相邻。
缺点:
1、比顺序存储结构的存储密度小 (每个节点都由数据域和指针域组成,所以相同空间内假设全存满的话顺序比链式存储更多)。
2、查找结点时链式存储要比顺序存储慢。
优点:
1、插入、删除灵活 (不必移动节点,只要改变节点中的指针)。
2、有元素才会分配结点空间,不会有闲置的结点。
3.索引存储:除建立存储结点信息外,还建立附加的索引表来标识结点的地址
4.散列存储:根据结点的关键字直接计算出该结点的存储地址 HashSet HashMap
注:同一逻辑结构可以对应多种存储结构
同样的运算,在不同的存储结构中,其实现过程是不同的
算法的介绍
算法:算法是指令的集合,是为了解决特定问题而规定的一系列操作。它是明确定义的可计算过程,以一个数据集合作为输入,并产生一个数据集合作为输出。
特性:输入性,输出性,可行性,有穷性,确定性。
复杂度:评价算法的优劣依据(时间复杂度和空间复杂度)
1.时间复杂度:执行算法所需要的计算工作量
一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。
一个算法中的语句执行次数称为语句频度或时间频度,表示为T(n),n表示问题的规模
但有时我们想知道它变化时呈现什么规律,想知道问题的规模,而不是具体的次数,此时引入时间复杂度。
一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,
若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。
记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。
或者说:时间复杂度就是时间频度去掉低阶项和首项常数。
注:时间频度与时间复杂度是不同的,时间频度不同但时间复杂度可能相同。
例:某两个算法的时间频度是 T(n)= 100000n^2+10n+6 T(n) = 10n^2+10n+6 T(n) = n^2 但是时间复杂度都是 T(n) = O(n2)
最坏时间复杂度和平均时间复杂度
1.最坏情况下的时间复杂度称最坏时间复杂度。一般不特别说明,讨论的时间复杂度均是最坏情况下的时间复杂度。
这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的上界,这就保证了算法的运行时间不会比任何更长。
在最坏情况下的时间复杂度为T(n)=O(n),它表示对于任何输入实例,该算法的运行时间不可能大于O(n)。
2.平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,算法的期望运行时间。
第一,难计算
第二,有很多算法的平均情况和最差情况的复杂度是一样的。
时间复杂度的计算
根本没有必要计算时间频度,即使计算处理还要忽略常量、低次幂和最高次幂的系数,所以可以采用如下简单方法:
⑴ 找出算法中的基本语句; 算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体。
⑵ 计算基本语句的执行次数的数量级;只需计算基本语句执行次数的数量级,这就意味着只要保证基本语句执行次数的函数中的最高次幂正确即可,可以忽略所有低次幂和最高次幂的系数。
这样能够简化算法分析,并且使注意力集中在最重要的一点上:增长率。
⑶ 用大Ο记号表示算法的时间性能。 将基本语句执行次数的数量级放入大Ο记号中。
常用时间复杂度的数量级:常数阶O(1) ,对数阶O(log2n),线性阶O(n),线性对数阶O(n*log2n),平方阶O(n2) ,立方阶O(n3)......
例:
时间复杂度为O(1)。
int count=0;
时间复杂度为O(log2 n)
int n=8, count=0;
for (int i=1; i<=n; i*=2)
count++;
时间复杂度为O(n)。
int n=8, count=0;
for (int i=1; i<=n; i++)
count++;
时间复杂度为O(n2)
int n=8, count=0;
for (int i=1; i<=100n; i++)
for (int j=1; j<=10n; j++)
count++;
时间复杂度为O(nlog2n)
int n=8, count=0;
for (int i=1; i<=n; i*=2)
for (int j=1; j<=n; j++)
count++;
时间复杂度为O(n2)
int n=8, count=0;
for (int i=1; i<=n; i++)
for (int j=1; j<=i; j++)
count++;
2.空间复杂度:执行这个算法所需要的内存空间
算法的存储量包括:
1.程序本身所占空间
2.输入数据所占空间
3.辅助变量所占空间
输入数据所占空间只取决于问题本身,和算法无关,则只需要分析除输入和程序之外的辅助变量所占额外空间。
空间复杂度是对一个算法在运行过程中临时占用的存储空间大小的量度,一般也作为问题规模n的函数,以数量级形式给出,记作: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++;
return(s);
}
由于算法中临时变量的个数与问题规模n无关,所以空间复杂度均为S(n)=O(1)。
空间复杂度分析2:
void fun(int a[],int n,int k)
//数组a共有n个元素
{ 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; //执行n-k次
fun(a,n,k+1);
}
}
此属于递归算法,每次调用本身都要分配空间,fun(a,n,0)的空间复杂度为O(n)。
注意:
1.空间复杂度相比时间复杂度分析要少
2.对于递归算法来说,代码一般都比较简短,算法本身所占用的存储空间较少,但运行时需要占用较多的临时工作单元;
若写成非递归算法,代码一般可能比较长,算法本身占用的存储空间较多,但运行时将可能需要较少的存储单元。