数据结构期末复习

前言:

第一次接触数据结构时,我觉得这门课特别抽象,和无聊。抽象在于,第一次接触就被一些没怎么名词,线性表,链表,队列,栈,散列表 这些奇怪的词语弄迷糊了,我一直很好奇,

这些名字真的能帮我们理解这个数据结构吗?

 

一、数据结构:

 

 

 

 

 

 

 其中,图和树不用多说,集合这样的类型,典型的数据结构就是集合,其次 并查集。 这样的逻辑结构的特点是:元素之间的共性仅仅是属于一个集合而已。

 

物理储存方式:

 

 

 

 

 

 

这个秒啊,插不到前面去,就插他后面,然后把数据交换。

 

 

 

 

 

 

 我的思路:遍历链表,全部反转一遍。然后从尾往头遍历n个长度。

 

 

 

二、队列

参考:https://www.jianshu.com/p/9ba8a65464dd

结论:

0.普通队列,插入元素rear 指针后移,删除元素,front指针后移。 队列满条件:rear 超过数组下标。

1.为了解决假溢出,使用循环队列。

2.循环队列中,插入元素的下一位置为 (rear+1)% maxsize

3.循环队列队满条件也是rear=front

队满时Q.front=Q.rear,这和队空的条件一模一样,无法区分队空还是队满,如何解决呢?有两种办法:一是设置一个标志,标记队空和队满;另一种办法是浪费一个空间,当尾指针Q.rear的下一个位置Q.front是时,就认为是队满。

 

(Q.rear+1) %Maxsize= Q.front,此时为队满。

 

队空:Q.front=Q.rear; // Q.rear和Q.front指向同一个位置

队满: (Q.rear+1) %Maxsize=Q.front; // Q.rear向后移一位正好是Q.front

入队

Q.base[Q.rear]=x; //将元素放入Q.rear所指空间,

Q.rear =( Q.rear+1) %Maxsize; // Q.rear向后移一位

出队

e= Q.base[Q.front]; //用变量记录Q.front所指元素,

Q.front=(Q.front+1) %Maxsize // Q. front向后移一位

队列中元素个数:
(rear-front+maxsize)%maxsize
 

三、二叉树

分类:

1.完全二叉树
2.完美/满二叉树
 

基本结论:

度数=顶点数-1
 
 
 
 

 

 

3、 三种遍历

preorder:中 左 右

inorder: 左 中 右

postorder: 左 右 中

 

根据遍历方式 确定二叉树,必须要有中序+其他一个

 4.森林与二叉树的转换

 

1、树转换为二叉树 :孩子兄弟表示法

任何一颗树都是二叉树。

左节点 连接兄弟

保留父节点和左孩子的连线,删除与其他孩子的连线。

旋转。

(1)加线。就是在所有兄弟结点之间加一条连线;
(2)抹线。就是对树中的每个结点,只保留他与第一个孩子结点之间的连线,删除它与其它孩子结点之间的连线;
(3)旋转。就是以树的根结点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明。

 

 

森林转换为二叉树:

首先,啥是森林? 

树的集合。

如图,三棵树集合在一起,组成为一个森林。

 

转换步骤:

1.把每棵树变成二叉树

2.从第二棵树开始,根节点连接前一颗树的右孩子。

 

 

 二叉树转化为树:

这就是树变二叉树的逆过程。即孩子兄弟表示法的逆过程。

我们知道,树变二叉树的过程中,树上的一个节点,与他左边第一个孩子保持关系,剩下的孩子都成了第一个孩子的右边一顺溜。

所以拿到一个二叉树,就把他左孩子的右边一顺溜,全部和他连起来。这就是他的孩子。

二叉树转换为树是树转换为二叉树的逆过程,其步骤是:
(1)若某结点的左孩子结点存在,将左孩子结点的右孩子结点、右孩子结点的右孩子结点……都作为该结点的孩子结点,将该结点与这些右孩子结点用线连接起来;
(2)删除原二叉树中所有结点与其右孩子结点的连线;
(3)整理(1)和(2)两步得到的树,使之结构层次分明。

 

 


 

 

 

 

图:

 

 

 1.DFS:

从一个点出发,一直往第一个邻接点的方向走,如果走到一个邻接点都被遍历过的点,回退到上一个点,直到最开始的点的邻接点都被遍历完成。

2.BFS:

 

 

 

 

prim:

一颗小树快快长大。

1。选取初始点v,放入minimal spanning tree的集合,作为生成树的一个节点。

2. 遍历他的邻接点w,更新邻接点到最小生成树的距离。(即 该距离dist=min(dist, edge length (v,w))))

Kruskal算法:

小树合并成森林。

先把所有顶点都看成一颗最小生成树,然后将它们合并起来就可了。

维护一个最小生成树的边集合。

只要我们取的边数没有到N-1条,并且图上还有边给我们取

我们就取出一条权重最小的边,然后把这条边从图上删除。

看看这条边是否构成回路,如果不构成回路,就加入到最小生成树的边集里。

循环出来后,判断边数是否为N-1 不是则说明图不连通,无最小生成树。

拓扑排序

条件.有向无环图。

流程:

1.遍历所有节点,找到入度为0的节点入队。

2.弹出入度为0的节点,后遍历这个节点的邻接点,把他们的入度-1,如果为0,再放入队列中。

3.循环跳出后,还有节点没有被遍历到说明图不是有向无环图。return Error

 

 

