ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

数据结构学习(考研)

2021-07-22 21:33:37  阅读:179  来源: 互联网

标签:结点 数据结构 元素 存储 next 学习 二叉树 排序 考研


目录

数据结构学习笔记,若有任何问题欢迎大家评论指出

其他

1.存取包括存和取
2.第i个元素指下标为i-1的位置

开端

有关概念:一个数据元素由若干个数据项组成,数据对象是具有相同性质的的数据元素的集合
数据结构是相互存在一种或多种特定关系的数据元素的集合(数据之间的关系)
数据结构三要素:逻辑结构(集合,线性,树形,图或网) 存储结构(顺序,链式,索引,散列) 数 据运算(与或非加减乘除等等)
抽象数据类型包含逻辑结构和数据运算,可以定义一个完整的数据结构(实现则要用到存储结构)
算法具有的特性:确定性:即运算结果是唯一的
最坏情况下某语句的频度指某一语句出现的频率(执行次数最大的时候)

时间和空间复杂度
时间复杂度:T(n) 一般表示程序执行次数T和传入的参数n的关系
一般只关注n的最高阶数,即如果T(n)=6n3+10n2+100000000,一般我们都假设T(n)=O(n3
o(1)<o(log2n)<O(n)<o(nlog2n)<O(n2)<o(n3)<o(2n)<O(n!)<O(nn)
例:传入i=1;i<n i=2i 则i=1,2,4,8,16 所以2x=n,即x=log2n,所以时间复杂度为log2n

空间复杂度:S(n) 一般表示动态容器扩容问题
无论问题的规模怎么变,算法运行所需的内存空间都是固定的常量,则空间复杂度S(n)=O(1),这种情况我们成为“原地工作

线性表

线性表:相同数据类型的m个数据元素的有限序列,以下都是线性表

顺序表:指用顺序存储(数组)的方式实现的顺序表
位序:数据元素位序是从1开始的,即下标为0的数
单链表:即每一个数据项由数据和指向下一个数据的指针构成
如何在单链表的某一个节点(p)前插入元素呢?(单链表访问不到前一个节点)
前插操作:可以将新的节点(s)插入到p的后面(即p.next=s; s.next=p.next),然后将p的值(p中存储的数据)和s互换即可
后插操作(这指节点的插入操作),即将新的节点插在中间.删除指定节点用前插操作
单链表的逆置:使用头插法从尾部遍历原链表,然后将其插入到新的链表中
创建单链表的步骤:定义节点的存储方式(即一个结构,包含一个int和指向下一个链表的指针)
头插法和尾插法:都是用的后插操作
静态链表:使用连续的一大片内存存储链表,即使用数组实现链表,以单链表来说,每一个节点存储一个值和指向下一个节点的地址(数组的下标)
typedef struct{int value;}List [10]; List a; 即a是一个包含10个结构的结构数组

栈和队列

栈(stack):只能在栈头进行操作,先进后出,”堆“
实现栈:定义一个结构,包含一个存储栈中元素的静态数组和表示栈顶的指针(栈顶元素存储在数组中的下标)(顺序存储结构)
链式存储结构:定义只能在单链表的一端进行操作即为栈
—在栈中,给你n个数,有多少种出栈的顺序? 1/n+1 * Cn(上)2n(下)

队列

队列:只允许在一端进行插入,在另一端进行删除,先进先出,队列
静态队列:即用静态数组存储的队列,
实现:1.在结构中定义一个静态数组存储队列,定义一个头指针和尾指针指向数组的头和尾,(从数组头开始往下增加元素,从数组头开始删除)
2.定义初始化队列的函数,将top,end和数组中的所有数设置为0
3.添加元素的函数和删除元素的函数,添加一个元素,则end++,删除一个元素则top++
4.注意,循环队列操作end和top时取余
什么时候队列已满? 1.当end在top的上面时,top-end=1则队列已满,2.当end在top的下面时,end肯定为9,top肯定为0,则top-end=-9则队列已满(栈的判断一样)
双端队列:允许在两端进行删除和添加操作

队列和栈的应用

前缀表达式(波兰表达式):a+b写成+ab a+b-c写成-+abc或+a-bc(右优先)
后缀表达式(逆波兰):即把运算符放在后面,例a+b写成ab+ a+b-c写成ab+c-或abc-+(bc-当作一个数在右边与左边的a相加)
中缀表达式就是平时的写法
在把中缀改为后缀时应该按左优先原则(使运算结果唯一),即左边的运算符优先执行,这样得出的结果中的运算符的顺序和你先定的计算顺序一致
例:a/b+cd 运算顺序应该定义为/+ (不定义为*/+),则改为后缀为 ab/cd*+
-实现将后缀转换为中缀
从左向右扫描字符,如果是数则存储在栈中,如果是运算符则移出栈中的两个元素进行运算,将运算结果放回栈中(其结果当作一个数)
-实现将中缀转换为后缀
从左到右处理各个元素,直到末尾。可能遇到三种情况:
①遇到操作数。直接加入后缀表达式。
②遇到界限符。遇到“(”直接将(入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到弹出“(”为止。括号不需要加入后缀表达式中
③遇到运算符。如果栈中已经有运算符,则依次弹出优先级比遇到的运算符高或相等的所有运算符(例:栈有上-下 - ,遇到+,则弹出-,遇到/则弹出*),
然后把遇到的运算符入栈
按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。

矩阵

矩阵:对称矩阵的存储,对称矩阵即中斜轴对称的矩阵,存储轴和上下任一区的元素即可,
如果按行优先原则(即按行存储),则i>j区(即下区)第Ai,j个元素对应数组中的下标为i(i-1)/2+j-1(i-1行的总数+j-1=下标) 访问上区的元素只要把ij互换即可
带状矩阵(三对角矩阵):中斜轴除对角两个元素外每个元素的上下左右都有一个非0元素,其他元素都是0
稀疏矩阵:即非0元素少于为0的元素,存储方法1:用一个结构存储非0元素的值和地址(行列)
十字链表法,即用两个数组,一个数组存储行的非0元素的地址,一个数组存储列的非0元素的地址
十字链表是由一个横向和竖向的结构数组构成,结构数组中的每个元素都是一个十字(右域和下域)的单链表,单链表中的每一个元素(结构,参数为行列的值和本身的值)(详见111)
(考研不考)广义表:扩展线性链表
广义表就是广义的线性表,即表中的每个元素都可以是一个广义表,广义表中的单个元素称为原子(非子表)
例:表a=(1,3(4,1),(1,(4,6))),表的深度指子表括号数+1,即a的深度为3
广义表的每个结构由三个元素组成(指向头和尾的指针,和一个标志位(是为原子还是子表)),
子表中的每个元素用含三个元素的结构构成(值和指向下一个元素的指针和(标志位?))(见222)

子串:串中任意个连续的字符串组成的子序列(可以为0)
串的位置:第一个字符的位置(从1开始)
每个英文占一个字节
KMP算法,解决:在主串中寻找子串时,传入一个大小为n的子串,在主串中寻找是否有和子串相同的序列
一般的方法是依次比较,即从主串的左边开始寻找和子串第一个元素相等的数,找到了再比较第二个元素是否相等,如果不相等则比较第二个元素是否和子串第一个元素相等,相等就比较第二个元素
一般的方法的缺点是当出现连续比较相等但最后一个值不相等时会浪费时间,比如主串为1111113(7个数) 子串为113(3个数),则要比较(7-3+1)*3次
KMP算法是当匹配不相等时,程序应该回到哪s开始,先将这个算出来放入一个数组中,然后当比较不相等时直接回到应该回到的位置(能节约时间的位置)
以子串为113为例:当匹配到第三个元素不相等时,应该判断第三个元素是否为1,如果为1则直接回退1重新匹配(或判断下一个元素是否为3,是则返回真,否则判断是否为1,如果为1则继续往后一位判断),
如果不为1.则重新从下一个元素开始匹配(next[3]=2)
当匹配到第二个元素不相等时,则重新从下一个元素开始匹配(next[2]=1), 当匹配到第一个元素不相等时,则重新从下一个元素开始匹配(next[1]=0),
next[j]=s s=前后缀相等且最长的数量+1 其他情况s=1 (前后缀指 子串第j位数之前的字符串,从前往后不包括最后一个数叫前缀,从前往后不包括第一个数叫后缀)
next[1]=0 next[2]=1(数组的第一位数恒等于0,第二位等于1),
例:23325 next[1]=0 next[2]=1 next[3]=1 next[4]=1 next[5]=1
23235 next[1]=0 next[2]=1 next[3]=1 next[4]=2 next[5]=3
22225 next[1]=0 next[2]=1 next[3]=2 next[4]=3 next[5]=4
优化版的KMP算法:就是对得到的next数组进行优化为nextval[]数组
如果第n个元素匹配失败,且前面有和这个数相等的数,则这个数的下标等于前个数的下标
例:23325优化为next[1]=0 next[2]=1 next[3]=1 next[4]=0 next[5]=1
23235 next[1]=0 next[2]=1 next[3]=0 next[4]=1 next[5]=3
22225 next[1]=0 next[2]=0 next[3]=0 next[4]=0 next[5]=4

树:—森林:大于等于0个树的集合
度:树中一个结点的子结点的个数称为改结点的度,树中最大的度为该树的度
度=0为叶子结点,度大于0为分支结点
前驱结点指按某种遍历方法,先遍历b再遍历a,则b为a的前驱结点,后继结点
二叉树:有序树
满二叉树,他的结点树为2的n次方-1(n为树的深度)
编号为i的结点(左子树比右子树小),其父结点的编号为[i/2]向左取整,左孩子为2i,右孩子为2i+1
完全二叉树:只有当一颗树按满二叉树的编号方法编号时,他的每个对应的结点的号码与满二叉树相等时,则这个树为完全二叉树
二叉排序树:左小
平衡二叉树:每一个节点下的左右子树的深度差不超过1
二叉树实现
1.用顺序存储结构存储二叉树 适用于完全二叉树,存储非完全二叉树会浪费很多空间(不建议使用)
一个完全二叉树的左结点为2i,右结点为2i+1,(从上-下,左-右编号),按编号存储在对应数组的下标中
而非完全二叉树只要将非完全二叉树补成完全二叉树即可,空位存储0
2.用链式存储结构(建议使用)
-遍历二叉树的方法:假设我们定义一个规则(先遍历左子树,再遍历右子树,即先把根的左边遍历完再遍历右边)则有三种遍历方法
1.左右根 2.根左右 3.左根右 所谓先序遍历指的是先遍历根
中序遍历:先遍历最左端的结点,然后依照左,根,右一直遍历到根结点,然后遍历根结点右边最左端的结点,依照左,根,右一直遍历到根则遍历结束
1.一般使用递归实现
2.非递归则使用栈实现,
初始时将根结点的所有左侧结点放入栈中,
然后出栈一次
将出栈的那个结点的右子节点放入栈(没有右子节点则继续出栈)
将右结点的所有左侧结点进栈
直到栈空为止
3.层次遍历,即一层一层的遍历,使用队列实现
将根入队,然后出队,并将根的左,右子结点入队(先左后右)
然后出队,将出队的结点的左右子结点入对
重复操作至队空为止
由遍历方法构造二叉树
先(或后)序遍历加中序遍历即可构造确定的二叉树(即顺序确定)(即先根后先左或先右)
–线索二叉树:若无左子树,则将左指针指向其前驱结点,若无右子树,则将右指针指向其后驱结点
一般用中序线索二叉树(即通过中序遍历得到的前驱和后继)
实现:增加两个变量用于表示这个结点的左右子结点是指向的结点还是前驱后继
树的存储方法
1.孩子存储法:即存储每一个结点的孩子
用一个二维数组存储每一个结点,和(链表)这个结点的所有孩子结点
2.左孩子右兄弟存储法:(可用于树于二叉树的转换)
即用链表存储,每一个结点包含三个元素(左指向左子结点,右指向右兄弟结点,还有存储自己的数据)
3.双亲存储法,
即用两个结构,一个结构存储结点的值和他的父节点的下标(根节点的下标用-1表示),另一个结构存储一个数组和表示该数组中存储数据的数据的个数
–树,二叉树和森林的互换
树-二叉树:用左孩子右兄弟存储法即可
森林与二叉树:将每一棵树转为二叉树,然后每棵二叉树的根依次作为上一棵二叉树的右子树
森林和二叉树的遍历方法相同,遍历次序相同

树的应用

并查集:给一个集合或者多个集合,将其用双亲存储法存储
即每个集合表示一颗树,令集合中第一个元素为根,则其他元素就是它的子结点
如果为一个集合我们可以认为他是一个数组或者里面所有值都是没有子结点的树

二叉排序树
二叉排序树中没有相同值的结点
创建二叉排序树:利用插入方法创建,
二叉树的删除:若被删除的结点有左右子树,则让其中序遍历的后继代替其,
平衡二叉树(AVL树):左子树的高度-右子树的高度<=1
创建平衡二叉树:先创建二叉树,然后再将二叉树转为平衡二叉树
二叉树转为平衡二叉树:变换的核心在于某一侧子树不变
右单旋转法:一颗原本是平衡的二叉树因为在c的左子树a左侧插入结点导致不平衡
则将a与它的父结点b互换,并保持a的左子树依然是原来的a的左子树,而b的右子树依然是原来的b的左子树
然后a的右子树为b,b的左子树为a原来的右子树
左单旋转法:一颗原本是平衡的二叉树因为在c的左子树a右侧插入结点导致不平衡
先将a的右子树b与a互换,然后将c与b互换
其他方法类似,
哈夫曼树:为带权树,且带权路径长度最小
带权路径长度:所有边的权长之和,如果叶结点的边长为a,且从根结点到叶子结点经过了n条边(n等于a所在子树的深度)则带权路径长度为a*n
构造哈夫曼树:将含有n个结点的树作为森林f,每棵树只含有一个结点
生成一个新的结点(它的权值为左右子结点的权值和),从f中取出权值最小的两颗树作为新结点的左右子结点(无左右顺序)(所以哈夫曼树是不唯一的)然后将新结点放回f中
重复至f中只有一颗树为止
哈夫曼树的应用:用哈夫曼树生成可变长度编码
可变长度编码:即对于一个字符串序列,我们可以用二进制表示,每一个特定的字符都由特定的二进制表示,
所有字符都由不同位的二进制数表示,但没有一个字符是另一个字符的前缀(比如a为001,则不能出现一个字符为0或00)
固定长度编码指每一个特定的字符都由固定的长度的二进制表示,比如所有字符都由三位二进制数表示
假如在一串字符序列中a出现3次,b出现4次,c出现2次
则我们可以认为出现的次数为字符的权值,则构建一个森林有abc三颗树,每棵树都只有一个结点,然后用这个森林构建哈夫曼树
然后我们将构建好的哈夫曼树左的边设为0,右的边设为1,则叶子结点的编码为从上而下对边上的0/1排序

—图:不能为空(图中的结点间不一定要有连线,但不能有线而没有结点) 图由顶点集(结点的总数)和边集组成,(v,e)表示一个图,v为顶点集e为边集
有向图无向图
简单图和多重图 多重图指两结点间有两条方向不同的边或图中存在结点指向自身的边,即图中存在(a,b),(b,a),(a,a)
完全图:即任意两个结点间都存在边
有向完全图:即任意两个结点间都存在方向相反的边(即任意(a,b)在图中都存在(b,a))
子图:若a图为b的子图,则b图包含a图的所有边,a可以等于b (!)
连通指两结点间有路径存在(从a可以到达b)(单一个结点也是连通图) 强连通:指a与b互相连通,即可从a到b也可从b到a
连通分量:一个图g的连通子图g1,若在g中找不到一个包含g1且不同于g1的连通子图,则称g1为g的连通分量(即g1为最大的连通子图)(强连通分量类似)
生成树:包含全部结点的一个极小连通子图(边最少为极小连通子图)
生成森林:非连通图的所有连通分量的生成树组成生成森林
顶点的度:一结点与其他结点连线的条数
出度是指在有向图中,以某结点为起点的边的条数(即结点a指向其他结点的边的条数) 入度是指在有向图中,以某结点为终点的边的条数
带权的图称为网
回路指从a到a的路径,例:a-b-a

图的存储

1.邻接矩阵法:对于稀疏图会浪费许多空间
定义一个一维数组存储所有结点
然后根据图生成一个邻接矩阵(二维数组),即从一维数组的第一个元素(结点)开始,到其他所有结点,有边则为1,无边则为0
依次将这些0和1存储在二维数据中(a[0]中),对于简单图来说,结点到结点自身为0,对于无向图来说,a-b和b-a都是1(所以为对称矩阵(左上,右下斜线对称),可以使用压缩矩阵的方式存储)
所谓以行主序表示为在矩阵中第i行第j列的数表示为(i,j),以列主序则为(j,i)
2.邻接表法:适用于稀疏图
用一个数组存储每个结点(每个结点的头指针)
为每个结点创建一个链表(单链表)用于存储从这个结点出发的边(对于结点可以用数组下标表示)
3.十字链表:专用于存储有向图的链式存储结构
定义一个数组,数组每个元素存储一个结构,结构中定义三个变量,一个是数据,一个是出边的头指针(从结点出发的边),一个是入边的头指针
(出边的头指针存储从结点出发的所有的边,然后其他的变量的作用都是用于区分的查找这些边是从这个结点出发还是以这个结点为终点)
对于每个头指针定义一个单链表,链表中每个结点存储5个变量(其中一个存储权值),分别是边尾(tailvex)的结点和边头(headvex)的结点(数组中的下标,边尾指从a指向b的边中的a)(int)
头边(headline)和尾边(tailline),头边指指向终点相同的下一条边 尾边是指指向起点相同的下一条边(即表示两条边都有共同的终点或起点)(struct)
单链表中的每一个结点都是一条边,(当一个结点有三条边以他为终点时应该如何指?)
4.邻接多重表:专用于存储无向图的链式存储结构
定义一个数组,数组中每个元素存储两个变量,一个是数据,一个是该结点指向的边的头指针
定义一个单链表,链表存储四个变量(还可以加标记(该边是否被访问)和该边的权值)四个变量分别是这条边的两个结点在数组中的下标(int)
一个存储一个结点相关的下一条边的地址,一个存储另一个结点相关的下一条边的地址(下一条边指含同一结点的不同的边)(struct)
(十字链表和邻接多重表的一个点就是单链表中的每一个结点表示一条边,结点的个数就是图中边的条数,没有表示的边都是靠指针指)

图的遍历:

广度优先遍历:和树的层次遍历差不多,只是多了一个变量用于标记该结点是否已经被遍历过了
广度优先遍历的应用:求无权图单源最短路径:从该结点出发,到达其他所有结点的最短路径(边数最少)
广度优先遍历会生成一颗树或者森林,邻接矩阵法的生成树唯一,表不唯一
—深度优先遍历:与树的先序遍历差不多,只是多了一个变量用于标记该结点是否已经被遍历过了
邻接矩阵法遍历的顺序唯一,邻接表不唯一
深度优先遍历会生成一颗树或者森林,邻接矩阵法的生成树唯一,邻接不唯一

图的应用:

最小生成树:带权无向连通图包含全部顶点的一个最小连通子图(边数最少)(且权值和最小)
一个图的最小生成树不一定唯一(所有边的权值都不同则唯一或图的边数为结点数-1)
创建最小生成树一:prim算法
1.创建一颗空树,
2.将图中任意一结点(a)加入树中,
3.从图中找一条包含结点a且权值最小的边(ae),将结点e加入树中并将边ae加入树中
4.从图中找一个除树中已有结点之外的结点,并且该结点与图中结点连接的边的权值最小,该结点加入树中,然后重复3,直到树中包含图中的所有结点
创建最小生成树二:借助并查集,适用于稀疏图
1.创建一颗树,包含图中所有结点,但不包含边
2.将图中的边按权值中小到大依次加入树中,如果构成回路(树与图的区别在于图会构成回路,树不会,即有三个结点互相连通)则那条边舍弃

找最短路径:带权有向图中一个结点到另一个结点所经过的边的权值和最小
实现方法一:不适合有权值为负数的图,最终会得到从第一个结点到其他所有结点的最短路径,包括序列
1.创建三个数组,dist[](表示第一个结点到其他所有结点的最短路径),s[](标识图中是否还有结点未被计算),path[](表示每个最短路径的序列,如ae的序列可能为abe或ae),
他们的大小为图中结点的数量,以下标表示图中的结点
2.初始化:dist[]存储第一个结点(a)到其他所有结点的权值,没有路径则设置为无穷大,s[]表示该结点是否已经被计算,1为被计算,0为未被计算,初始化将第一个结点设置1
path[]表示第一个结点能到达哪些结点,能到达的则将该下标下的值设置为该结点的下标(每个下标表示一个结点,如下标0表示第一个结点,将下标为三的值设置为0则表示0到3有一条边)
3.循环执行,直到s[]中所有元素的值为1为止
从dist[]中找出最小的值,这个值表示a到达的权值最小的边(ae),则将e这个结点作为被计算的对象,假如e可以到达b和c,则比较ab,ac和ae+eb,ae+ec的值的大小(无穷大为最大)
如果左边大,则用小的值代替大的值,如果左边小则不做修改,然后将s[]中e的下标修改为1,将path[]中b和c的下标修改为e的下标,
做完这些一轮循环执行完成,继续从dist[]中找出最小的值继续循环
4.从path[]中获得最端路径的序列,因为path中存储的是以该结点为终点的结点的下标,所以我们只要从下标找
比如从第一个结点到第三个结点的最短路径,如果path中下标3存储的值为0,则序列为03
如果值为2,则找2的下标的值,如果2的下标为0则序列为023
实现方法二:计算了所有结点间的最短路径
1.创建一个矩阵:大小为图中结点的总数(方形),每一列代表每一个结点,每一行存储这个结点到其他结点的权值,
比如第2行第3列存储第二个结点到第三个结点的权值(有向,无边为无穷大,到自身为0),
2.以第一个a结点为中介点,计算以a为中介点的最短路径(例:b-a-e),如果这个值小于原值(b-e),则替代原值
3.以a相邻的结点作为第二个结点b和a一起作为中介点,计算以a,b为中介点的最短路径(例:f-b-a-e),如果这个值小于原值,则替代原值
4.继续以相邻的下一个结点为中介点,循环,直到所有结点

拓扑排序:对DAG图排序,使其结点间有自己想要的先后顺序(AOV网),结果不一定唯一
DAG图:有向无环图
AOV网:用DAG图表示一个工程,每个结点表示一项活动,有向边表示活动的先后关系,则称这种DAG图为AOV网
拓扑排序的实现:1.从DAG图中选择一条没有入边(没有一条边的终点是这个结点)的结点
2.删除这个结点和包含这个结点的所有边,
3.然后继续在图中选择一条没有入边的结点,重复12,直到图为空或者没有一条没有入边的结点
若邻接矩阵为三角矩阵则存在拓扑排序,否则不一定存在拓扑排序

关键路径:从源点到汇点权值和最大的路径称为关键路径,关键路径上执行的活动叫关键活动
AOE网:和AOV网类似,在有向带权图中,每一个结点代表一个事件,有向边代表事件执行的的顺序(活动),边上的权值代表完成该过程的开销,我们称这种网为AOE网
特性:有且仅有一个结点叫做源点,他只有出边 有且仅有一个结点叫做汇点,他只有入边
找出关键路径需要计算以下几步
-事件最早发生时间:假设执行a这个事件有两条路径(b-c-a,b-d-a),则计算c和d的最早发生时间(c1和d1),然后计算c1+c-a(ca边的权值)和d1+da的值,其最大值就是a这个事件的最早发生时间
而如果b为源点,则c1的值为bc,d1的值为bd
事件最迟发生时间:计算方式与最早发生时间相反,从汇点开始,假设a这个事件有两条路径(b-c-a,b-d-a,b是汇点,这里的路劲是逆路径),则计算c和d的最迟发生时间(c1和d1)
然后计算c1-c-a(ca边的权值)和d1-da的值,其最小值就是a这个事件的最早发生时间
而如果b为汇点,则b的最迟发生时间等于最早发生时间
-活动最早发生时间,活动最早发生时间等于这条边的出发点的事件的最早发生时间
活动最迟发生时间等于这条边的终点的事件的最迟发生时间-这条边的权值
-活动的差额:活动最迟发生时间-活动最早发生时间,等于0则为关键活动(关键路径)
当关键路径不唯一时,选包含结点最多的关键路径即可

查找算法

概念:查找表:被查找的数据集合(从查找表中查找内容)
查找平均长度,在查找到一个元素所需要查找多少个数据(这个值叫查找长度)才能查找到这个元素,则查找平均长度为这个查找表中n个元素的查找长度之和/n
查找方法:
-顺序查找:即一个一个的比较
判定树:判定查找失败的树,将一个结点的另一个结点作为失败结点,查找到失败结点则认为是查找失败(不存在查找表中的值)
-折半查找:又称二分查找,仅适用于有序顺序表
首先将关键字与中间元素比较,若中间元素等于关键字,则返回查找成功,若大于关键字,在升序序列中则将关键字与查找表前半部分的中间的值继续比较
-分块查找:结合顺序查找和折半查找
即将查找表有序的分为若干块,块内元素是无序的,而各个块间是有序的
即从头开始对查找表进行分块,可以按升序或降序进行分块,即块是升序或降序的
-B-树:多路平衡查找树,树中每个结点的最大子结点数为该树的阶数,即一个结点他最多有n个分支,则这棵树为n阶B树
n阶B树每个结点最多存储n-1个元素,每个结点有这个结点存储的变量的个数+1个子树
定义B树每个结点左边的值都小于父结点,右边的值都大于父结点,中间的值是父结点中间的值(因为一个结点可能包含2个以上的值,基本思想是左小右大)
插入和删除和平衡二叉树类似,基本思想是要保持n阶B树的特性(一个结点他最多有n个分支,每个结点最多存储n-1个元素)
-B+树:B+树和B-树的不同在于结点存储的每个变量都会有一个分支(子结点)
叶节点包含; 树中的所有结点,且叶节点是按顺序从左到右分布的
散列表:即给一个查找表,将查找表中的数据用散列函数计算出其地址(下标)然后存储在顺序结构(数组)中,这个顺序结构叫散列表
散列函数:通过函数用数据得到它的存储地址(/下标),这样的数据和其地址的关系的函数叫散列函数
例:对于查找表{1,5,6},定义散列函数地址=a%3,则定义的散列表为{6,1,5}
解决冲突的方法一:开放定址法:不能随便删除某一元素
线性探查法:即如果出现冲突,即向下查找一个空位存储即可,查找时也是,如果按照散列函数找到地址,但这个地址存储的却不是目标数,则向下查找即可
解决冲突的方法二:数组中出现冲突用单链表解决

排序算法

排序:
内部排序:数组小于或等于内存时,在内存中进行的排序
外部排序:数组大于内存时的排序

内部排序

直接插入排序:稳定的,时间复杂度为n方
每一个将被排序的序列都可以被分为三个部分,前一部分是有序序列(一般第一个元素组成有序序列),第二部分是待插入的值,第三部分是无序序列(待排序的值的序列)
1.将被排序序列存储在数组中(sort[]),下标为0的空间空出来
2,定义一个值记录待插入的值的下标(i),定义第二个变量记录有序序列中要与待插入的值比较的下标(j),这个值初始为有序序列中最后一个值(j=i-1)
3.假如是升序排序,将待插入的值复制到A[0]位置,然后将待插入的值(A[0])与有序序列最后一个值比较,如果i大,则不变,如果i小,则让i与j互换,然后j-1
void sort(A[],int n){//升序排序
int i , j; //i记录待插入的值的下标,当最后一个值已经插入时,结束整个循环,j记录有序序列中要与待插入的值比较的下标,这个值初始为有序序列中最后一个值
for(i=2 ;i<=n;i++){
A[O]=A[i]; //将待插入的值复制到A[0]位置,利用A[0]作比较
for(j=i-1;A[j] .key>A[O] .key;j–){//当有序序列最后一个值大于待插入的值时执行循环
A[j+1]=A[j]; //使后一个值等于前一个值
} //每次j–;将待插入的值与有序序列中每一个元素进行比较,大的后移(移出来的空放待插入的值),直到遇到小的或循环到与A[0]比较,
A[j+1]=A[0]; //当j=0时,上一个循环退出了,没有执行A[j+1]=A[j];语句,这里补上(A[1]=A[0])
}
}
折半插入排序:折半查找和直接插入排序的结合,时间复杂度为n方
即将有序序列的中值与待插入值排序,然后继续和前一半或者后一半的中值比较
找出待插入的值应该插入的位置,然后移动即可
希尔排序:不稳定,最坏n方,将一个待排序的序列分为n组(一般是分为该序列的总长度除以2取下界),然后对每一组进行直接插入排序
然后继续将该序列分为k组(n/2取下届=k),然后对每一组进行直接插入排序,直到分为1组时进行直接插入排序,这样就对序列完成了排序操作
void shellsort(ElemType A[] , int n){
for(int dk=n/2; dk>=1; dk=dk/2){ //每次分为2/dk取下界个小组
for(inf i=dk+1; i<=n; ++i){ //下面所有代码执行一次分组后,对所有组进行直接插入排序(这个直接插入排序是所有组同时进行的,先i+1,然后判断i<n)
if(A[i].key<A[i-dk] .key) { //下面代码执行一组内的一次直接插入排序
A[O]=A[i];
for(int j=i-dk; j>0&&A[O].key<A[j] .key; j-=dk){//每次循环ij为不同小组内的的两个元素,并进行比较和互换
A[j+dk]=A[j];
}
A[j+dk]=A[0];
}}}}
冒泡排序:稳定的,n方
对待排序序列从第一个元素开始,两两比较,将较大的值放后面,直到比较到最后一个元素,这样会将序列中最大的值移到尾部(一次冒泡),
这样对原序列中除最后一个值外再进行冒泡

选择排序:(不稳定)选择排序和冒泡排序相似,选择排序每次从序列中找取最小的数放在首部

快速排序:(不稳定),最好平均时间复杂度为nlog2n,最好平均空间复杂度为log2n 最坏空间时间为 n方和n
选一个值作为基准值,(默认找第一个值),一次快速排序会将一个值放在这个序列排序好后应该放的位置(且这个值前部分的数都小于这个值,后部分都大)
1.拷贝第一个数(即将第一个数的位置空出来),然后从右向左(定义一个标记变量end)找比第一个数小的值放在第一个数的位置(则右边空出一个位置),
然后从左向右(定义一个标记变量top)找比第一个数大的数放在右边(放在空位)
(当top大于end时结束一次排序),如此递归,这样便以第一个数为中心,右边的都比第一个数大,左边的都比第一个数小
2.然后对前后两个分区的数重复第一步
快速排序适用于数据不大的情况,因为适用递归会占用空间

堆排序:(不稳定),时间复杂度为nlog2n
小根堆:第i个元素小于第2i和第2i+1个元素的序列,大根堆反之,(1<=i<=n/2)
排序过程中将小大根堆看作一颗完全二叉树
将一个待排序序列初始化为大根堆:
先将所有元素放入一个完全二叉树中,然后从第n/2取下界开始往前调整
如果该结点的所有子结点都小于该结点,则不用调整,
1若有大于该结点的子结点,则取最大的子结点与该结点互换
然后如果移动位置的子结点是叶子结点则结束调整,如果不是,则对该结点做上一步调整(1)
大根堆的堆顶元素就是该序列中最大的值,然后将该值与树中最后一个值互换,这样我们就认为该值已经输出了
然后继续调整互换后的树,并将根结点与最后一个值互换(不包括已经输出的值)(即将树中最后一个值减去再做互换)
插入:插入到树的尾端,然后向上调整即可

归并排序:时间复杂度为nlog2n 稳定 空间复杂度为n(实现)
将一个待排序的序列(总长度为n)非为n个块,然后将每两个元素组成一个有序的块,然后继续使每两个块合为一个有序的块,直到只剩一个块
2路归并排序,指每次使两个块归并
实现:与上述有所不同,执行顺序不同

基数排序:稳定,时间复杂度为r(n+q) 空间复杂度为n
对于一组待排序序列,有n个值,序列中最大的值的长度为r(r=3,则最大值为百位数),每一个数的取值范围为q(q=10,则取值范围可能为0~9)
排序过程一共循环r次,需要q个辅助队列
1.定义q个队列
2.将每一个待排序的值按个位数的大小放入对应队列中,即个位数为0的放入第0个队列中,
3.从第0个队列开始依次从队列中取值,组成一个新的序列(新序列按个位数的大小排序)
4.将每一个待排序的值按十位数的大小放入对应队列中。。。(重复23)

外部排序

总时间=内部排序时间+外存信息读写时间+内部归并时间
在待排序序列的大小大于内部存储空间的大小时,应该使用外部排序,外部排序基于归并排序实现
1.假设内部空间大小为n,我们将待排序序列分为a个块,每一个块的大小为n,将每一个块排序成有序序列
2.将内部空间分为三个部分,前两个空间用于存放两个块中的前几个元素,后面一个块用于存放输出结果
3.比较前两个块中最小的值放入输出空间中,当输出空间满的时候输出结果,当前两个块中某个块中的所有元素都已存在与结果中时释放该空间,并将该块剩余的元素添加到块中继续比较
4.重复循环

外部排序二:败者树
1.假设内部空间大小为n,我们将待排序序列分为a个块,每一个块的大小为n,将每一个块排序成有序序列
2.定义一个完全二叉树,使所有叶子结点存储待比较的值,每一个叶子结点分别存储一个块中的第一个值(即待比较的值)(有a个叶子结点)
叶子结点的父结点存储它的两个子结点中最大值的块的编号(即每个块都有编号)(在递增排序中,较小的值为胜利者,较大的为失败者)
每个结点存储左右子树中的失败者,而胜利者则会继续上传继续比较。(即每个结点会代表胜利者继续和它的兄弟结点做比较,将胜利者上传并记录失败者)
3.到根节点后会输出该树中最小的值,并传入最小的值所在的块的第二个值继续比较

外部排序三:置换-选择排序
得到n个有序的块
设有待排序序列a,工作区b(内存,大小为3),输出文件c
1.从a中拿出三个值放入b中(从头开始拿),从b中取最小值(q)放入c中
2.然后从a中拿一个值放入b中(保持b中有三个值),取b中最小且比q大的值放入c中
3.循环2,直到a为空或者b中所有值小于q
三个循环结束后假如a,b都不为空则重复12,直到为空,这样的结果就是生成了n个有序的块

外部排序三:最佳归并树
置换-选择排序所生成的块作为一个结点生成哈夫曼树(缺少结点用0补)

标签:结点,数据结构,元素,存储,next,学习,二叉树,排序,考研
来源: https://blog.csdn.net/Silentambition/article/details/119006991

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有