数据结构概述
1. 数据结构 = 个体的存储 + 个体的关系存储
把现实中大量而复杂的问题以特定的数据类型和特定的存储结构保存到主存储器(内存)中,以及在此基础上实现某个功能(比如查找,删除某个元素,对所有 元素进行排序)而执行的相应操作,这个相应的操作也叫算法。
2. 算法 = 对存储数据的操作( 解题的方法和步骤 )
衡量算法的标准:
- 时间复杂度:大概程序要执行的次数,而非执行的时间;
- 空间复杂度:算法执行过程中大概所占用的最大内存;
- 难易程度; 4.健壮性;
-
逻辑结构
1)线性结构:
- 数组
- 链表
栈和队列是一种特殊的线性结构
2)非线性结构:
- 树
- 图
-
物理结构
模块一 线性结构
【把所有的结点用一根直线穿起来】
1. 连续存储【数组】
优点:存取速度快
缺点:插入删除元素慢;事先必须知道数组的长度;空间通常是有限制的,需要大块连续的内存块
2. 离散存储【链表】
优点:插入删除元素快;空间没有限制
缺点:存取速度慢
2.1 定义
n 个节点离散分配,彼此通过指针相连;每个节点只有一个前驱节点和一个后续节点;首节点没有前驱节点,尾节点没有后续节点
-
首节点:第一个有效节点
-
尾节点:最后一个有效节点,指针域为空
-
头结点:第一个有效节点之前的那个节点
- 头结点并不存放有效数据
- 可以方便对链表的操作
-
头指针:指向头结点的指针变量,存放的头结点地址
-
尾指针:指向尾结点的指针变量,存放的尾结点地址
若通过一个函数对链表进行处理,确定一个链表,只需要一个参数:头指针,即可推算出链表的其他所有信息。
2.2 分类
-
单链表:每个节点的指针域只能指向后继节点
-
双链表:每一个节点有两个指针域
-
循环链表:能通过任何一个节点找到其他所有的节点
-
非循环链表
算法:遍历、查找、清空、销毁、求长度、排序、删除、插入
3. 线性结构的两种常见应用之一 栈
3.1 定义
一种可以实现 “ 先进后出 ” 的存储结构,类似于弹夹
3.2 分类
-
静态栈
-
动态栈
3.3 算法
-
出栈
-
压栈
3.4 应用
函数调用、中断、表达式求值(利用两个栈可以编写一个计算器)、内存分配缓冲处理、迷宫
4. 线性结构的两种常见应用之二 队列
4.1 定义
一种可以实现 “ 先进先出 ” 的存储结构
4.2 分类
-
链式队列 —— 用链表实现
-
静态队列 —— 用数组实现
4.2.1 静态队列
静态队列通常都必须是循环队列 !
循环队列:
- 静态队列为什么必须是循环队列
避免内存泄漏
- 循环队列需要几个参数来确定
2个参数:
front
、rear
- 循环队列各个参数的含义
不同场合有不同含义:
1)队列初始化:
front
和rear
的值都是零2)队列非空:
front
代表的是队列的第一个元素
rear
代表的是队列的最后一个有效元素的下一个元素3)队列空
front
和rear
的值相等,但不一定是零
- 循环队列入队伪算法讲解
1)将值存入
r
所代表的位置2)正确的写法:
rear = ( rear + 1 ) % 数组的长度
错误的写法:
rear = rear + 1
- 循环队列出队位算法讲解
front = ( front + 1 ) % 数组的长度
- 如何判断循环队列是否为空
如果
front
与rear
的值相等则该队列就一定为空
- 如何判断循环队列是否已满
front
的值可能比 rear
大,也可能比 rear
小,也有可能两者相等
两种方式:
1)多增加一个标识参数
2)少用一个元素 (通常使用第二种方式)
如果 front 和 rear 的值紧挨着,则队列已满
if ( ( rear + 1 ) % 数组长度 == front )
已满
else
不满
-
算法:
入队
出队
-
队列的具体应用:所有和时间有关的操作都有队列的影子
5. 专题 —— 递归
5.1 定义
一个函数自己直接或间接调用自己
5.2 满足的三个条件
-
递归必须得有一个明确的终止条件;
-
该函数所处理的数据规模必须在递减;
-
这个转化必须是可解的。
5.3 举例
-
求阶乘
-
1+2+3+…+100
-
汉诺塔
-
走迷宫
5.4 循环和递归
-
循环:不易理解,速度快,存储空间小
-
递归:易于理解,速度慢,存储空间大
5.5 应用
-
树和森林就是以递归的方式定义的
-
数和图的很多算法都是以递归来实现的
-
很多数学公式就是以递归的方式定义的
6. 函数的调用
-
当在一个函数的运行期间调用另一个函数时,在运行被调用函数之前,系统需要完成的三件事:
1)将所有的实际参数,返回地址(下一条语句)等信息传递给被调函数保存;
2)为被调函数的局部变量(也包括形参)分配存储空间;
3)将控制转移到被调函数的入口。
-
从被调函数返回主调函数之前,系统也要完成的三件事:
1)保存被调函数的返回结果;
2)释放被调函数所占的存储空间;
3)依照被调函数保存的返回地址将控制转移到调用函数。
-
当有多个函数相互调用时,按照 “ 后调用先返回 ” 的原则,上述函数之间信息传递和控制转移必须借助 “ 栈 ” 来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就在栈顶分配一个存储区,进行压栈操作,每当一个函数退出时,就释放它的存储区,就进行出栈操作,当前运行的函数永远都在栈顶位置。
模块二 非线性结构
1. 树
1.1 定义
- 专业定义:
-
有且只有一个根节点;
-
有若干个互不相交的子树,这些子树本身也是一棵树
- 通俗定义:
-
树是由节点和边组成;
-
每个节点只有一个父节点,但可以有多个子节点;
-
但有一个节点例外,该节点没有父节点,此节点称为根节点
1.2 专业术语
- 节点 父节点,子节点
- 子孙 堂兄弟
- 深度:从根节点(第一层)到最底层节点的层数称之为深度
- 叶子节点:没有子节点的节点
- 非终端节点:实际就是非叶子节点,即有子节点的节点
- 度:子节点的个数称为度
1.3 分类
1.3.1 一般树
任意一个节点的子节点个数都不受限制
1.3.2 二叉树
任意一个节点的子节点个数最多为两个(可以一个,可以两个),且子节点的位置不可更改(左子树,右子树)
1)一般二叉树
2)满二叉树:在不增加树的层数的前提下,无法再多添加一个节点的二叉树就是满二叉树
3)完全二叉树:如果只是删除了满二叉树最底层最右边的连续若干个节点,这样形成的二叉树就是完全二叉树。(特例:满二叉树)
性质:
知道节点个数,可以知道树有多少层;
知道任意一个点,可以知道它的父节点,有没有子节点,子节点是谁。
1.3.3 森林
n 个互不相交的树的集合
1.4 存储
1.4.1 二叉树的存储
-
连续存储【完全二叉树】
- 优点:查找某个节点的父节点和子节点速度很快(也包括判断有没有子节点)
- 缺点:耗用内存空间过大
-
链式存储
-
一般树的存储:把一个普通树转化成二叉树来存储。一个普通树转化成的 二叉树一定没有右子树!
1)双亲表示法:求父节点方便
2)孩子表示法:求子节点方便
3)双亲孩子表示法:求父节点和子节点都很方便
4)二叉树表示法:具体转化方法:设法保证任意一个节点的 左指针域指向它的第一个孩子,右指针域指向它的下一个兄弟。
1.4.2 森林的存储
具体转化方法:先把森林转化成二叉树,再存储二叉树。左指针域指向它的第一个孩子,右指针域指向另一棵树。
1.5 二叉树操作
1.5.1 遍历:递归!
-
先序遍历( 根——左——右 ):先访问根节点,再先序访问左子树,最后先序访问右子树
-
中序遍历( 左——根——右 ):先中序遍历左子树,再访问根节点,最后中序遍历右子树
-
后序遍历( 左——右——根 ):先后序遍历左子树,再后序遍历右子树,最后访问根节点
- 已知两种遍历序列来求原始二叉树:通过 先序和中序 或 中序和后序 ,可以还原出原始的二叉树。但是,通过 先序和后序 无法还原出原始的二叉树!
应用:
-
树是数据库中数据组织的一种重要形式
-
操作系统子父进程的关系本身就是一棵树
-
面向对象语言中类的继承关系本身就是一棵树