一、什么是数据结构
1、什么是程序
程序=数据结构+算法
数据结构:如何把现实世界的问题信息化,将信息存储到计算机当中。同时要实现对数据结构的基本操作。
算法:如何处理这些信息
2、什么是算法
如何处理信息的方式,解决问题的方法
(1)算法的特性
a、有穷性:一个算法必须总在执行有穷步骤之后结束,且每一步都可以在有穷时间内完成。
注意:算法必须是有穷的,而程序可以是无穷的
设计一个算法,解决一个特定的问题(在有限的步骤和有限的时间内完成)
b、确定性:
算法中每条指令必须要有确切的含义,对于相同的输入只能得到相同的输出结果
c、可行性:算法当中描述的操作都是可以通过已经实现的基本运算执行有限次来实现的
d、输入:一个算法有零个或者多个输入,这些输入取自于某个特定的对象的集合
e、输出:一个算法有一个或多个输出,这些输出是与输入有着特定关系的量
(2)好的算法的特质
设计算法时要尽量追求的目标
1)正确性。算法应能够正确地解决求解问题。
2)可读性。算法应具有良好的可读性,以帮助人们理解。
3)健壮性。输入非法数据时,算法能适当地做出反应或进行处理,而不会产生莫名其妙的输出结果。
4)高效率与低存储量需求
高效率:执行速度快,时间复杂度低
低存储量:不浪费内存,空间复杂度低
(3)知识点:
二、算法
1、算法销量的度量
(1)时间复杂度
如何评估算法时间的开销
T = T(n)
a、算法的时间复杂度
尝试程序
#include<stdio.h>
void main(){
loveYou(3000);
}
//算法1 - 逐步递增爱你
void loveYou(int n){ //n 为问题规模
int i = 1;//爱的程度
while(i<=n){
i++;//每次+1
printf("I Love You %d\n",i);
}
printf("I Love You More Than %d\n",n);
}
语句频度:
第7行代码 执行1次
第8行代码 3001次
9和10行执行 3000次
12行代码 执行1次
T(3000) = 1 + 3001 + 2 * 3000 +1
时间开销和问题规模的n的关系
T(n)=3n+3 为什么是3n因为循环语句当中执行了3条语句所以是3n其余的3,其中一个是判断条件多执行一次和循环外是两条语句
b、表示方法
问题:
时间开销于问题规模n的关系
当问题规模比较大的时候可以忽略掉,只取高阶即可
结论:可以只考虑阶数较高的部分
还可以更加减缓
大O表示法:只取数量级
T表示时间复杂度 O表示数量级
c、时间复杂度的运算规则
i、加法规则:
T(n) = T1(n) + T2(n) = O(f(n)) + O(g(n)) = O(max(f(n),g(n)))
多项相加,只保留最高阶的项,且系数变为1
ii、乘法规则
T(n) = T1(n) X T2(n) = O(f(n)xO(g(n)) = O(f(n)Xg(n))
多项相乘,都要保留
因为加法定理需要保留数量级大的,但是那个数量级更大呢通过以下结论得出
通过这个结论可以得出
问题:两个算法的时间复杂度分别如下,哪个的阶数更高(时间复杂度更高) ?
斜率越大算法就越差
常<对<幂<指<阶 (时间复杂度)
d、实际代码时间复杂度的分析
问题二:
如果有好几千行代码安装这种一行一行的数?
就算有好几千行顺序执行的代码,以及不影响数量级O的大小
问题三:嵌套循环
#include<stdio.h>
void main(){
loveYou(3000);
}
//算法2 - 嵌套循环型爱你
void loveYou(int n){ //n 为问题规模
int i = 1;//爱的程度
while(i<=n){
i++;//每次+1
printf("I Love You %d\n",i);
for(int j = 1; j <= n; j++){
printf("I am Iron Man\n");
}
}
printf("I Love You More Than %d\n",n);
}
e、小练习一:指数型递增
#include<stdio.h>
void main(){
loveYou(3000);
}
//算法3 - 指数型递增爱你
void loveYou(int n){ //n 为问题规模
int i = 1;//爱的程度
while(i<=n){
i = i * 2;
printf("I Love You %d\n",i);
}
printf("I Love You More Than %d\n",n);
}
运行结果
计算上述算法的时间复杂度T(n):
设最深层循环的的语句的频度(总循环的次数)为x,则
当循环了x 次的时候 i = 2 ^ X
由于循环条件可知,循环结束时刚好满足2^x > n
f、小练习二(三种复杂度)
#include<stdio.h>
void main(){
int flag = {1,2,3,4,5,6};
loveYou(flag,3000);
}
//算法4 - 搜索数字型爱你
void loveYou(int flag[],int n){ //n为问题规模
printf("I Am Iron Man\n");
int i;//爱的程度
for(i= 0; i < n;i++){//从第一个元素开始查找
if(flag[i] == n){//找到元素n
printf("I Love You %d\n",n);
break;//找到后立即跳出循环
}
}
printf("I Love You More Than %d\n",n);
}
情况一:最好情况时间复杂度
计算上述算法的时间复杂度T(n)
最好的情况:元素n在第一个位置
------- 最好的时间复杂度 T(n) = O(1)
情况二:最坏时间复杂度
最坏情况:元素n在最后一个位置
------- 最坏时间复杂度T(n) = O(n)
情况三:平均情况
假设元素n在任意一个位置的概率相同为1/n
------- 平均时间复杂度T(n) = O(n)
循环次数
很多算法的执行时间于输入的数据有关
最坏时间复杂度:最坏情况下的算法的时间复杂度
平均时间复杂度:所有输入示例等概率的情况下,算法的期望运行时间
最好的时间复杂度:最好的情况下的时间复杂度
小提示:算法的性能问题只有在n很大的时候才会暴露出来
(2)空间复杂度
空间开销(内存开销)与问题规模n之间的关系
a、程序运行时的内存开销
b、空间复杂度
假设一个int变量占4B…
则所需内存空间=4 + 4n + 4 = 4n + 8
只需要关注存储空间大小与问题规模相关的变量
S(n) = O(n)
c、函数递归调用带来的内存开销
#include<stdio.h>
void main(){
loveYou(5);
}
//算法5 --递归型爱你
void loveYou(int n){//n 为问题规模
int a,b,c;//声明一系列的局部变量
//..
if(n > 1){
loveYou(n-1);
}
printf("I Love You %d\n",n);
}
三、线性表
1、线性表的定义
逻辑结构
数据的运算
存储结构(物理结构):存储结构不同,运算的实现方式不同(物理结构)
(1)线性表的定义:数据结构三要素–逻辑结构
2、线性表的基本操作
数据结构的三要素—运算
InitList(&L):初始化表。构造一个空的线性表L,分配内存空间。
Destory(&L):销毁操作。销毁线性表,并释放线性表L所占的内存空间。从无到有,从有到无。的操作
ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e。
ListDelete(&L,i,&e);删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。
其他常用操作:
Length(L):求表长。返回线性表L的长度,既L中数据元素的个数。
PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。
Empty(L):判空操作。若L为空表,则返回true,否则返回false
(1)对数据的操作(记忆思路)--创销、增删改查
(2)C语言函数的定义---<返回值类型> 函数名 (<参数1类型> 参数1,<参数2类型> 参数2,....)
(3)实际开发当中,可以根据实际需求定义其他的基本操作
(4)函数名和参数的形式、命名都可以改变(Reference)
命名需要具有可读性
(5)什么时候要引入用“&”--对参数的修改结果需要“带回来”
(key:命名要要可读性)
3、线性表的基本操作:代码的实现
I、C语言代码的实现
什么时候要传入参数引用“&” 一一对参数的修改结果需要“带回来”
#include<stdio.h>
void test(int x){
x = 1024;
printf("test函数内部 x = %d\n",x);
}
int main(){
int x = 1;
printf("调用test前 x = %d \n",x);
test(x);
printf("调用test后 x = %d \n ",x);
}
通过指针的方式使其操作同一块内存空间
#include<stdio.h>
void test(int x){
x = 1024;
printf("test函数内部 x = %d\n",x);
}
void test2(int *x){
*x = 1024;
}
int main(){
int x = 1;
printf("调用test前x=%d \n",x);
test(x);
printf("调用test后x=%d \n",x);
test2(&x);
printf("调用test2后x=%d\n ",x);
}
II、C++代码实现
什么时候要传入参数引用“&” 一一对参数的修改结果需要“带回来”
#include<stdio.h>
void test(int x){
x = 1024;
printf("test函数内部 x=%d\n",x);
}
int main(){
int x = 1;
printf("调用test前的 x = %d \n",x);
test(x);
printf("调用test后的 x = %d \n",x);
}
什么时候需要传入参数的引用“&” 一一对参数的修改结果需要“带回来”
#include<stdio.h>
void test(int &x){
x = 1024;
printf("test函数内部 x=%d\n",x);
}
int main(){
int x = 1;
printf("调用test前的 x = %d \n",x);
test(x);
printf("调用test后的 x = %d \n",x);
}
4、为什么要实现对数据结构的基本操作
a、团队合作编程,你定义的数据结构要让别人能够很方便的使用(封装)
b、将常用的操作/运算封装成函数,避免重复工作,降低出错风险