数据结构分类
数据结构包括的方面:数据的逻辑结构,物理结构和基本操作
算法定义:指令集里的指令为了解决某个问题而规定的运算序列
算法的评价:正确性、可读性、健壮性、效率
程序 = 数据结构 + 算法
逻辑结构分类:线性结构:线性表。 非线性结构:树、图
物理结构:顺序存储、链接存储(内存)、索引存储、散列存储(外存)。
基本操作。
ADT:抽象数据类型:数据对象、数据关系、基本操作
数据结构通过类型表示和实现
算法的定义(时间复杂度的分析)
1线性表:有相同数据类型的n数据元素的有限序列
实际应用:除了第一个,和最后一个节点,每一个元素都只有一个前驱和后驱
存储结构:顺序存储结构:顺序表
链式存储:单链表,双链表,循环链表(指针实现),静态链表(数组实现)
顺序表:
增删考虑满/空, 删除的位置是不是合适(1~n)复杂度O(n)
优点:1 原理简单 2 支持随机访问。可以通过式子得到第几个元素的位置3 省地儿
缺点:1 实现分配存储空间 2存储空间必须连续3 插入删除时间复杂度高
链式表:
结构:一组地址任意的数据单元 + 指针
建立方法: 1申请一个链结点的空间 2 ,,,
优点:1存储空间动态分配,根据实际使。2地址不一定连续.3插入删除时间复杂度为O(1)
缺点:1每一个链结点要有指针。2查找定位要顺序扫描O(n)
递归算法比链式算法:
占用更多的空间。每次递归时会将大量不必要的临时变量暂存在堆栈中。如果不对递归进行剪枝优化,那么传递参数也是需要时间的
malloc是在堆上分配的。堆栈区别:
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其
操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回
收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表
线性链表插入头节点的目的。头节点指向固定的头指针,不用分类讨论。
1、是为了方便单链表的特殊操作。对在第一个元素结点前插入结点和删除第一个结点,其操作与对其它结点的操作统一了
2、单链表加上头结点之后,无论单链表是否为空,头指针始终指向头结点,因此空表和非空表的处理也统一了
循环链表:这只尾指针的目的:头节点,判断是否一圈。
双向链表:
链表建立的两种方法:头插法,尾插法
2堆栈和队列
堆和栈的区别:
程序内存分配中的堆和栈:
栈是系统会自动为其开辟空间。堆则是程序员根据需要自己申请的空间
堆会在申请后还要做一些后续的工作这就会引出申请效率。而栈的剩余空间大于所申请空间,系统将为程序提供内存
数据结构中的堆和栈:栈就像装数据的桶或箱子。堆:经过排序的树形数据结构。比如二叉堆
栈:stack , 后进先出,一端进行插入删除,push, pop,一个指针负责入栈,出栈
运用例子:递归函数的逐级调用,简单计算器的实现,二叉树深度遍历的非递归方法。编译上变量和参数的存储空间的分配
溢出:上溢— 当堆栈已满时做插入操作。下溢— 当堆栈为空时做删除操作。
队列:先进先出FIFO,一端插入,一端删除。Rear and front变量分别指头、尾元素的位置
释放一个队列:删除所有节点,释放存储空间
例子运用:OS:作业调度、进程调度。二叉树的层次遍历,图的广度优先遍历
二者操作时间都是O(1)
串
String : N个字符组成的有限序列。用一对引号括起来
串的存储结构:顺序:.非紧缩格式、紧缩格式、单字节方式
链式存储结构:
模式匹配KMP算法:
字符串的定位。给定一个主字符串S和一个子串T(又称模式串),长度分别为n和m。在主串S中,从位置pos开始查找,若在主串S中找到一个与子串T相等的子串,则返回T的第一个字符在主串中的位置序号。
数组
广义表
每个元素可以是一个表。一般采用链式存储结构,链结点的构造
(表结点或原子节点)
树和二叉树
[树 Tree]:是一个非空的有限元素的集合,元素之间具有如下
逻辑结构:除根外,每个元素有且仅有一个前驱,有零个或多个后继
最重要操作:遍历
存储结构:
顺序存储(需要约定,不能存结构)
链式存储
[二叉树 Binary Tree]
二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”
运用例子:用于实现二叉查找树和二叉堆
性质:二叉树的第 i 层上至多有 2i-1 个结点(i³1)
深度为 k 二叉树至多有 2k -1个结点(k³1)
存储方式:
- 顺序存储的特点,性质,优缺点:
性质:对二叉树按完全二叉树形式编号而得,编号为 i 的存放在第i 个位置。
优点:双亲 i/2 ,孩子 2i, 2i+1。便于查找
缺点:插入、删除需移动元素复杂;占用空间随二叉树形态的变化而变化。存在极端情况(所有结点只有右子树没有左子树),空间利用率会很低
- 链式:任意的空间单元来存储二叉树的各个元素,同时也存储其相关元素的地址
优点:占用空间不随二叉树的形态而变化;插入、删除元素不需移动
缺点:涉及的指针多
遍历方式:深度优先遍历/广度优先遍历
深度遍历非递归方法:借助栈数据结构可以实现
广度优先遍历:又称为层次遍历,从第1层开始,逐层访问二叉树的元素,每一层从左向右
队列数据结构辅助来完成层次遍历
(已知完全前序序列,唯一确定一棵二叉树: 将空的也表示出来)
只要有中序遍历,结合其他的就可得到的原因:中序,根的左边全是左子树,右边都是右子树。而其他的方法可以具体知道根所在的位置和数值。从而可以再中序序列中具体的确定根。然后结合递归的方法就能画出一棵树。
- [ 满二叉树 full binary tree ] 高度为 k 的二叉树,若具有 2k-1 个结点,称为满二叉树(每一层上的结点数都是最大结点数)
- [ 完全二叉树 complete binary tree ]
1定义:(从左往右加)除最后一层外,若其余层都是满的,并且或者最后一层是满的,或者是在右边缺少连续若干结点
2 顺序存储:
存储位置体现层次关系,双亲 i/2 ,孩子 2i, 2i+1,但是,插入、删除需移动元素;空间效率低
3 链式存储
特点:三个域。数据,左、右
- [线索二叉树]:记录了线索的二叉树。对于n个结点的二叉树,在二叉链存储结构中有n+1个空链域。空出来的指针用来记录某种遍历情况下的前驱结点后驱结点的指针。
目的:记录二叉树某种遍历方式下元素之间的线性关系
树、森林与二叉树的转换
特殊二叉树:二叉排序树、堆、最优二叉树
- [二叉排序树 Binary Sort Tree,BST]
应用:查找方便。查找一个结点的平均时间复杂度是O(log n)
- 定义:任何结点均满足条件:“大于其左子树上的所有结点,小于其右子树上的所有结点
- 特点
1)可以实现类似折半思想的查找,有较高的查找性能
2)中序遍历二叉排序树,可得到一个有序序列
3.插入方法:
(1)若二叉树为空,则插入到空二叉树中,该数据元素就是根
(2)若二叉树不是空,则与根比较,若比根大则在右子树中插入,否则在左子树中插入
4.删除方法:
用被替换结点p的前驱或者后继去替换被删除的结点p,然后把前驱或者后继删除。
前驱:小于某个节点的最大节点(左子树中最大的值);后继:大于某个结点的最小结点(右子树的最小值)
平衡二叉树
它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
- [堆]
- 定义:“堆”是一种特殊的二叉树数据结构。堆(小堆):根节点小于左右子树,但是左右子树没有大小之分
- 应用:用来排序的。 相当于c++中的优先级队列。
- 堆的插入:1 自上而下将新插入的元素放在最后一个位置 2不断地在与其父节点比较(重复调用siftDown),调整其位置,并维持完全二叉树的形态。
- 通过插入操作,将N个元素一个个相继插入到一个初始为空的堆中去,其时间代价最大为O(N logN)
- 堆的删除:由于堆是队列结构,只能删除堆顶元素根节点。用最后一个元素来替换堆顶元素,可能破坏了堆的结构,所以需要将对自顶向下调整,使其满足最大或最小堆
- [最优二叉树]:
- 举例:Huffman编码,根据字符使用频率来最大化节省字符(编码)的存储空间
- 从根结点到各个内结点的加权路径长度之和最小的二叉树
- 建立的过程:思想:权值越小的叶子,离根的距离尽可能远(贪心算法)。
将所有节点一开始都视为森林,每次从森林中选取两个权值最小的树合并为一棵新树,新树的根节点大小为两个子节点大小的和,并将这棵新树重新加入到森林中。
如此一来每一轮操作都可以:将两棵树合并为一颗、放到森林里,直到森林中只剩下一棵树,即是哈夫曼树
哈夫曼编码作用:解决远距离电报数据传输的优化问题。比如过英文文章传输,假设每个字母固定用一个二进制串表示,文章很长的传送串就会很长。但每个英文字母出现的频率是不一样的,所以可以根据字母频率设定权值。用哈夫曼规划。然后从根到叶子所经过的路径的数字用来编码。当双方约定好同样的哈夫曼树之后,发送的信息就能明显减少串的长度。
(树的指针更多,转成二叉树解决问题,然后再转化回来。)
- 树、二叉树数据结构的定义、特点
- 有关的术语(度、深度、满二叉树、完全二叉树……)
- 二叉树的性质(顺序链式,和上面的操作)
- 二叉树ADT的定义
- 二叉树ADT实现
顺序存储:方式、优缺点
链式存储:方式、特点
6. 二叉树的重要操作
遍历:方式
创建:
6.线索二叉树是什么
(指针指向便利中的前驱和后继)
线索的目的、线索的存储、线索的使用、线索的建立
7. 树、森林与二叉树的关系
转换
遍历的对应
8. 特殊二叉树(树的应用)举例和应用
二叉排序树:特点、创建、应用——查找
堆树:特点、创建(调整)、应用——排序找最大最小
最优二叉树:特点、构造、应用——哈夫曼编码,节省空间
二叉树的性质、二叉树各种操作的实现是重点!
图
- 应用:
- 逻辑结构: 由顶点(Vertex)集合(即数据元素)及顶点间的边(Edge) 集合(即元素之间的关系)组成。
逻辑结构的特点:每个元素可以有多个前驱、多个后继;
图结构没有顺序存储方式,而一般的链式存储又存在一些缺点
-
实现 or 存储方式:
1 存储矩阵(邻接关系);
2 邻接表(很快找到哪些与自己直接相连)list of connected vertex 。表索引是结点的index, 指向一个单链表。这个单链表存储与这个结点邻接的所有结点的index
选择方式:边比较多,邻接矩阵比邻接表省空间。变数少,用邻接表节省空间。因为有指针,插入删除快一点
- 与树遍历的区别:
a 因为图中存在环,所以要再在遍历的时候,对每个顶点作一个访问标志visit[maxn]
b 还要考虑是不是连通图。如果不是连通图,那么有的结点不能通过出发点访问到。
A 图的深度优先(递归,栈):
从顶点开始访问。例如访问v0,判断是否是递归的终点。是则返回,如果不是则设置已访问标志;选择一个与v0邻接但未访问的顶点u,依次递归访问。如果没有相邻的,则返回
特点:
类似与树的前序遍历。沿着图的某一分支访问,直到它的末端,然后回溯
B图的广度遍历:BFS(利用了queue,进行迭代)
用一个queue来记录所有访问过的点。迭代过程:queue非空的情况下,取出首元素,将与首元素相邻接的节点都push进这个queue里。迭代直到queue变成空的
特点:类似树的层次遍历
树的应用:(4个)
1. 最小生成树:假设图是一个加权连通图,具有最小权值
之和的生成树称为最小代价生成树。
构造方法:
(采用的贪心策略,都是基于MST性质的:最小权值的边一定再最小生成树上)
- Kruskal(克鲁斯卡尔)算法——边归并.(排序,加边)
2. Prim(普里姆)算法 ——顶点归并
(维基百科)
2最短路径:
◆单源单点最短路径问题:转成下面两个解决
◆单源多点最短路径问题—Dijkstra(迪杰斯特拉)算法(贪心+摊销)
◆多源多点最短路径问题—Floyd(弗洛伊德)算法(动态规划)
Dijkstra(迪杰斯特拉):
特点:求从顶点到其余各顶点的最短路径,主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止
给定一个集合S来存放已经被访问过的结点,每次从没访问过的结点中,找到一个与起点距离最短的顶点U,访问并且加入到集合S里,然后让u作为中介点,优化起点与所有从u能到达的顶点v之间的最短距离。这样的操作执行N次,直到集合S,包含了所有的结点。
优化:找距离起点最短距离的没访问过的结点u的时候可以用堆优化。STL中的Priority_queue
复杂度: O(vlogv + e)
同Floyd的运用时的区别:
适用条件不同:Dijkstra算法对边的权值有负数时不适用。
多元多点时最好用Floyd
原理不同:Dijkstra基于贪心,Floyd基于动态规划
Floyd算法:
利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法
方法:枚举顶点k,使得以k为中介点时顶点i和顶点j 的d当前最短距离最短。那么就使用k作为顶点i和j的中介点,更新i j之间的距离。
3有向图在工程中的应用:
都是有向无环图。
AOV网:表示活动之间的优先关系。在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系。因为有些活动的开始是以前序活动的结束为条件的
关键路径的求解:工程中关键路径不变,减少关键路径上的时间就可以减少整个时间
4 AOE网络(关键路径):表示工程的进行过程。用结点代表事件,边代表活动,边权表示需要的时间
关键路径影响最终时间,优先考虑
AOE解决什么问题:a工程起始到结束需要的时间。B哪条路径的活动是影响整个工程进度的关键。
AOE的最长路径就是关键路径。关键路径上的活动就是关键活动
AOV 转成AOE:给定AOV图和每个边的权重(需要的时间),将AOV中的顶点变成两个顶点,一个表示这个活动的起点,一个表示终点。
搜索:
A 静态查找表
1 顺序查找
特点:简单,O(n),不断地找前驱(或后继)
2折半查找(Fibonacci查找、插值查找)
特点:对查找表要求多,O(log2n)
3静态二叉排序树查找
查找表的要求:二叉排序树
特点:类似折半,比较次数最大是树的深度
等概率时,深度为log2n二叉排序树最好;
不等概率时,概率高的应该靠近根
4 分块查找(索引顺序查找)
要求:每一块中的结点不必有序,但块与块之间必须"按块有序"
·先选取各块中的最大关键字构成一个索引表
·折半方法确定被查找元素可能所在的块;
·在块中采用顺序查找,确定元素是否存在
特点:要建立索引表;效率介于折半和顺序之间O(logn~n)
B 动态查找表(举例子:伸展树,B-树,红黑树,平衡二叉树)
伸展树,B-树,红黑树,平衡二叉树:
查找过程中对数据改变。平衡二叉树查找完就从不平衡变成平衡。
特点:
查找效率与构造出的二叉排序树的深度有关
1 AVL树:当且仅当每个结点的左右子树的高度至多相差为1,一棵二叉排序树是平衡的,
为O(log2n);
2 平衡二叉树上的查找
和静态二叉树查找的区别
3 Hash查找(不基于比较):
使通过哈希函数得到的n个数据元素的哈希地址尽可能均匀地分布在m个连续内存单元上,同时使计算过程尽可能简单以达到尽可能高的时间效率
Hash构造
把值用函数映射到地址,函数有这几个类型:
- 除法取余 hash行数将这个值映射到。。里
- 直接定址法
- 数字分析法
- 折叠法
- 平方取中
冲突问题:不同的x值通过函数映射得到的值相同,产生了重叠。
解决冲突的方法
(1) 开地址法: 同义词放在HASH表中的其他位置,比如当映射到的这个位置不是空的,那么就放到下一个最近的空的位置。
(2) 拉链法: 同义词放在HASH表之外的空间中,比如hash表里每个key对应的value是一个链,存放映射到这个位置的多个元素
Hash思想
通过值的公式直接映射,复杂度为O(1)
排序
描述一个算法
时间、空间复杂度、稳定性如何
内部分类:若整个分类过程不需要访问外存便能完成,则称为内部分类;
外部分类:若参加分类的记录数量很大,整个序列的分类过程不可能在内存中完成
内部分类方法分为下面几大类:
1、基于“插入”思想的分类方法
执行一趟是将一个元素“插入”到有序序列中 仍然有序,使有序部分扩大
直接插入分类
折半
SHELL分类
2、基于“交换”思想的分类方法
执行一趟是通过交换“逆序”元素使之到有序序列中,使有序部分扩大。
冒泡(标准交换)分类
奇偶(成对)交换分类
穿梭分类
快速分类
3基于“选择”思想的分类方法
选出当前无序部分的最小元素放到有序序列的后面,使有序部分扩大。
简单选择分类
堆分类
4、基于“归并”思想的分类方法:归并两个短的有序序列为一个有序序列,使有序部分扩大
2路归并分类
多路归并分类
5、其他思想的分类方法
计数分类
基数分类
空间复杂度:除了存储元素本身外,分类过程中需要的空间大小
时间复杂度:移动的次数
稳定性:排序前和排序后,相同大小的元素的位置顺序是否发生变化
步骤详解:
1 插入分类方法举例:
- 直接插入排序:——时间 O(n^2),空间O(1) 用到常数个辅助单元
第i次迭代的状态是L(1...i-1)前i-1个序列是有序的,后面L(i...n)是无序的。
那么就依次遍历到L(i)应该插入到L(1...i-1)中的位置K
将L(k...i-1)所有元素都倒着后移一个
再将L(i)赋值到L(k)
简述:第i次迭代的时候将第i个数据插入到前i-1个有序数列中
- 折半插入分类——比较次数O(nlogn);移动次数:O(n^2), 所以时间还是O(n^2)。空间复杂度是O(1)
在直接插入的基础上,用折半的思想找L(i)应该插入的位置k
然后依次后移给插入的位置腾空间,将元素放到这个位置里
2-路插入分类——移动有了变化 空间复杂度是O(n)
- SHELL排序(希尔排序)——提高效率的改进
复杂度:时间依赖于步长变化的函数:O(n^(1.3—2))。 空间O(1)。 排序方法不稳定
使用范围:基本插入:基本有序或者个数少的时候。Shell:线性表为顺序存储或者元素个数很多。
基本思想:将整个排序记录间隔的分割成若干子序列,进行直接插入排序,合并,然后再分组排序。等到整个序列中的记录基本有序时,再对全体记录进行一次直接插入排序。
具体步骤:设置一个步长dk,把每隔一个步长的数据放在一组,进行直接插入排序。然后逐趟让步长减少到1。这样就只有一组了,变得基本有序了,再进行直接插入排序。
2 交换分类方法
根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置
- 标准交换分类(冒泡分类)
基本思想: 按顺序两两交换,每次都把序列中最小的元素放到最终位置
具体步骤:假设序表长度为n, 从后往前两两比较元素的值,如果逆序,则交换他们,再往前挪动一个继续比。下一趟时,前一趟确定的最小元素就不再参与比较。待排序列就减少一个。这样最多n-1趟冒泡就能排号
时间效率:O(n^2)空间效率:O(1).
最好:若序列已经正序排列时,仅需进行一趟,n-1次比较没有交换;
最坏:若序列逆序排列时,需进行n-1趟:
- 快排
快速分类方法:基于分治法。
任意选取一个元素作为轴元素,一般选取序列的第1个元素。重新排列其余元素,凡小于轴元素的均移动至轴元素之前,反之,移动至该记录之后。这样轴元素就把原序列分为两个子序列。分别递归的对这两个子序列重复上述过程。
递归的思想
时间:取决于划分的是否对称,最坏情况时O(n2)
空间:最坏情况:O(n2) - 最好情况:O(nlogn)
不稳定
3 选择分类方法
每一趟(如第i趟)从后面n-i+1个无序元素中选择最小的元素作为有序子序列的第i个元素,直到第n-1趟做完。
- 简单选择
假设排序表时L(1...n),第i趟排序从L(i...n)中选择关键字最小的元素与L(i)交换,每一趟元素可以确定一个元素的最终位置。这样经过n-1趟。
空间O(1),时间O(n^2)
不稳定
- 堆分类方法:从堆中取最小的,时间复杂度O(nlogn)
(建立完全二叉树)
将无需序列调整为堆,选择堆顶元素就是最值,把这个元素加入到有序序列,然后对剩下的无需元素再调整为堆。进行n-1趟,就排好序了。
时间复杂度:O(nlogn) 空间:O(1). 有可能把同样大小的调整到前面,所以不稳定
归并排序:
将两个或两个以上的有序子序列“归并”为一个有序序列
- 开辟一个临时数组temp[]来辅助归并
- 使用三个索引ijk来在数组内进行追踪. k指向复制位置,ij指向要被合并的两个记录位置
- 合并时依次比较 array[i] 和 array[j] 的关键字,取关键字较小(或较大)的记录复制到 temp[p] 中,然后将被复制记录的指针 i 或 j 加 1,以及指向复制位置的指针 p 加 1。重复这一过程直至两个输入的子序列有一个已全部复制完毕(不妨称其为空),
- 此时将另一非空的子序列中剩余记录依次复制到 array 中即可。
时间复杂性:O(nlog2n)
空间复杂性:O(n),即创建的临时空间r2的大小;
稳定
归并和shell的区别:小组的划分不一样。
Shell排序是对插入排序的改进。
Shell排序先选取一定的间隔,相差一个间隔的元素视为一个组,在每组内进行插入排序。
然后选取更小的间隔(一般折半)进行插入排序,直至间隔为1。
将待排序序列R[0...n-1]看成是n个长度为1的有序序列,将相邻的有序表成对归并,得到n/2个长度为2的有序表;
将这些有序序列再次归并,得到n/4个长度为4的有序序列;如此反复进行下去,最后得到一个长度为n的有序序列。