数据结构总结
什么是数据结构
不是研究数据的计算的学科,而是研究数据之间关系的学科
数据结构相关术语
数据:大自然中的数字、符号图像、音频、视频都被看作数
数据段:某个事物其中的一个数据(类似结构的成员)
元素:某个事物的一个完整数据(类似结构体变量)
数据结构:由元素+元素关系而组成后的一个整体,叫数据结构(如栈、队列)
算法:数据结构所具备的功能
数据的逻辑结构
集合:元素之间除了同处一个整体,没有其他关系,类似数组。
表:元素之间存在一对一关系。顺序表(数组)、链式表
树:元素之间存在一对多关系。
图:元素之间存在多对多。
同属于数组可以是集合也可以是表,就看配合什么算法。
数据的物理结构/存储结构
顺序表
元素存储在一整块连接的内存中,根据前后位置来确定逻辑关系。
顺序表结构:元素存储在一整块连续的内存中。
优点:
访问速度快(随机)
可以用各种算法排序
可以使用二分查找
不容易产生内存碎片
缺点:
插入、删除元素麻烦
对内存的要求高,需要一整块连续的内存
操作有:创建、销毁、增删改查(按值、按下标两种版本)、插入、二分查找、冒泡排序、访问元素(根据下标)、遍历
其实就是数组,在顺序表里数据已经有序的情况下,使用二分查找效率会快些。
链表
链表的特点
每个元素随机存储在内存中,元素中有一块指针域(一个或多个指针),它们负责指向与元素有关系的其他元素,链表的每一个元素称之为节点。每一个节点还有其数据域,数据域可以是一个或多个变量也可以是一个结构体。
指针域有一个指针的是单向链表,两个的是双向链表。每一个节点在内存中位置是随机的不是连续的。由指针连起来的链式结构叫链表。链表设计有头和尾,方便添加。添加不可能失败,因为每添加一次都是创建一个节点,也就是申请一个节点的内存,理论上内存不够会导致添加失败,但是内存不够程序都跑不了了,所以说不可能添加失败。销毁,一直删除,直到链表为空。头为空,链表就是空。头删除,拷贝一个头节点,如果头尾相同就头尾置空,不然就是把头指向拷贝的下一个节点,再把拷贝free掉。尾删除类似。链表销毁需要把所有数据给清掉再free掉,顺序表不需要。头节点为空就代表链表为空
链表定义、优缺点以及操作
定义:由分布在内存不同位置的节点(元素),通过成员指针所连接起来的表结构,元素之间存在一对一关系。
优点:对内存要求不高,可以使用小块内存。插入以及删除方便。可以根据元素的数量,动态调整链表的长度,节约内存。可以无限的添加元素(学员、教师的添加)。
缺点:频繁的插入、删除节点可能会产生内在碎片。不能随机访问,只能从头节点挨个遍历,因此访问速度慢。
操作:创建、销毁、添加(节点,这是数据和链表为参数)、制造节点(这是数据为参数)、头删除(如果为空不能删)、尾删除(如果为空不能删)、链表空的判断、按位置修改、按值修改、按值查询(链表不能二分查找,只能顺序查找)、访问(按照下标访问)、长度、遍历
其他链表
不带头节点的链表
带头节点的链表
使用带头节点版本的链表就不需要二级指针了。使用该版本头添加变得简易
双向链表
一开始创建头节点的前指针和后指针都指向头节点,末尾节点把后指针指向头,首节点的前指针指向头节点
链表内核特殊版
功能受限的表结构
栈
是功能受限的表结构,先进后出FILO,只有一个端口进出元素。
常考问题:输入两个序列,第一个是入栈序列,判断第二个序列能否是出栈序列。
栈的应用:
1.栈内存的管理(函数调用)(函数最后调用的第一个返回的原因)
2.表达式的解析
栈相关操作:
创建、销毁、入栈、出栈、查看栈顶、输入两序列判断第二序列能否是第一序列的出栈顺序、判断栈满、判断栈空、查看栈中元素数量。
链式栈:
首先是栈,即特性同栈,但是结构是链式,所以元素都是有指向下一个元素的指针以及本元素的数据。
操作就是栈和链表的结合。因为是链表的栈,所以不存在栈满。和之前的栈比起来只是少了个栈满的函数。入栈是不可能失败的,所以入栈函数返回值为空而不是bool。出栈的返回值是布尔类型,即使在函数里返回的值不是true或false,只要返回值不为0,那就转换成true。由于栈有先进后出的特点,入栈的元素的next都是指向前一个元素而不是后一个元素。
队列
是功能受限的表结构,有两个端口进出数据,一个只能进一个只能出。先进先出FIFO,有一个队首指针和一个队尾指针,每加入一个元素队尾后移,每删除一个元素队首后移,当队首位置等于队尾位置说明队列为空,当(队尾+1)%长度==队首可以判断是否队满。元素数量计算。想象成一个圈有助于理解。出队队头后移,计算方式(队头+1)%长度。查看队尾,(队尾-1+长度)%长度
操作:创建、销毁、入队、出队、查看队首、查看队尾(查看元素的下标是队尾-1+len的和取余len)、元素个数(尾巴减去头加长度取余长度)、判断队满(元素个数等于长度)、判断队空。队列长度要多给一个,所以赋值len要+1,申请arr内存需要用TYPE*(len+1)。
如何用两个栈模拟一个队列
输入1234进入第一个栈
然后每出栈一个元素就把这个元素入栈到另一个栈里
出栈4321
栈2:4321
再用栈2出栈就又是
1234了,满足先进先出。
树型结构
元素之间存在一对多的数据结构,适合存储具有层次关系的数据模型:如文件树、组织关系、族谱。
树的相关术语
根节点:树的最顶层结点,一棵树最多只有一个根结点。
双亲结点、父节点:结点的上一层结点,一个节点只有唯一一个双亲结点。
子节点:节点的下一层节点,可以有若干个。
叶子节点:没有子节点的节点。
树的高度:树的层数。
树的密度:节点的数量。
树的种类
普通树:只有一个双节点,子节点数量任意
B树\AVL树:多路平衡查找树
多路:最多有M个子节点
平衡:所有字树高度差不超过1。
查找:所有节点是有序的
二叉树:一个结点最多两个节点
B树和B+树了解即可,普通树一般转化成二叉树研究
二叉树的种类
满二叉树:除了最后一层无任何子节点外,每一个结点都有两个子节点,每一层的数量是2(n-1),总节点数是2n-1
完全二叉树:除了最后一层外的所有层的结点数是:2^(n-1)。最后一层的结点按照从左到右的顺序排列。
有序二叉树:左子结点比跟结点小,右子结点比根节点大。所有节点都遵循这个规则。
线索二叉树:给普通二叉树节点增加线索,使其能够使用循环遍历,提高遍历速度。
哈夫曼树:带权重的二叉树
平衡二叉树:左右子树高度差不超过1的的二叉树。
红黑树:接近于平衡的二叉树,伪平衡二叉树
堆:
大根堆:双亲结点比左右子节点都要大。
小根堆:双亲结点比左右子节点小。
二叉树的性质
性质1:在二叉树的第 i 层上至多有2^(i-1)个结点
性质2:深度为k的二叉树至多有2^k-1个结点
性质3:对任何一颗二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。
n0+n1+n2 = n
n1+n2*2 = n-1
性质4:具有n个结点的完全二叉树的深度为[log(n)]+1([x]表示不大于x的最大整数)
性质5:如果对一棵有n个结点的完全二叉树(其深度为[log(n)]+1)的结点按层序编号(从第1层到第[log(n)]+1层,每层从左到右),对任一结点i(1<=i<=n)有:
(1).如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2]
(2).如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i
(3).如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1
二叉树的遍历
前序:根 左 右
中序:左 根 右
后序:左 右 根
以下面的二叉树为例:
A
/ \
B C
/ / \
D E F
/ \ \
G H J
/
I
前序遍历:第一个元素为根节点
A B D G H I C E J F
A B D G # # H I # # # # C E # J # # F # #
后序遍历:最后一个元素为根节点
G I H D B J E F C A
中序遍历:根节点左边的都是左子树,右边的都是右子树。
G D I H B A E J C F
所有子树都要遵循这个规则,要把每个节点当成一棵树看待
1.根据二叉树写出前中后遍历顺序
2.根据前序+中序 中序+后序 还原二叉树
层序:从上到下,从左到右。一层一层从左往右输出。
有序二叉树
左右子树与根结点的关系必须满足:左 < 根 < 右
没有键值相等的节点
当采用中序遍历时有序二叉树的遍历结果是从小到大的。
当它结点均匀、平衡,查找效率接近二分查找,
如果不均匀且呈单支状分布它查找的效率接近链表。
注意:创建有序二叉树的目的是为了能够借助它的二分查找特性,快速查找数据。
线索二叉树
当结点的右子树为空时,让它指向即将要访问的下一个结点,然后就可以使用循环来遍历这棵树,提高遍历的效率。
注:不用深入研究,顶多一道选择题
堆
堆是一种特殊的二叉树,它对结点的位置不做要求,所以一般以完全二叉树或满二叉来存储它。一般堆采用顺序的方式存储,故访问下标查找数据。
哈夫曼树
结点由数据+权重(数据出现的概率)组成,权重越高结点的层数越小。
创建过程:
1、把所有的数据和权重创建成结点(子树),也被称为创建森林。
2、然后根据权重排序,把权重最小的两棵子树合并形成新的子树,并成为它的左右子树。
3、再把新合并的子树与现在的森林进行排序,重复2上步骤,直到没有子树。
4、记录每个带数据的结点,从根据结点访问的路径,生成哈夫曼编码,由0和1组成,0表示走了左子树,1表示走了右子树。
5、在之后存储数据时,用它的哈夫曼编码共代替数据,达到压缩数据的目的。
6、根据哈夫曼编码,从哈夫曼树中找到对应数据的过程叫解压数据。
平衡二叉树/AVL树
平衡二叉树(AVL)首先是一棵有序二叉树,为了解决有序二叉树不均匀导致查询速度过低,而调整它左右平衡,使有序二叉树以最快的速度查询数据。
二叉树的链式存储
使用链式队列
每个节点(元素)由三个部分组成
值
左子树指针
右子树指针
优点:能清晰的表示树的结构,对内存要求低,节约内存
缺点:只能逐级访问,递归遍历,可能产生内存碎片。
二叉树的顺序存储
注意:需要把普通二叉树补全为完全二叉树。
[] [#][] [#][#][][] [][][][][][][]
用顺序存储的方式存储完全二叉树还能用来存满二叉树,使用以下公式遍历
i是当前节点的下表
前提:节点的下标从1开始排列
i/2 = 父节点下标
i*2 = 左子节点下标
i*2+1 = 右子节点下标
前提:节点的下标从0开始排列
(i+1)/2 = 父节点下标
(i+1)*2 = 左子节点下标
(i+1)*2+1 = 右子节点下标
优点:创建树方便,计算高度、密度速度快,使用的整块内存不易产生内存碎片。
缺点:对内存的要求高,当树比较稀疏时,对内存浪费极高,转换成完全二叉树时麻烦。
图
图与树、表的差别
表结构:数据元素之间存在线性关系,每个数据元素只可能有一个前驱和一个后继(一对一)。
树结构:数据元素之间存在层次关系,上一层的数据元素可以和下一层的多个数据元素存在关系(一对多)。
图结构:任意两个数据元素之间都可能存在关系,可以是多对多的关系。
图的相关术语
顶点:在图型结构中,数据元素被称为顶点。
弧:从顶点V1出发,可以到达顶点V2,这种关系被称为弧,用<V1,V2>表示,V1被称为弧尾,V2被称为弧头,这种图被称为有向图。
边:从顶点V1可以到达顶点V2,顶点V2可以到达顶点V1,这种关系被称为边,用(V1,V2),这种图被称为无向图。
注意:图型结构不讨论顶点到自身的边或弧。
如果用n表示顶点的数量,e表示边或弧的数量:
有向图的e的取值范围:[0,(n-1)*n],如果e的数量是(n-1)*n,这种图叫完全有向图。
无向图的e的取值范围:[0,(n-1)*n/2],如果e的数量是(n-1)*n/2,这种图叫完全无向图。
e < nlogn 这种图被称为稀疏图,反之被称为稠密图。
如果图的弧或边具有相关数据,这种与弧或边相关的数据叫权或权重,可看作是一个顶点到另一个顶点的消耗,带权的图称为网。
假设有两个图:G1 = (V1,{E1}) , G2 = (V2,{E2})
如果V1是V2的子集且E1是E2的子集,则称G1是G2的子图。
图的种类
无向图:
如果图中有一条边(V1,V2),则称V1,V2互为邻接点,即V1,V2相邻,或者说边(V1,V2)依附于V1,V2,又或者说V1,V2相关联。
与顶点V1相关联的顶点的数量,被称为V1的度。
有向图:
如果图中有一条弧<V1,V2>,则称顶点V1邻接到顶点V2,顶点V2邻接自顶点V1。
以顶点V1作为弧尾的弧的数量,被称为V1的出度。
以顶点V1作为弧头的弧的数量,被称为V1的入度。
有图G = (V,{E}),顶点V1到Vj的路径是一个顶点序列(V1,V2,...Vj)。
1、如果V1就是Vj,且边或弧的数量等于顶点数,该路径称为回路或环。
2、序列中的顶点不重复出现和路径称为简单路径。
3、除了每个顶点和最后一个顶点,其余顶点不重复出现的回路称为简单回路。
在无向图G中,如果V1到V2有路径,则称V1和V2是连通的,如果图中的任意两个顶点都是连通的,则称G是连通图。如果G1不是连通图,而G是它的子图,则称G是G1的连通分量,也叫极大连通子图。
在有向图G中,如果任意一对顶点Vi,Vj,从Vi到Vj和Vj到Vi都存在路径,则称G是强连通图。如果G1不是强连通图,而G是它的子图,则称G是G1的强连通分量,也叫极大强连通子图。
一个连通图的生成树是一个极小连通子图,它包含图中的全部顶点,但只有n-1条边,如果在一棵生成树上再加一条边,必定构成一个环。
一棵有n个顶点的生成树有且仅有n-1条边。
但有n-1条边的图不一定是生成树。
图的存储
邻接矩阵:由顶点表和边表组成
[A][B][C][D][E][F][G] 顶点表
[0][0][0][0][0][0][1] //[A] 边表
[0][0][0][0][0][0][0] //[B]
[1][0][0][0][0][0][0] //[C]
[0][0][0][0][0][0][0] //[D]
[0][0][0][0][0][0][0] //[E]
[0][0][0][0][0][0][0] //[F]
[1][0][0][0][0][0][0] //[G]
优点:
计算出度、入度方便。
缺点:
添加删除麻烦,如果是稀疏图,非常浪费存储空间。
邻接表:
顶点[数据][第一条边指针] 边结点 [顶点下标][下一条边指针]
[A]-> 1-> 3->
[B]-> 4-> 3->
[C]-> 3->
[D]-> 2-> 3->
[E]-> 3-> 0->
[F]-> 4->
是一种顺序+链式混合存储结构
优点:节约存储空间
缺点:计算入度不方便
十字链表:
顶点[数据][弧头第一条边指针][弧尾第一条边指针] 边结点 [入顶点下标][出顶点下标][弧头相同指针][弧尾相同指针]
优点:在邻接的基础进行了拓展,既能节约存储空间,也能方便计算入度。
图的遍历
深度优先:DFS
广度优先:BFS
遍历顺序不是唯一的,会受到顶点顺序和边的添加顺序的影响。
算法
算法的概念以及术语
算法:
广义:解决特定问题的方法。
狭义:数据结构具备的功能。
算法的特征:
有穷性、确切性、输入项、输出项、可行性
算法的评定:
正确性、时间复杂度、空间复杂度、可读性、健壮性
时间复杂度:
一个用于描述算法在执行时,随着输入参数数量的变化,而执次数发生变化的函数。
一般采用大O表示法:O(函数)
常见的时间杂度:
常数阶 O(1)
对数阶 O(logn)
线性阶 O(n)
线性对数阶 O(nlogn)
平方阶 O(n?)
指数阶 O(2^n)
空间复杂度:
一个用于描述算法在执行时,随着输入参数数量的变化,而需要存储空间发生变化的函数。
常见的空间复杂度:
O(1)、O(n)、O(2n)、O(n?)
查找算法
顺序查找:时间复杂度O(n),顺序查找适合于存储结构为顺序存储或链接存储的线性表。就是一个一个找,找到相同的返回。当待查表中的记录个数较少时,采用顺序查找。无序表短用顺序,对待查找数据没有要求。
二分查找:元素必须是有序的,如果是无序的则要先进行排序操作。最坏情况下,关键词比较次数为O(logn),且期望时间复杂度为O(log2n)。对于需要频繁执行插入或删除操作的数据集来说不建议使用。有序表短用二分。
插值查找:基于二分查找算法,可以提高查找效率。当然,差值查找也属于有序查找。对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。有序表长用插值。
分块查找:分块查找数据量较大的线性表时优越性更突出。无序表长用分块。
哈希查找:空间换效率,效率高,但是耗费空间。数据量大时或者追求效率使用。 首先要创建哈希表,创建哈希表的前提是对数据有一定了解。创建哈希表最重要的就是解决数据冲突问题,没有固定统一的方法,要根据数据的特性找到合适的方法。一旦哈希表创建完成它查找的时间复杂度接近O(1)。速度虽然很快,但有很大的局限性。
块查找:
当面对海量数据时,可以根据数据的特性,把数据分块、再分块降低数据规模,再使用以查找算法查找数据。
10大排序算法
两大类:
比较类排序:交换排序、插入排序、选择排序、归并排序
非比较类排序:计数排序、桶排序、基数排序
细分:
交换排序:冒泡排序、快速排序
插入排序:简单插入排序、希尔排序
选择排序:简单选择排序、堆排序
归并排序:二路归并排序、多路归并排序
复杂度:
排序方法 时间复杂度(均) 时间复杂度(max) 时间复杂度(min) 空间复杂度 稳定性
冒泡排序 O(n^2) O(n^2) O(n) O(1) 稳定
快速排序 O(nlog2n) O(n^2) O(nlog2n) O(nlog2n) 不稳定
插入排序 O(n^2) O(n^2) O(n) O(1) 稳定
希尔排序 O(nlog2n) O(n^2) O(n) O(1) 不稳定
选择排序 O(n^2) O(n^2) O(n^2) O(1) 不稳定
堆排序 O(nlog2n) O(nlog2n) O(nlog2n) O(1) 不稳定
归并排序 O(nlog2n) O(nlog2n) O(nlog2n) O(n) 稳定
计数排序 O(n+k) O(n+k) O(n+k) O(n+k) 稳定
桶排序 O(n+k) O(n^2) O(n) O(n+k) 稳定
基数排序 O(n*k) O(n*k) O(n*k) O(n+k) 稳定
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
各种排序算法的应用场合和优缺点:
冒泡排序:可以增加一个标记flag用于查看每次循环是否交换了元素,如果没有,就说明序列已经有序了,这样就不用进行完所有循环了。是个对顺序有序敏感的排序算法。缺点慢,优点稳定。
快速排序:速度比冒泡快和插入排序快,但是数量少的时候浪费空间,比较适合数据多些的时候使用。极快,但不稳定。
插入排序:比选择要快且稳定,插入基本代替选择,除非解决的问题本身不是很需要速度,那么选择排序就好,因为选择稳定。数据量大时不建议使用。数据的交换次数少,适合对有序的数据添加新的数据。
希尔排序:在插入排序的基础添加增量概念,提高了数据到达合适位置的速度。数量级在几万,我们结合Sedgewick增量序列使用希尔排序还是很划算的,快但不稳定。
选择排序:总是比较n*(n-1)/2次,重在时间可计算,稳定,缺点是比较次数多。如果待排序的数据字节较多,选择排序是一个不错的方案。
堆排序:对于较大的序列,将表现出优越的性能。把数据看作完全二叉树,然后把这个二叉树调整为大根堆,然后把堆顶的数据交换到末尾,再把二叉树的数量-1,再调整为大根堆,直到二叉树的数量为1,排序完成。
归并排序:很耗时,所以归并排序一般用于外排序。对数据的有序性不敏感。若数据节点数据量大,那将不适合。但可改造成索引操作,效果将非常出色。
计数排序:数值的区间不能太大,太散。在满足此条件的情况下,计数排序效率非常高,有N+K的时间复杂度
桶排序:将元素根据数值的区间分成多块区域,每个区域再调用其他的排序算法进行排序,比如:1000个数据,先根据大小划分到5个桶,每个桶装的都是差值200以内的元素,把每个桶的元素都排好序,所有元素就排好了。这种排序本质依靠其他排序算法,在数据量大的时候挺不错。
基数排序:不大了解
37万+

被折叠的 条评论
为什么被折叠?



