基本概念
- 数据:信息载体,被计算机识别处理的集合符号
- 数据元素:数据基本单位
- 数据对象:具有相同性质的数据元素集合
- 数据类型:一个值得集合与定义在此集合上的一组操作总称
- 抽象数据类型:数据结构+定义与此的一组操作
- 数据结构:相互之间存在一种或多种关系的数据关系集合
数据结构三要素
- 逻辑结构:分为线性结构和非线性结构。如下图:
- 线性表:元素之间只存在一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。
线性表有两种存储方式,一种是顺序存储结构,另一种是链式存储结构。
集合:同属于一个集合
- 属性结构:元素存在一对多的关系
图状结构或网状结构:元素存在多对多关系
存储结构:
顺序存储:相邻元素存储在物理位置上,元素之间关系由存储单元表示。
优点:可以实现随机存取,元素占用最少空间
缺点:只能使用相邻一块存储单元,可能产生较多外部碎片
链式存储:逻辑上相邻元素存储在物理位置上也相邻存储单元里,元素之间由存储单元邻接关系体现。
优点:充分利用所有存储单元;
缺点:每个元素因存储指针而占用额外的存储空间,只能实现顺序存取;
索引存储:建立附加索引表;
优点:检索速度快
缺点:增加的索引表,占用过多存储空间;修改时,也会话费很多时间
散列存储:根据元素关键字直接计算出该元素的存储地址,称为Hash存储
优点:检索,增加和删除结点操作更快;
缺点:如果散列函数不好则会出现元素存储单元的冲突,解决冲突需要增加时间和空间开销
3.算法的特点
有穷性、确定性、可行性、输入、输出,其含义,你懂得,无需我多言
4.算法效率的度量
时间复杂度
一个语句频度在该语句在算法中被重复执行次数,所有语句频度之和,记作:T(n) 是算法问题规模N的函数
空间复杂度
S(n),定义为算法所耗费的存储空间,是问题规模N的函数
思维拓展
1.对于两种不同的数据结构,逻辑结构或物理结构一定不相同吗?
两种不同的数据结构,他们的逻辑结构和物理结构完全有可能相同。比如二叉树和二叉排序树,二叉排序树可以采取二叉树的逻辑表示和存储方式,前者通常用来表示层次关系,而后者通常用来排序和查找。虽然运算都有建立树,插入结点,删除结点和查找结点等,但是对于二叉树和二叉排序树,这些运算定义是不同的,以查找为例,二叉树时间复杂度是O(n),而二叉排序树是O(log2n)
经典题目2:写一个函数,输入n,其斐波那契数列的第n项。
方法1:使用递归解,时间复杂度是n的指数级别
#include<iostream>
#include<stdlib.h>
using namespace std;
int Fibonacci(int n)
{
if(n<=0)
return 0;
if(n==1)
return 1;
return Fibonacci(n-1)+Fibonacci(n-2);
}
void main()
{
int f=Fibonacci(10);
cout<<f<<endl;
system("pause");
}
时间复杂度是n的指数级别,随着n的增大,以f(10)为例,f(10)=f(9)+f(8),f(9)=f(8)+f(7)。其中的f(8)就是重复计算的。
方法2:开辟一个长度为(n+1)的数组,时间复杂度为O(n),空间复杂度为O(n)
将这些计算出来的值保存在一个数组arry[n+1]上,这样计算斐波那契数列就相当于是一个填表的过程:
int Fibonacci(int n)
{
if(n<=0)
return 0;
else if(n==1)
return 1;
else
{
//动态创建一个长度为(n+1)的数组
int *arr=new int[n+1];
arr[0]=0;
arr[1]=1;
for(int i=2;i<=n;i++)
{
arr[i]=arr[i-1]+arr[i-2];
}
int result=arr[n];
delete [] arr;
return result;
}
}
注意点:
因为不知道要求的f(n)中的n有多大,因此不能事先开辟一个数组,需要动态创建数组。而动态数组与数组变量不同,动态分配的数组将一直存在,直到程序显式释放它为止。普通的数组变量,只要出了数组的作用于,其内存会自动释放。
c++提供delete []表达式释放指针所指向的数组空间。delete []
arry;该语句回收了arr所指向的数组,把相应的内存返回给自由存储区。在关键字delete和指针arr之间的方括号[]是必不可少的:它告诉编译器该指针指向的是自由存储区中的数组,而非单个对象。delete
arry只释放了arr指针所指向的内存地址,理论上来说会少释放了内存空间,从而产生内存泄露。方法3:优化方法2,空间复杂度为O(1),时间复杂度为O(n)
int Fibonacci(int n)
{
if(n<=0)
return 0;
else if(n==1)
return 1;
else
{
//当n>=2时,初始化pre=f(0)=0,post=f(1)=1,f(n)=0;
int pre=0;
int post=1;
int fn=0;
//采用循环计算斐波那契数列,通过两个临时变量pre和post保存中间结果,避免重复计算
for(int i=2;i<=n;i++)
{
fn=pre+post;//fn等于其前面两个元素值的和
//然后让pre和post分别直线他们后面的元素。
pre=post;
post=fn;
}
return fn;
}
}