查找表:

 

1.分类:  

 静态查找, 操作完成后表不改变

动态查找, 表改变

2. 平均查找长度:

期望值。

ASL=sum(pi*ci) 

ci 找i个记录需要的比较次数

pi i个记录出现的概率 1/n

哨兵: 让0下标位置放要查找的元素,查找时间节约一半。

3. 二叉排序树

4.二分搜索

排序:

1.插入排序:

1) 抽一张牌q,放入手牌中(手牌都是有序的)

2) 再抽一张n,比较大小,插在手牌里合适的位置。(比左边大,比右边小)

2.冒泡排序:

1)从起始位置开始,依次比较相邻两元素的大小,如果逆序,则交换。

2)比较完成后,n-i个位置是第n-i 大的元素。

 

总结: 

这两种排序都是交换顺序的排序,逆序对有多少个就需要交换多少次,

3.希尔排序

1) 每个d个间隔取出元素来,做插入排序。

2) d 减小,重复1)

3) 直到d=1,做完全的插入排序。

前面1) 2)的步骤是为3)做铺垫,消除绝大多数有序对。

 

 

3.选择排序:

每次从无序序列中选择出一个最小元,和有序部分的最后一个元素交换。

找到最小元的方法有:

1). 遍历寻找 O(n)

2.) 堆排序

4.堆排序:

方法1:

1.)建堆

2) 堆中弹出元素 赋值给数组
额外空间复杂度O(n)

方法2:

1) 建堆

2) 交换堆顶和第i个元素的位置

3) 从第i个元素往上建堆。

 

以上是O(n^2)复杂度的排序。

---------------------------------------------------------------

以下是O(n*logn)复杂度的排序。 两种递归的排序方式。

1.归并排序:

1) 递归把序列分成不可再分的若干小序列。(递归)

2) 将各个小序列 归并为有序 序列

3) 返回上一层,直至整体序列有序。

https://pic4.zhimg.com/v2-a29c0dd0186d1f8cef3c5ebdedf3e5a3_b.webp

 

 

 

额外空间复杂度是O(n)

2. 快速排序:

1) 选出一个pivot 元素作为基准,通过交换元素的方式将左右两边调整为左边元素都比pivot 小,右边元素都比pivot 大。

2) 递归左半边和右半边。

void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j -- ; while (q[j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    quick_sort(q, l, j), quick_sort(q, j + 1, r);
}

作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

 

 关于排序的稳定性:

链接:https://www.nowcoder.com/questionTerminal/38bb6a80673b4052a79b6932e4fee5c1?toCommentId=1998023
来源:牛客网

(1)冒泡排序
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改 变,所以冒泡排序是一种稳定排序算法。
(2)选择排序
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个 元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么 交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
(3)插入排序
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开 始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相 等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳 定的。
(4)快速排序
快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,i <= j, 交换a[i]和a[j],重复上面的过程,直到i>j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻。
(5)归并排序
归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有 序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定 性。那么,在短的有序序列合并的过程中,稳定是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结 果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。
(6)基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优 先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。
(7)希尔排序(shell)
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小, 插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元 素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
(8)堆排序
我们知道堆的结构是节点i的孩子为2*i和2*i+1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n /2-1, n/2-2, ...1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。
综上,得出结论: 选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。

 

例题:

 

 使用以上的排序方式。

 

二叉搜索树:

 

堆:

取出元素的顺序按照元素的优先级。
 
1.性质:
1) 完全二叉树
2) 任何一个根节点都比其孩子节点大或者小。
 
2. 实现:
 
1)使用数组实现。因为是完全二叉树,按照父亲与左右儿子节点下标的关系,确定逻辑关系(父亲,左,右孩子),数组最后一个位置就是数的最后一个元素。
 
2) 插入,放在最后一个位置上,
 
如果符合性质2),pass
如果不符合,调整树。 假设比父亲节点大,就和父亲节点交换,直到所有节点都满足性质2.

 

 

 

3)删除: 删除的位置是确定的,肯定是根节点。

删除根节点后,将最后一个元素补到根节点位置上,

再调整堆的结构(比较根节点元素和它左右孩子的最大值,如果不满足,交换),使它满足性质。

 

4) 建堆:

 链接:https://www.nowcoder.com/questionTerminal/1f1d7f05826140eaaccf534892758753
来源:牛客网

  • 首先根据序列构建一个完全二叉树
  • 在完全二叉树的基础上,从最后一个非叶结点开始调整:比较三个元素的大小–自己,它的左孩子,右孩子。分为三种情况:
    • 自己最小,不用调整
    • 左孩子最小,交换该非叶结点与其左孩子的值,并考察以左孩子为根的子树是否满足小顶堆的要求,不满足递归向下处理
    • 右孩子最小,交换该非叶结点与其右孩子的值,并考察以右孩子为根的子树是否满足小顶堆的要求,不满足递归向下处理

二叉搜索树: 

左子树都比根节点小,右子树都比根节点大。

1) 插入: 不论如何插入的节点都会变成叶子节点。 所以只需要查找到要插入的位置,然后插入即可。

2) 删除: 若删除叶节点,则直接删除。若非叶子节点,则删除后将左子树的最大值(左子树的最右边节点)补在根节点位置或者右子树的最小值(右子树的最左边节点)。

 

posted @ 2021-01-01 16:46  彭张智写字的地方  阅读(496)  评论(0)    收藏  举报