数算笔试复习笔记(随手写的,没有图片)

数据结构与算法复习笔记

Chapter1 绪论

数据结构

问题->模型->抽象数据结构->数据结构:抽象数据类型的物理实现

什么是数据结构?数据、数据元素、数据对象

数据结构的三要素:逻辑结构,存储结构,操作

四种逻辑结构:线性结构,树形结构,复杂结构,集合

四种存储结构:顺序表示,链接表示,散列表示,索引表示

算法

算法:由有穷规则构成的为解决某一问题的运算序列

算法的五个性质:输入,输出,有穷性,确定性,可行性,

算法分析:时间复杂度,空间复杂度

Chapter2 线性表

线性表

逻辑结构:除了头尾以外,每个元素仅有一个前驱和一个后继;第一个元素记为k_0,最后一个元素记为k_(n-1)

存储结构

(1)顺序表示(顺序表):存储在内存的相邻区域之中;loc(k_i)=loc(k_0)+i*c;使用元素的物理位置的相邻关系表示逻辑上的相邻关系,要预先开辟MAXNUM大小的存储空间

操作:插入/删除,复杂度为O(n)

(2)链接表示(链表):用不连续的存储单元存储线性表;每个节点要额外存储后继结点的信息:info+link->next

头指针:指向第一个结点的指针

分类

(1)单链表:有/无头结点,一般来说带头结点更好用

操作:插入/删除,复杂度为O(1)(如果已经给定了要插入的位置的指针)

(2)循环链表:尾节点指向头结点

(3)双链表:每个结点有两个指针,一个指向前驱结点(llink),一个指向后继结点(rlink)

(4)循环双链表:尾和头互指

Chapter3 字符串

字符串:特殊的线性表,每个元素是一个字符;一个串中包含的字符个数称为其长度

字符在字符串中的序号/位置/第x个字符,是从1开始的,相当于下标+1

存储结构:顺序表/链表

操作:模式匹配:朴素/KMP

重要算法:KMP算法

匹配算法:用两个指针匹配字符串p和a,其中p是模式,a是目标,如果p[i]和a[j]不相等,则直接比较p[next[i]]和a[j],若next[i]小于0,则比较p[0]和a[j+1]。

而当我们比较p[i]和a[j]不相等的时候,我们知道的信息其实是a[j]前面的一串字符等于p[0],p[1],…,p[i-1],那么我们下一步的移动就相当于寻找最长的p[0],p[1],…,[k]=p[w],…,p[i-1],也就是寻找p[0],…,p[i-1]的最大相等前后缀的下标k(长度-1),然后令next[i]=k+1

next数组的进一步改进:在求next[i]的时候,如果还发现p[i]=p[next[i]]的话,就令next[i]=next[next[i]],直到p[i]≠p[next[i]]为止

复杂度分析:O(m+n),远远比朴素模式匹配的O(mn)要好得多

next数组的求法

可以看这个图好好品味一下:假想现在上侧有一个目标,你正在用a[j]和p[i]进行比较,现在发生了冲突,你需要考虑下一步要用a[j]和p[what]比较

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tyq2N7yI-1610548792470)(C:\Users\bill\AppData\Roaming\Typora\typora-user-images\image-20210112195656083.png)]

Chapter4 栈和队列

操作:插入/删除/创建/判空

