什么是数据结构:
1、数据结构的起源:
1968年美国的高钠德教授开创的一门课程《基本算法》,确定了数据结构和算法的基本体系
是一门研究非数值计算的学科,专注于数据的关系及操作。
程序 = 数据结构 + 算法
2、数据结构的基本概念
数据:所有能输入到计算机中的去描述客观事物的符号
数据项:有独立含义的数据的最小单位。
数据元素:数据的基本单位,也称为节点或记录
数据结构:数据元素 + 数据关系组成的集合
算法:
狭义上来说数据结构所具有的功能。
广义上来说解决问题的方法
3、数据结构的三个方面
数据的逻辑结构
数据的存储结构
数据结构的运算
二、四种基本的逻辑结构
集合结构:数据元素之间除了同一属一个集合外,没有关系。
线性结构:数据元素之间存在一对一关系
树型结构:数据元素之间存在一对多关系
图型结构:数据元素之间存在多对多关系
四种基本类型的数据结构
集合:元素之间没有任何关系
线性表:元素之间存在一对一关系(数组)
数组、链表、功能受限的表(栈、队列)
树:元素之间存在一对多关系
普通树、二叉树、完全二叉树、满二叉树、有序二叉树
图:元素之间存在多对多关系
邻接表、表的遍历(深度优先、广度优先)、最短路径
三、数据结构的存储方式
顺序:在一块连续的内存空间上存储元素与元素之间的关系
优点是速度快,不易产生内存碎片,但是对内存要求高,添加删除不方便
非顺序(链式):元素随机存储内存空间中,元素中存储指向其他元素的地址
优点是对内存要求低,添加删除方便,查找速度慢,容易产生内存碎片
1、顺序结构
数据存储在一段连续的空间,用数据元素在存储器的位置来表示数据元素的关系
优点:可能随机访问,访问效率极高。
缺点:空间利用率低,对内存的要求高(必须使用整块的内存),元素添加删除不方便
2、链式结构
结构中的每个数据元素存储在彼此独立的内存中,每个元素中增加一个或多个数据项
用来存放另一个元素的地址,用来表示数据元素之间的关系。
优点:插入删除元素方便,空间利用率高,对内存要求低。
缺点:只能逐个遍历不能随机访问,查询效率低。
注意:每一种逻辑结构采用什么物理结构实现,没有明确规定,通常根据实现的难易程度,
以及在时间和空间的复杂度的要求,在选择合适的物理结构,也可能是顺序和链式混合存储。
表:顺序/链式
树:顺序/链式
图:混合
每种逻辑结构用什么物理结构储存并没有明确规定,通常是根据难易程度、时间和空间上的要求,选择合适的存储结构
数据结构的运算:
1、创建
2、销毁
3、添加元素
4、删除元素
5、修改元素
6、查找元素
7、排序
8、遍历
9、访问
线性结构(表):
顺序表(存储结构)
链式表(存储结构)
栈:是一种功能受限的线性表,只有一个端口用于元素的进出。
特点是先进后出(后进先出),为函数的调用提供支持、常用于对数据的管理、表达式的解析。
练习1:有两个长度相同的序列,序列1是入栈顺序,判断序列2是否是出栈顺序
bool is_pop(int *in,int *out,size_t len);
顺序栈
链式栈
队列:是一种功能受限的线性表,有两个端口进出元素,一个端口只能入队,另一个端口只能出队
特点是先进先出,常用于任务排队,如:数据池、生成者与消费者模型,树的层序遍历,图的遍历。
链表(链式表):元素分散存储在内存中任意位置,元素中有一个数据项用来记录下一个元素的
地址(单向链表),如果元素中有两个数据项一个用来记录下一个元素的地址另外一个记录上一个元素
的地址,这种叫双向链表
练习2:实现一个函数,显示链表的倒数第k个元素
顺序队列、链式队列
作业1:对一个链表进行排序。
作业2:把一个单向链表逆序。
作业3:判断一个链表中是否有环
作业4:判断两个链表是否是Y型链表
链式队列:
1.创建队列
2.销毁队列
3.入队
4.出队
5.队头
6.队尾
7.队空
函数指针:
函数就是一段数据(由代码编译出的二进制指令),存储在代码段中。
函数名就是指向这段数据的指针,因此我们就可以把函数指针当作参数在函数之间进行传递
标准库的快速排序函数:
void qsort(void *base, size_t nmemb, size_t size, int(*compart)(const void *, const void *))
base:数组的首地址
nmemb:数组中每个元素的字节数
compar:用于比较元素之间的函数,他由函数的使用者提供
作业1:从尾到头打印链表
作业2:输入k的值,显示链表中的倒数第k个节点的值
作业3:合并两个有序链表,让合并的结果依然有序
作业4:找出链表的缓刑入口,保证链表中有环
作业5:使用通用链表,实现电话簿
作业1:使用两个栈实现一个队列的功能
作业2:链表逆序
作业3:显示链表的倒数第k个值
作业4:删除链表中的重复的数据,只保留一份
作业5:检查链表中是否有环
作业6:查找环形链表的入口
作业7:检查两个链表
通用链表
void* 万能指针
void* 可以与任意类型的指针互换
int* = void*
void* = int*
把链表的数据域换成void*类型
注意:由于存储的类型不确定,因此类型的运算规则不确定,当需要使用到关系运算,
需要链表使用者
树:
树是一种元素之间存在一对多关系的数据结构,常用于表示组织结构、辅助排序、查找等。
一般以倒悬树形式根在上枝在下
树的相关术语:
根:树的最顶层的元素,有且仅有一个
双亲:指的是上一层元素
儿子:指的是下一层元素
度:“孩子”的数量
深度:树的层数
密度:树的元素个数
节点:一个元素就是一个结点
普通树:孩子的数量无限制。
普通树的顺序存储
一、对节点的存储顺序没有要求
二、从上到下从左到右存储
三、从上到下从左到右存储
普通树的链式存储
二叉树:最多只有两个孩子。
普通二叉树
完全二叉树
满二叉树
有序二叉树:左子树的所有节点都比双亲小,右子树的所有节点都比双亲大
平衡二叉树:左右高度相差不超过1的有序二叉树
2^(k-1) k层节点数
前序遍历:根,左,右
中序遍历:左,根,右
后序遍历:左,右,根
层序遍历:从上到下,先左后右
链式结构存储有序二叉树:
1、将一棵有序二叉树,转换成一个有序的双向链表
2、镜像一颗二叉树
3、有两棵二叉树,A和B,判断B是否是A的子树
4、现有一个树的前序遍历和中序遍历,重建二叉树
5、判断一个序列是否是二叉树的后序遍历结构
bool is_post(int arr[],Node* root);
6、判断一棵二叉树是否是AVL树
图:
元素之间存在多对多关系(线性表的元素之间存在前驱和后继,树的元素之间存在
父子关系,图的任意元素之间都有可能存在关系)
在图中不允许出现没有点,但可以没有边。
G(V,E),V表示顶点的集合,E表示边的集合
无向图:顶点与顶点之间没有方向,这种边称为无向边,边用无向序偶对表示(v,V1)
V = {A,B,C,D} E = {(A,B),(B,C),(C,D),(D,A),(A,C)}
在无向图中,如果任意两个点之间都存在边,这种图称为无向完全图
n*(n-1)/2
有向图:若顶点之间有方向,这种边称为有向边,也叫弧,用有序偶对表示,V1叫
作弧头,v叫作弧尾
注意:若不存在顶点到自身和边,也不存在重复出现的边这种图叫简单图,数据结构课程中
讨论的都是简单图
在有向图中如果任意两个顶点之间存在方向相反的两条弧,这种图叫做有向完全图
图中有很少边或弧的图叫做稀疏图,反之叫作稠密图
如果图中的边或弧有关的数据,数据称为权,这种图也叫网(带权图)
如果G(v,E)和G1(v1,存在V∈V1且,E∈E1那么G1是G的子图
顶点与边的关系
顶点的度:指的是顶点相关联的边或弧的条目,有向图又分为出度和入度
入度:其他顶点到该顶点的弧的条目数
出度:从该点出发到其他顶点的弧的条目数
顶点序列:从一个顶点到另一个顶点的路径,路径长度指的是路径上的边或弧的条目数
连通图相关术语:
在无向图中,在顶点v到v1之间有路径,则称v到v1之间是连通的,如果任意两个顶点都是连通的
,那么这种图称为连通图
无向图中的极大连通子图称为连通分量:
1、必须是子图
2、子图必须是连通的
3、连通子图含有极大的顶点数
在有向图中,在任意顶点之间都存在路径,这种图叫强连通图。
有向图中的极大连通子图称为有向的强连通分量
在有向图中如果有一个顶点的入度为0,其他顶点的入度均为1,则是一棵有向树
图的存储结构:
图的存储主要是两个方面:顶点,边
邻接矩阵:一个一维数组(顶点)和一个二维数组(边、弧)组成
二维数组i,i位置都是0,如果是无向图则数组对称(左上到右下的对角线为轴)
优点:
1、非常容易判定两点之间是否有边
2、非常容易计算任意顶点的入度、出度
3、非常容易统计邻接点
缺点:如果存储稀疏图,会非常浪费存储空间
邻接表:由顶点表,边表组成
优点:
1、节省存储空间
2、非常容易计算出度
缺点:不方便计算入度
十字链表:由于邻接标不能同时兼顾出度和入度,因此我们修改邻接标的边表结构
,使它即存储入度也存储出度,这种表就叫十字链表
邻接多重表:由于遍历表是需要一些删除边操作,而邻接表在删除边时非常麻烦,因此
就设计出的邻接多重表
边集数组:由两个一维数组构成,一个存储顶点的信息,另一个存储边的信息(它的每个数
据元素都由一条边的起点到终点的下标和权组成),这种存储结构更侧重于边的相关操作(路径、
路径长度、最短路径),而统计顶点的度需要扫描整个数组,效率不高。
图的遍历:
注意:图的遍历结果无论时深度优先还是广度优先都不是唯一的
深度优先:类似树的前序遍历
广度优先:类似树的层序遍历,与树一样也需要队列的配合
算法:
数据结构中的算法,指的是数据结构所具备的功能
解决特定问题的方法,他是前辈们的一些优秀的经验总结。
分治:
把一个大而复杂的问题,分解为很多小的问题而简单的问题。利用计算机的强大计算能力来解决问题
实现分支的方法有:循环、递归
递归:是函数自己调用自己的一种行为,可以形成循环调用,进而实现分治算法‘
什么情况下使用递归:
1、问题太复复杂无法拆解成循环语句
2、问题非线性,而函数递归,由于借助栈内存(函数每次调用,他的数据都会保留下来),在单
线程模式下,只能同时执行一个函数,当函数自己调用自己时,会先执行子级的代码,然后子级
执行完成后再返回到上一级继续执行
如何安全实现递归:
使用递归很有可能造成“死循环”,非常浪费资源
1、先写出口,考虑如何无限的调用停止下来
if(条件) return;
2、解决一个小问题
3、把剩下的问题交给我的下一级(参数发生变化)
练习:1、使用递归计算前n项斐波那契数列
2、使用递归解决汉诺塔问题,n层盘子的移动过程
递归的优点:
代码简单容易理解。
递归的缺点:
容易形成死循环,耗费内存,执行效率低
查找:
顺序查找:从头到尾逐一比较,对于数据没有要求,简单
二分查找:数据有序,然后从数据中间位置开始查起
块查找、权重查找:适用于特殊条件下,需要对数据进行排序、分析、总结、归纳
排序:
排序算法的稳定性:当序列中相同的数据量,算法会不会改变这两个数据的前后位置
冒泡:是一种稳定排序,再排序过程中可以检测到数据是否已经有序,可以立即停止,如果待排序的数据
基本有序,则冒泡的效率是非常高的
插入排序:当一列数已经有序,再有加入的数据时,适合使用插入排序
选择排序:是冒泡排序的一种变种,但是它没有对冒泡对数据有序性的敏感,但它在排序过程中比冒泡少
很多的数据交换,因此要比冒泡快的多
快速排序:一种基于交换的排序
堆排序:首先把数组构建成完全二叉树,然后保障根点最大,然后把根结点与最后一个元素交换,然后
在调整二叉树,让根依然保持最大,重复上次操作
归并排序:不交换数据,但需要借助额外的空间,用作临时的存储空间
时间复杂度:
注意:时间复杂度并不是指算法运行所需的时间,而是算法执行的次数
冒泡排序:0(N),0(N^2)
插入排序:0(N),0(N^2)
选择排序:0(N),0(N^2)
快速排序:0(nlog2n)
堆排序:0(nlog2n)
归并排序:0(nlog2n)
排序法 最差时间分析 平均时间复杂度 稳定度 空间复杂度
冒泡排序 O(n2) O(n2) 稳定 O(1)
快速排序 O(n2) O(n*log2n) 不稳定 O(log2n)~O(n)
选择排序 O(n2) O(n2) 稳定 O(1)
二叉树排序 O(n2) O(n*log2n) 不一顶 O(n)
插入排序 O(n2) O(n2) 稳定 O(1)
堆排序 O(n*log2n) O(n*log2n) 不稳定 O(1)
希尔排序 O O 不稳定 O(1)