赞
踩
博主暑期整理了一些数据结构相关的问题,以作保研面试之用。整理过程中也借鉴了一些网上的其他资料,其中绝大部分已不可考,如有侵权,通知删除。
时间复杂度(Time complexity)是一个函数,它定性描述该算法的运行时间。
线性存储结构:地址连续,存储密度大,便于查找和修改;支持随机存取
链式存储结构:地址可以不连续,不会占用连续的空间;不支持随机存取;插入或删除元素很方便。
随机存取和顺序存取:比如给你一列纵队的人,让你找到位置为5的人,第一种方法是你从第一个位置开始一个一个往后找,直到找到位置为5的位置,这样的就是顺序存取;而如果你已经知道5的位置,不需要经过前面的四个位置直接到达位置为5的位置,那么这样的就是随机存取
顺序存储结构就是在内存空间中开辟一片连续的空间,然后把数据按照顺序进行存储的一种方式。
链式存储结构就是计算机 中用一组任意的存储单元存储线性表的 数据元素 (这组存储单元可以是 连续 的,也可以是不连续的)
头指针是指向第一个节点存储位置的指针,具有标识作用。头指针是链表的必要元素,无论链表是否为空,头指针都存在。
头结点是放在第一个元素节点之前,便于在第一个元素节点之前进行插入和删除的操作,头结 点不是链表的必须元素,可有可无,头结点的数据域也可以不存储任何信息。
区别:
(1)栈按照后进先出的规则处理,只能在表的一头进行插入和删除操作;队列按照先进先出的规则处理,通常在表的一头进行插入操作,在表的另一头进行删除操作。
(2)应用场景不同:栈一般用于深度优先遍历;队列一般用于计算机资源的管理,广度优先遍历等。
补充:如何实现栈和队列?用顺序表和链表都可以实现。
(F的位置-P的位置+n)%n
补充:如何判断链表是否有环?
定义两个指针(快慢指针),同时从链表的头节点出发,一个指针一次走一步,另一个指针一次走两步。如果走得快的指针追上了走得慢的指针,那么链表就是环形链表;如果走得快的指针走到了链表的末尾(next指向 NULL)都没有追上第一个指针,那么链表就不是环形链表。
判断队满:Q.front == (Q.rear + 1) % MAXSIZE;
判断队空:Q.front = Q.rear
堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
(1)堆中某个节点的值总是不大于或不小于其父节点的值;
(2)堆总是一棵完全二叉树。
根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆。
根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆。
堆的构建:堆的构建方法
因为堆是对父节点-左/右孩子节点之间的约束,所以从最后一个非叶子节点开始调整。注意每次交换后,都要对下一层的子堆进行递归调整,因为交换后有可能破坏已调整子堆的结构。
堆排序:堆排序
哈希函数 :在记录的关键字与记录的存储位置之间建立的一种对应关系。是从关键字空间到存储位置空间的一种映象。
哈希表 :应用哈希函数,由记录的关键字确定记录在表中的位置信息,并将记录根据此信息放入表中,这样构成的表叫哈希表。
构造方法:
(1)直接定址法
:取关键字的某个线性函数值作为散列地址,H(key)=akey+b。
(2)除留余数法
:取关键字对p取余的值作为散列地址,其中 p<m H(key)=key%p (p<m)。
(3)数字分析法
**:当关键字的位数大于地址的位数,对关键字的各位分布进行分析,选出分布均匀的任意几位作为散列的地址,适用于所有关键字都已知的情况。*
(4)平方取中法
:对关键字求平方,再取结果中的中间几位作为散列地址。
(5)折叠法
:将关键字分为位数相同的几部分,然后取这几部分的叠加和作为散列地址。适用于 关键字位数较多,且关键字中每一位上数字分布大致均匀。
(6)随机数法
:选择一个随机函数,把关键字的随机函数值作为散列地址。适合于关键字的长度不相同时。
哈希冲突指的是,传入的参数不同,但是传出的结果却相同。
哈希冲突的解决方法包括:开放定址法和拉链法,再哈希法,当冲突发生时,使用某种探测技术形成一个探 测序列,然后沿此序列逐个单单元查找,直到找到该关键字或者碰到一个开放的地址为止,探测 到开放的地址表明该表中没有此关键字,若要插入,则探测到开放地址时可将新节点插入该地址 单元。其中开放定址法包括:线性探查法,二次探查法,双重散列法
(1)线性探查法
:基本思想,探查时从地址d开始,首先探查T[d],在探查T[d+1]…直到查到T[m1],此后循环到T[0],T[1]…直到探测到T[d-1]为止。
(2)二次探查法
:基本思想,探查时从地址d开始,首先探查T[d],再探查T[d+12],T[d+22]…等,直 到探查到有空余地址或者探查到T[d-1]为止,缺点是无法探查到整个散列空间。
(3)再哈希法
:用不同的哈希函数再做一遍哈希。基本思想,使用两个散列函数来确定地址,探查时从地址d开始,首先探查 T[d],再探查T[d+h1(d)],T[d+2*h1(d)]… 链接法:将所有关键字为同义词的节点链接在同一个单链表中,若选定的散列表长度为m,则可 将散列表定义为一个由m个头指针组成的指针数组,凡是散列地址为i的节点均插入到头指针为i的 单链表中。
(109条消息) 【算法】如何判断链表有环_Mlib的博客-CSDN博客_如何判断链表是否有环 判断链表是否有环
二叉搜索树即为二叉排序树。
二叉排序树:对于一棵空的二叉树或者具有如下性质的二叉树:
1.若其左子树不为空,则左子树所有结点的值均小于根结点的值。
2.若其右子树不为空,则右子树所有结点的值均大于根结点的值。
3.其左右子树也是二叉排序树。
如何构造二叉排序树:在插入元素x的时候,不断将元素x和二叉排序树中已经有的元素进行比较,找到一个合适的位置进行插入。
平衡二叉树:平衡二叉树一定是一个二叉排序树,并且任意一个结点的左右子树的高度差<=1。
平衡二叉树及其调整:(109条消息) 平衡二叉树_月雲之霄的博客-CSDN博客_平衡二叉树定义
如何构造平衡二叉树:
有了二叉搜索树,为什么还要二叉平衡树?
二叉搜索树容易退化成一条链
这时,查找的时间复杂度从O ( log n)也将退化成O ( N )
引入对左右子树高度差有限制的平衡二叉树 AVL,保证查找操作的最坏时间复杂度也为O ( log n)
问:有了平衡二叉树,为什么还需要红黑树?
AVL的左右子树高度差不能超过1,每次进行插入/删除操作时,几乎都需要通过旋转操作保持平衡
在频繁进行插入/删除的场景中,频繁的旋转操作使得AVL的性能大打折扣
红黑树通过牺牲严格的平衡,换取插入/删除时少量的旋转操作,整体性能优于AVL
红黑树插入时的不平衡,不超过两次旋转就可以解决;删除时的不平衡,不超过三次旋转就能解决
红黑树的红黑规则,保证最坏的情况下,也能在O ( log n)时间内完成查找操作。
问:红黑树那几个原则,你还记得么?
(颜色属性)节点非黑即红
(根属性)根节点一定是黑色
(叶子属性)叶子节点(NIL)一定是黑色
(红色属性)每个红色节点的两个子节点,都为黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
(黑色属性)从任一节点到其每个叶子的所有路径,都包含相同数目的黑色节点。
问:红黑树写入操作 ,是如何找到它的父节点的?
这个还是看原网站吧…
问:红黑树的有那些内部操作
变色:把一个红色的节点变成黑色,或者把一个黑色的节点变成红色,就是对这个节点的变色。
旋转:与平衡二叉树的旋转操作类似。
由遍历序列构造一棵二叉树:点击超链接查看
什么是B树:B树和AVL树(平衡二叉树) 的差别就是 B树 属于多叉树,又名平衡多路查找树,即一个结点的查找路径不止左、右两个,而是有多个。
一个m阶B树的特点:①每个结点最多有m个子树,除根结点之外的所有非终端结点至少有⌈m/2⌉棵子树。②如果一个结点有n-1个关键字,则该结点有n个分支,且这n-1个关键字按照递增顺序排列。
B树的查找操作:
B-树的具体查找步骤如下(假设查找的关键字为key):
(1)先让key与根结点中的关键字比较,如果key等于K [i](K []为结点内的关键字数组),则查找成功。
(2)若key<K [1],则到P [0]所指示的子树中进行继续查找(对p[]为结点内的指针数组),这里要注意B-树中每个结点的内部结构。
(3)若key> K [n]的,则到P [n]所指示的子树中继续查找。
(4)若k [i] <key <k [i + 1],则沿着指针p [i]所指示的子树继续查找。
(5)如果最后遇到空指针,则证明查找不成功。
为什么要有B+树:要说明这个问题,首先要从B树的好处和不足出发。
B树好处:
B树的每一个结点都包含key(索引值) 和 value(对应数据),因此方位离根结点近的元素会更快速。(相对于B+树)
B树的不足:
不利于范围查找(区间查找),如果要找 0~100的索引值,那么B树需要多次从根结点开始逐个查找。
而B+树由于叶子结点都有链表,且链表是以从小到大的顺序排好序的,因此可以直接通过遍历链表实现范围查找。
B+树和B树最大的不同是:
(1)B+树内部有两种结点,一种是索引结点,一种是叶子结点。
(2)B+树的索引结点并不会保存记录,只用于索引,所有的数据都保存在B+树的叶子结点中。而B树则是所有结点都会保存数据。
(3)B+树的叶子结点都会被连成一条链表。叶子本身按索引值的大小从小到大进行排序。即这条链表是 从小到大的。多了条链表方便范围查找数据。
(4)B树的所有索引值是不会重复的,而B+树 非叶子结点的索引值 最终一定会全部出现在叶子结点中
红黑树是一种自平衡的二叉搜索树(二叉排序树),是二叉排序树的改进版本。它与AVL树类似,都在添加和删除的时候通过旋转操作保持二叉树的平衡,以求更高效的查询性能。
AVL树的缺点:必须保证左右子树平衡,Max(最大树高-最小树高) <= 1,正是由于这种严格的平衡条件,导致AVL需要花大量时间在调整上,故AVL树一般使用场景在于查询场景, 而不是 增加删除 频繁的场景。
二分查找时间复杂度:O( log2N);线性搜索时间复杂度:O(n)
最小生成树:普利姆P、克鲁斯卡尔K
最短路径:迪杰斯特拉D、弗洛伊德F
最小生成树概念:
最小生成树必须满足3个条件:不能形成环;必须包含所有n个节点;必须包含n-1条边
普利姆算法Prim:
Selected数组用来记录某一个定点是否已经被选中。
minDist数组用来记录某一时刻连接已选顶点集合到该顶点的所有边中,权值最小的那条边的权值大小,初始全为inf。
Parent数组记录最小权值边两端顶点的信息,初始全为-1。
①更新列表信息。当已选顶点集合中,每加入一个新的顶点。更新所有与该顶点相连的顶点的列表信息:minDist、parent。
②扫描最小距离列表,找到最小值所对应的顶点。就是minDist里面找到最小值。
③添加刚刚找到的顶点到已选顶点集合中。同时,按照parent列表信息创建最小生成树的一条边。接着再重复第一步操作。
当所有顶点都被加入到顶点集合中时,最小生成树创建完毕。
克鲁斯卡尔算法Kruskal:
①把图中所有边取出,放到列表中,按照边权值由小到大的顺序进行排序。
②从列表中,每次按次序选择一条边添加到图中。判断是否产生环。如果产生环那么就抛弃该边,取下一条边;如果不产生环,就把该边加入到图中。
③一直加边,直到选择了n-1条边为止。
迪杰斯特拉算法Dijkstra:
首先维护两个数组,数组1记录了结点n与出发点0之间的最短路径,数组2记录了结点n到出发点0之间的最短路径所经过的前一个结点。
①检查所有未标记结点中,到出发点0距离最短的结点,并把这个点n加入到已选结点中。
②更新点n的邻接点到出发点0的最短距离,以及这些邻接点的前面点修改为n。
最短路径怎么走呢?通过前面点反过来追溯回去。 4 -> 5 -> 6 -> 7 -> 0
弗洛伊德算法:
维护一个A数组,一个path数组。A数组存储了任意两个顶点之间的最短路径长度。Path数组记录了任意两个顶点所在最短路径上的中间点。假如path[1][0]的值为3,说明从顶点0到顶点1需要经过顶点3。
对于每一个顶点v,和任意一个顶点对(i,j),如果A[i][j]>A[i][v]+A[v][j],那么就将前者的值更新为后者。并且将path[i][j]改成v。
时间复杂度:
Dijkstra(迪杰斯特拉)算法时间复杂度一般是o(n 2 ),Floyd算法时间复杂度是o(n 3)
普利姆算法时间复杂度为O(n2),该算法适用于求边稠密网的最小支撑树
克鲁斯卡尔算法的时间复杂度为O(eloge),适用于求稀疏网的最小支撑树
如果这个节点的左指针域是空的,那么就让其指向前驱,如果这个节点的右指针域为空,那么就让其指向后继。
前驱节点:对一棵二叉树进行中序遍历,遍历后的顺序,当前节点的前一个节点为该节点的前驱节点;
后继节点:对一棵二叉树进行中序遍历,遍历后的顺序,当前节点的后一个节点为该节点的后继节点;
例如一棵完全二叉树(1,2,3,4,5,6,7),按照中序遍历后的顺序为:(4,2,5,1,6,3,7),1节点的前驱节点为:5,后继节点为6.
二叉树的线索化:
对普通二叉树以某种次序遍历使其成为线索二叉树的过程就叫做线索化。因为前驱和后继结点只有在二叉树的遍历过程中才能得到,所以线索化的具体过程就是在二叉树的遍历中修改空指针。
拓扑排序可以决定哪些子工程必须要先执行,哪些子工程要在某些工程完成后才能执行。把以顶点为活动,边为活动间先后顺序关系的有向图成为顶点活动网,简称为AOV 网。一个AOV网应该是有向无环图,不应该存在回路,如果要存在回路的话则该回路上的所有活动都无法执行。在AOV网中如果不存在回路,则可以把所有的活动排列成一个序列,称该序列为拓扑序列,拓扑序列并不是唯一的。形成拓扑序列的过程称为拓扑排序。
拓扑排序的步骤:(1)在有向图中任意选择一个没有前驱的节点输出(2)从图中删去该节点以及与它相连的边(3)重复以上步骤,直到所有的顶点都输出或者当前图中不存在无前驱的顶点为止,后者代表该图是有环图,所以可以通过拓扑排序来判断一个图是否存在环。
并查集顾名思义就是有“合并集合”和“查找集合中的元素”两种操作的关于数据结构的一种算法。
并查集,在一些有 N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。
并查集是一种树形的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。
并查集也是使用树形结构实现。不过,不是二叉树。每个元素对应一个节点,每个组对应一棵树。在并查集中,哪个节点是哪个节点的父亲以及树的形状等信息无需多加关注,整体组成一个树形结构才是重要的。类似森林。
循环链表 :是一种头尾相接的链表。其特点是无须增加存储量,仅对表的链接方式稍作改变,即可使得表处理更加方便灵活。
判断一个单向链表中是否存在环的最佳方法是 快慢指针 。
涉及遍历操作时,其终止条件变为判断它们是否等于某一指定指针,如头指针或尾指针等。
双向链表 :在单链表的每个结点里再增加一个指向其直接前趋的指针域prior。这样就形成的链表中有两个方向不同的链。双链表一般由头指针唯一确定的,将头结点和尾结点链接起来构成循环链表,并称之为双向链表。在有序双向链表中定位删除一个元素的平均时间复杂度为O(n),但是若指定节点,则可以直接删除当前指针所指向的节点。而不需要像单向链表中,删除一个元素必须找到其前驱。
顺序栈 :顺序存储结构。top= -1时为空栈,top=0只能说明栈中只有一个元素,并且元素进栈时top应该自增
链栈 :链式存储结构。插入和删除操作仅限制在链头位置上进行。栈顶指针就是链表的头指针。通常不会出现栈满的情况。 不需要判断栈满但需要判断栈空。
栈的经典应用 :进制转换、括号匹配、迷宫求解、表达式求解:用于求解中缀表达式或者在前缀、中缀、后缀表达式间进行转换。
森林:在数据结构里,森林是由若干棵互不相交的树组成的。下图就是一个由两棵树组成的森林。两棵树之间分别独立,没有交集。
森林转换成二叉树 :
1.将各棵树分别转换成二叉树
2.将每棵树的根结点用线相连
3.以第一棵树根结点为二叉树的根
连通图 :无向图图 G 的任意两点之间都是连通的,则称G是连通图。
连通分量 :极大连通子图,子图中包含的顶点个数极大
强连通图 :有向图G的任意两点之间都是连通的,则称G是强连通图。各个顶点间均互相有向可达。
强连通分量 :极大连通子图
一个无向图 G=(V,E) 是连通的,则:|E|>=|V|-1,而反之不成立。
一个有向图 G=(V,E) 是是强连通图,则:|E|>=|V|,而反之不成立。
没有回路的无向图是连通的当且仅当它是树,即等价于:|E|=|V|-1。
子图 :一个图的结点集和边集都是另一个图的子集,称这个图为另一个图的子图
(1)直接插入排序,稳定
直接插入排序的代码如下。注意,其中荧光笔标出来的部分是>。这个>保证了直接插入排序是稳定的。
假设对于数列:3 15(1) 38 44 57 15(2)。Arr[j]=15(1),temp=15(2),当执行到arr[j]>temp这句话的时候,不满足这个大于条件,所以不进行交换,所以是稳定的。
void Straight_Insert_Sort(int *arr, int length)
{
int tmp;
for (int i = 1; i < length; i++)
{
tmp = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > tmp)
{
arr[j + 1] = arr[j];
j--;
}
}
arr[j + 1] = tmp;
}
(2)冒泡排序,稳定
百度答案,我认为言之有理。
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
(3)简单选择排序,不稳定
百度答案,我认为言之有理。
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n - 1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等 的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
(4)希尔排序,不稳定
在希尔排序中,由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。对于排序算法,所谓的不稳定指的就是相同元素在排序过程中被移动;
(5)快速排序,不稳定
举个例子:
待排序数组: int a[] ={1, 2, 2, 3, 4, 5, 6};
在快速排序的随机选择比较子(即pivot)阶段:
若选择a[2](即数组中的第二个2)为比较子,,而把大于等于比较子的数均放置在大数数组中,则a[1](即数组中的第一个2)会到pivot的右边, 那么数组中的两个2非原序(这就是“不稳定”)。
若选择a[1]为比较子,而把 小于等于 比较子的数均放置在小数数组中,则数组中的两个2顺序也非原序。
(6)堆排序,不稳定
堆排序不是稳定的排序算法,因为在排序的过程,存在将堆的最后一个节点跟堆顶节点互换的操作,所以就有可能改变值相同数据的原始相对顺序。
(7)2路归并排序,稳定
归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。
(8)基数排序,稳定
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。