逻辑结构:一种特殊的线性表,插入和删除操作都在一端进行;允许插入/删除操作的一端称为栈顶;另一端称为栈底;插入操作通常称为入栈;删除操作通常称为出栈;当栈中没有元素时称为空栈;LIFO性(Last In First Out,后进先出

存储结构:顺序表(pastack->r存放当前的栈顶元素;对满表进行入栈**:overflow**/对空表进行出栈**:underflow**)/链表(不需要头结点)

链表可以封装为一个结构体,里面有一个指向栈顶的指针

注意:栈在链表里面是倒过来存储的,也就是说,栈顶是第一个元素,不是任何元素的后继,这样做删除的时候使用的是top=top->link

应用:递归<=>非递归+栈;深度优先搜索;表达式的计算(中缀转后缀,后缀的计算);括号匹配的检验

队列

操作:插入/删除/创建/判空

逻辑结构:一种特殊的线性表,只允许在表的一端插入,另一端删除;允许删除的一端称为队头(类比实际的排队),允许插入的一端称为队尾;当队列中没有元素时称为空队;插入操作称为入队,删除操作称为出队;FIFO性(First in First Out,先进先出

存储结构:顺序/链表

顺序表存储时会出现上溢/下溢,而且由于元素不断向右移动,所以可能发生假溢出
解决方法**:环形队列**,将p[MAXNUM-1]的后继设为p[0](即:插入使用%MAXNUM的方法),并且为了区分空表和满表的情况,一般队列中存放有MAXNUM-1个元素时就认为满(即(p->last+1)%MAXNUM==p->first时就满)注:paqu->first存放队头元素;而paqu->last存放队尾元素+1(当前还不在队列里面)

链表可以封装为一个结构体,里面有两个指针,一个指向队头,一个指向队尾

注意:链表的指针顺序也是队头->…->队尾

应用:广度优先搜索

表达式计算过程详解

(1)中缀转后缀:一个运算符栈和一个数组,遇到数字直接进组,遇到运算符放在栈里面,直到被优先级平级或更的运算符挤出来

eg:(A-B)/C*D+E

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FfXo6v3C-1610548792474)(C:\Users\bill\AppData\Roaming\Typora\typora-user-images\image-20210112204835423.png)]

(2)后缀的求值:

从左向右扫描后缀表达式,遇到数字直接进栈,遇到操作符则从栈里面弹出两个数字,对其进行操作,再放回去,直到扫描结束后弹出栈中的数字即可

Chapter5 二叉树与树

二叉树

逻辑结构

节点的层数:一般规定根节点的层数为0,其余节点的层数为父节点的层数+1

二叉树的高度:二叉树中节点的最大层数

树叶/分支节点:左右子树都为空的结点;其它节点都是分支节点

节点的度数:节点的非空子树的个数(因此,结点的度数有三种情况:2,1,0

=>n_0=n_2+1

路径长度:x是y的祖先,那么x=x0,x1,x2,…,xn=y称为x到y的路径,并且n称为这条路径的长度

满二叉树**:每个节点要么是树叶,要么有两个子树**

满二叉树可不一定恰有2^n-1个节点!事实上,扩充二叉树都是满二叉树

完全二叉树:相当于以顺序表存储时的结构:除了最后一层以外都是满的,最后一层的节点也全都在左边

扩充二叉树:每个没有满的节点都使用分支节点填满,新增的节点称为外部节点,原有的节点称为内部节点;外部路径长度记为从根节点到每个外部节点的路径长度和;内部路径长度记为从根节点到每个内部节点的路径长度和

​ 性质:在扩充二叉树中,外部节点的个数比内部节点多1;E=I+2n

二叉树的周游**:先序遍历,中序遍历,后序遍历**:二推一

​ (在写周游的时候最好严格按照定义过一遍,不然很可能会脑残

存储结构

(1)顺序表示:对二叉树的所有节点从0~n-1编号,然后设定一下父子关系就行了

(2)链接表示:llink-rlink表示法

应用

(1)二叉树计数:n个节点的二叉树的可能形态数:Catlan数**:C(2n,n)/(n+1)**

(2)堆与优先队列**:存储结构:顺序表**;二叉树中每个节点都是所在的子树里面最大/最小的:大根堆/小根堆

一个小根堆:

image-20210112214647722

操作

​ 插入:先放在最后一个地方,然后顺着到根节点的路径逐步上调,停止则调整结束

​ 删除:把根节点扔了,把最后一个和两个次根比较一下,然后把其中最小的放到根上,因此动了至多一个根,然后一路往下调整

(3)Huffman树

使得WPL(带权外部路径总长度)最小的树

给定一个集合{w_1,w_2,…,w_m},构造一棵以w_1~w_m为外部节点的二叉树,使得这棵树的外部路径权值和最小

Huffman算法:贪心(每次取两个权值最小的树合并,并把根节点写上合并之后的权值)

最终得到的WPL的值为Huffman树的所有分支结点上的数字之和

Huffman编码:要求:字符串的头不能相互包含(否则无法直接查找)

(4)并查集

一次查找的时间复杂度:O(nα(n)),其中α为accerlman函数的反函数

树/树林

逻辑结构:树林:若干个树的并集

​ 树的周游:先根/后根/广度优先

存储结构

​ (1)父指针表示法

​ (2)子表表示法

​ (3)长子-兄弟表示法

树林与二叉树的转换

​ 树林->二叉树:相同层相同分支的节点连一条平行边;去掉非主子节点的节点和父节点连的所有边

​ 二叉树->树林:所有右子树串都和第一个节点的父节点连边并且去掉这些边(特别的,如果包含根节点的就会变成分支的树林

重要知识点整理
image-20210112230114952

(1)树的度数和:n-1

(2)以0为根,父节点[(n-1)/2];以1为根,父节点[[n/2]]

(3)在构建小根堆的时候只要记住一点,每次看顺序最后一个节点就行了

Chapter6/7 字典与检索

字典

逻辑结构:元素的有穷集合,每个元素由key和attribute两部分组成

操作:检索:给定一个key,在字典中查找关键码等于key的元素

衡量检索算法效率的主要标准:平均检索长度ASL=Σpc

存储结构概述

(1)静态字典:主要考虑检索效率和空间的利用效率

(2)动态字典:主要考虑插入效率和删除效率

对应数据结构:顺序表适合静态字典,索引顺序表、散列表都适合,树结构适合动态字典

明细

(1)顺序表示:顺序查找(O(n));二分查找(O(logn)),要求关键码是可以排序的

(2)索引表示:将关键码分成若干个段,查找的时候可以通过某些方法确定目标所在的块

说白了就是顺序表示,只不过attribute外面套了一层地址,在顺序表里面存的是地址而已

(3)链接表示

二叉排序树:左key<根key<右key

检索:类似二分法,与形状有关,一般来说是O(logn)

插入:和根比较,然后一路向下

删除一个指定节点:删掉之后把左子树插在原来的位置,把右子树连在左子树的最后一个节点(即在顺序表示下顺序码最大的节点)的右面

最优二叉排序树

最少比较次数:ASL=Σpi*(li+1)

等概率最佳排序树的构造:先把节点按照关键码排序,然后按二分法依次检索关键码,将检索中遇到的还未在二叉排序树中的结点插入树中

平衡二叉排序树:节点的左右子树高度差的绝对值小于等于1

可以保证插入删除都一定是O(logn),性能非常好

平衡因子右子树的高度-左子树的高度

插入:和根比较,然后一路向下,插入之后再向上寻找子树进行调整

调整策略:左/右重则右/左旋(LL/RR);中间重则中间升(LR/RL)

image-20210113112307540image-20210113112403407

其它

Trie树:关键码范围空间分解

字符树:每个分支是若干个字符

多路查找树:每个节点的孩子数量可以多于两个(乱七八糟的)

(4)散列表示:key->h(key)

碰撞:h(key1)=h(key2);key1和key2称为同义词,h(key)的值域称为基本区域;α:负载因子

散列函数:一堆,似乎只要掌握碰撞函数的选择就行了:

开地址法(闭散列):当碰撞发生时,按照某种固定的序列寻找可以存放元素的地址

常用探查序列:线性探查(+1%m,+2%m,…),平方探查(+12%m,-12%m,+2^2%m…),双散列(探查序列也是关于key的一个函数)

问题:

a.容易发生堆积(二次聚集),即不是同义词的字也放在了一个地方

b.在删除的时候不能直接删除元素,只能留下一个tomb,浪费了很多空间

开散列法(拉链法):每个散列表的attribute是一个指针,指向一个存储了所有以之为key的元素构成的链表

算法分析:关于负载因子α

α越小,表越空,空间复杂度越高;α越大,表越密,时间复杂度越高

一般来说,α在1/2左右时,所有操作的复杂度都在1.5左右(与n无关,这点非常nb

重要知识点整理

(1)二分法检索时,在区间[low,high]上的检索方式是取mid=[(high+low)/2],然后比较tag和a[mid],小于则转到low,mid-1;大于则赚到mid+!,high

(2)image-20210113110036102(可以自己算一下)

(3)检索长度就是检索到元素的总比较次数,因此,高度为h的二叉排序树,最大检索长度是**(h+1)**,最后相同的节点也要比较一下值的

(4)n层的AVL树的最少节点数量:n0=1 n1=2 n2=4 n3=7 n4=12 n5=20,n(k+1)=n(k)+n(k-1)+1

只能这么算,不能正着分配,否则会爆炸image-20210113110926922

(5)注意一下二叉排序树和堆的区别:二叉排序树的插入操作是从根开始的,而堆则是从顺序最后一个节点开始的;而且删除的时候二叉排序树也不一定是删除的根

image-20210113112630939(进行一次RL型调整即可)

(6)在二叉排序树中,新结点总是作为叶结点来插入的。(✔)

(7)给定一棵非空二叉排序树的先序序列,可以唯一确定该二叉排序树。(✔)(因为这些数据的顺序序列就是该二叉排序树的中序序列)

(8)

注意:就算是每个节点都大于左小于右也不一定是二叉排序树,必须要大于所有左子孙小于所有右子孙

Chapter8 排序

排序的目的:将一个放在顺序表里面的文件列按某种顺序重排

五大排序算法
1.插入排序:维护一个顺序表,每次向其中插入数据,直到要排序的数据全部插入为止。

常见算法:

(1)直接插入:每次直接按照顺序比较法插入一个元素

插入的时候是倒序查看,即:k_i与k_i-1,k_i-2,…,k_0比较,找到之后就把**后面的元素后移**,然后插入之。由于使用顺序表存储,效率相当低下

复杂度分析:最小比较次数:n-1;最小移动次数:0;最大比较次数:n(n-1)/2;最大移动次数:n(n+1)/2;平均比较次数:O(n2),平均移动次数:O(n2),空间复杂度O(1),稳定

(2)二分法插入排序:每次使用二分法查找插入,找一半就行了

二分插入排序的比较次数与待排序记录的初始状态无关, 仅依赖于记录的个数;这话不太严谨,但是只差至多一个,而且挺爱考的

复杂度分析:最小移动次数:0,最大移动次数:n2;平均移动次数O(n2);最小比较次数=最大比较次数=nlogn-n+1,空间复杂度O(1),稳定

(3)表插入排序:将顺序表改为链表;这样插入操作只需要O(1),但是查找就不能再用二分查找了

哦哦卧槽我原来以为二分法插入排序是O(1)插入O(logn)寻找原来插入必须用链表就不能使用二分查找了啊

复杂度分析:最小比较次数:n-1;最大比较次数:(n-1)n/2;移动次数:0;平均时间复杂度:O(n^2);空间复杂度:O(n)(指针);稳定

(4)Shell排序:先取d_1,把所有记录分成d_1组,所有距离为d_1倍数的记录放在同一组,先在各组内排序;然后取d_2<d_1重复上述操作

复杂度分析:平均比较次数和移动次数都在O(n^1.3)左右,不稳定

2.选择排序:每次都从待排序记录中选出排序码最小的记录

常见算法:

(1)直接选择排序:从i~n个记录中选出最小的和第i-1个交换

选择排序的比较次数与记录的初始状态无关:显然从i~n个记录中选出最小的的比较次数是固定的n-i

复杂度分析:总比较次数:n(n-1)/2,移动次数至少为0,最多为3(n-1),时间复杂度O(n^2),空间复杂度O(1),不稳定

(2)胜方树:构造一个满二叉树,n补充至2^i,然后第一次两两比较,让最小的上升一层,直到构建出整棵树;然后每次取的时候删除一个根及所在的一条线;然后从最后一个开始更新

image-20210113192356391image-20210113192417622

复杂度分析:总比较次数O(nlogn),移动次数O(nlogn),空间复杂度O(n)

(3)堆排序:先把数据构造成一个大根堆,然后第i次直接依次将根节点和当前最后一个节点(第(n-i个)交换;这样每次让第n-i个数据归位;然后把堆从根节点向下重新调整为最大堆,重复之前的操作

核心算法:构造初始堆:首先把所有数据都放在顺序表里面,然后从第一个非叶节点开始向前,把以每个数据为根的树调整为堆;调整方法仍然是从根向下一次遍历法,因此在深度为m的树上最多比较2m次(和两个子女比较),因此初始建树的复杂度不超过4n;最后每次去掉一个节点重新建堆的复杂度是2log_2(n-j),总计O(nlogn)

复杂度分析:建初始堆比较次数C1 :O(n) ;重新建堆比较次数C2 :O(nlog2 n) ;总比较次数=C1 +C2 ;移动次数小于比较次数 ; 时间复杂度:O(nlog2 n) ; 空间复杂度:O(1),不稳定

3.交换排序:不停地把逆序对换成正序的

(1)起泡排序:对(R0,R1),(R1,R2),…,(Rn-1,Rn)进行换序,那么最后Rn一定会放着最大的,然后对前n-1个继续……为了提升速度,可以设置一个flag来看某一次是否做了交换,如果没有交换就说明排序已经结束。

复杂度分析:最小比较次数:n-1;最大比较次数:n(n-1)/2;平均比较次数:O(n2);最小移动次数:0;最大移动次数:n(n-1)/2;平均移动次数:O(n2),时间复杂度O(n^2);空间复杂度O(1),稳定

(2)快速排序:从待排序的序列中选取一个“轴值”作为基准,然后对序列进行划分,让轴值左边的元素值都小于之,右边的元素值都大于之;然后递归

轴值选择的目标:让左右序列尽可能等长;中值计算/固定位置/随机选择;选出的轴值应该先换到第一个(方便一趟排序)

操作:把轴值从数列里面丢出去,留下一个空位;i和j分别从头和尾扫描,j先开始移动,扫描到比轴值小的就移到空位里面,它原来的地方是新的空位;然后i再开始移动;j再移动……

复杂度分析:当数据已经排好序时比较次数最多(因为快排算法的左右序列分的越均匀,二叉树就越平衡,复杂度就越低),因此,最大比较次数:n(n-1)/2,最小比较次数O(nlogn),平均比较次数O(nlogn),最大移动次数:n(n-1)/2,最小移动次数:0,平均时间复杂度O(nlogn),空间复杂度O(n);需要一个栈实现递归,空间复杂度取决于栈的大小,因此最坏情况下是O(n),最好情况下是O(logn),平均为O(logn),不稳定

4.分配排序:把排序码分成若干个部分:

常见算法**:基数排序**:先把数据按照个位分开,把十位排好,再按照个位从小到大合起来,再按照十位分开,把个位排好,再合在一起就行了。

两种方法:高位优先/低位优先;即从权重大的字典开始比较还是从权重小的开始

高位优先法每次都要按照相同的编码分成若干个堆,然后对堆内排序,最后合起来,比较直观,但是复杂度高;(注:这里的“堆”其实是类似抽屉的一个东西

低位优先法每次从最低位开始依次对所有元素排序,经过k次以后就已经好了;而且如果像基数排序这样每一层的关键码取值有限的话就可以直接建辅助队列来做,相当快

基数排序的复杂度分析:每趟排序中,清队列的时间为O®,将n个记录分配 到队列的时间为O(n),收集的时间为O®,因此, 一趟排序的时间为O(r+n) ;总共要进行d趟排序,基数排序的时间复杂度是 T(n)=O(d*(r+n)),空间复杂度为O(r+n),稳定

5.归并排序:刻在DNA里的排序方法

把待排序的文件分成若干个子文件,先将每个子文件内的记录排序,再将已排序的子文件合并,得到完全排序的文件;每次取文件的大小一般递增

复杂度分析:具有n个记录的文件排序,必须做logn趟归并,每趟归并所花费的时间是O(n) ,因此时间复杂度为T(n)=O(nlog2 n) ;空间复杂度 S(n)=O(n) ;由于每趟归并都要扫描两个数组中的所有元素,与时间无关。稳定

排序算法总结:
image-20210113195030154

(1)“顺序表”和“顺序/逆序”一点关系都没有

(2)直接选择排序是不稳定的,是这么多O(n2)里面唯一一个不稳定的,相当垃圾

堆排序的辅助空间只有O(1)(因为行为相当于采用了顺序表存储树进行排序);归并排序是O(nlogn)里面唯一一个稳定的;快速排序的空间消耗并不是O(1),因为递归并不是不费空间

(3)快速排序法最快,被认为是目前基于比较的排序方法中最好的方法,当待排序的记录是随机分布时,快速排序的平均时间最短;
当数据基本稳定的时候,起泡排序或者直接插入是最快的排序方法

二分插入,直接选择排序、归并排序、基数排序对数据分布情况不敏感

(4)image-20210113204814776

(5)基数排序的nb之处:如果已知数据的位数较小时,速度相当快。例如,给所有中国人的身份证号进行排序,则d=18,r=10,时间复杂度只有O(18n)

Chpater9 图

逻辑结构:每个元素可以有多个前驱和多个后继

集图里面的基本概念+握手定理

环:起点和终点相同的路径(和集图里面定义不一样)

存储结构

(1)邻接矩阵:n*n的关联矩阵,O(n2);带权:有边则为权;无边则为正无穷

(2)邻接表:n个链表存储后继节点,O(n*d);带权:后继节点增加一个属性即可

有向图的两种:出边表/入边表

操作

1.周游

深度优先周游/广度优先周游=>DFS生成树

广度优先周游=>BFS生成树

复杂度:邻接矩阵O(n2),邻接表O(n+e),空间O(n)

2.构造最小生成树:在带权图中寻找一个权值和最小的树

(1)Prim算法:

image-20210113215512395

注意每次别数漏了就行

复杂度分析:一共取n-1次,每次比较n-i个权值,因此总时间复杂度为O(n2)

(2)Krustal算法:

把所有边的权值排序,每次寻找不在同一个连通分支中的边中的最小值并加入

复杂度分析:O(n3),相当的辣鸡

3.求任意两个顶点间的距离

(1)Dijkstra算法:

求顶点v_0和其它所有顶点之间的距离:构造V={v_0},然后每次选取V和U-V的所有边中最短的,用a[i][j]=min{a[i][min]+a[min][j],a[i][j]}来更新v_0到每个U-V中的节点的距离

复杂度:O(n2)

(2)Floyd算法:对关系矩阵直接进行操作;并附加一个nextvex存放节点的后继节点的编号

对邻接矩阵进行更新:每次加入a[i]作为中间节点,对邻接矩阵进行更新:a[i][j]=min{a[i][min]+a[min][j],a[i][j]}

4.拓扑排序

AOV网:顶点活动网,用顶点表示活动,边表示活动之间的某些约束关系

算法:每次删掉一个入度为0度的节点

关键路径:从开始节点到完成节点的最长路径

算法:计算每个事件的最早发生时间和最迟发生时间;一个从正向开始按照拓扑序列计算,一个从反向开始按照拓扑序列倒序计算

重要知识点整理

(1)有向图至多可以有n(n-1)条边

(2)image-20210113223444603

一道坑题(n=1的图值得特殊对待一下,毕竟设定是连通的)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值