美文网首页
数据结构-408

数据结构-408

作者: 梦终无痕_311d | 来源:发表于2019-10-04 09:59 被阅读0次

    记录一些复习(主要是看视频课程)过程中遇到的难点和容易遗忘的点,以及一些之前不知道的小知识点之类。

    仅为个人笔记所用,不是知识点总结,慎重参考。


    线性表:
    1. 当要在指针 p 所指的结点之前进行插入操作时,可以先在 p 所指结点后进行插入,再 swap 两个结点的 data 域。
      当要删除 p 所指的结点时,可以先 swap p 和 p->next 的 data 域,再删除 p->next。
    2. 反转链表的两种方法:
      ①从首结点开始依次将所有结点插入到某个固定的结点后,即反转完成。
      ②使用三个指针, pre 指向前一个结点, cur 指向后一个结点, tmp 为临时指针。
      tmp=cur->next;
      cur->next=pre;
      pre=cur;
      cur=tmp;
      遍历完整个链表即可。
    3. 对于 n 个数据,以不同的次序全部入栈并出栈,有 f(n) 种出栈序列: f(n)=C(2n,n)/(n+1)。
    4. 中缀表达式转后缀表达式的方法:准备一个栈用来存放运算符,依次读取中缀表达式中的每个元素,
      ——若为数字,直接将其加入新表达式;
      ——若为(,则压入栈中;
      ——若为运算符,且栈为空或者栈中上一个元素为(或者栈中上一个运算符的优先级低于此运算符,则将此运算符入栈,
      否则,循环将栈中的运算符出栈并加入新表达式,直到满足上述条件;
      ——若为),循环将栈中的运算符出栈并加入新表达式,直到遇到(并将其出栈为止;
      ——若读取完毕,则将栈中剩余元素依次出栈并加入新表达式,直到栈为空。
      注:左右括号均不加入新表达式,出栈后直接丢弃即可。
    树:
    1. 非递归方式实现树的遍历。
      • 中序遍历。用的是王道视频里讲的思路,需要用到栈,详见王道视频课程。
        ①从根结点开始,依次压栈的同时一路遍历到最左端的结点;
        ②将栈顶元素出栈并加入中序序列,同时检查此元素是否有右孩子?若有,将右孩子压栈,然后自此处开始,依次压栈的同时一路遍历到最左端的结点;
        ③循环执行②,直到栈为空。

      • 后序遍历。自己想的思路,也许并不算好。
        需要用到一个栈和额外的存储空间保存当前结点是否已被访问过,若已被访问过则标记为 visited。
        ①根结点入栈;
        ②若栈顶元素没有标记为 visited,将其右孩子、左孩子(如果有的话)依次入栈,同时将其标记为 visited;
        若栈顶元素已被标记为 visited,将其出栈,加入遍历序列
        ③循环②,直到栈为空。

      • 先序遍历。与后序遍历流程一样,只是将元素加入遍历序列的时机不同。
        需要用到一个栈和额外的存储空间保存当前结点是否已被访问过,若已被访问过则标记为 visited。
        ①根结点入栈;
        ②若栈顶元素没有标记为 visited,将其右孩子、左孩子(如果有的话)依次入栈,同时将其标记为 visited,并加入遍历序列
        若栈顶元素已被标记为 visited,将其出栈;
        ③循环②,直到栈为空。
        也可利用后面深度优先遍历的思路

    2. 根据(先序遍历or后序遍历or层次遍历)and中序遍历序列确定一个二叉树:都是在中序遍历序列中寻找根结点。
    3. 二叉树线索化和线索二叉树的使用
      线索化的代码看得云里雾里,实在想不明白。关于线索化,我倒是一直觉得直接遍历一遍,遍历的同时完成线索化不就好了……如果考试时真要用到此代码,我就真的这么写了……
      以下为我自己的对于中序遍历线索化实现,与网上的其他代码相比复杂了许多,不过好歹完成了操作。
      中序遍历线索化的思考:对于每一个结点,如果只考虑其前驱结点,则有两种可能:
      ①此结点无左子树。此时它的前驱结点为其所在的某个“可能更大的子树”的双亲结点。
      ②此结点有左子树。此时它的前驱结点为其左子树最右端的结点。
      因此,只要想方设法与这两个结点建立联系,然后递归对树的所有结点执行此操作即可。此处采用自上至下的方式访问每个结点,因此可以将前驱结点为“祖先结点”的可能以参数的形式递归传递下来,将前驱结点为“子孙结点”的可能以返回值的形式传递上来。
    //返回值:当前子树的最右结点
    //cur:当前结点
    //pre:在cur->left==nullptr时会成为cur前驱结点的祖先结点
    TreeNode* inThread(TreeNode* cur, TreeNode* pre)
    {
        if (cur->left != nullptr)
        {
            //若左子树不为空,则继续线索化左子树,左子树的前驱结点依然为pre
            TreeNode* tmp = inThread(cur->left, pre);
            //与返回上来的前驱结点建立联系
            if (tmp->right == nullptr)
            {
                tmp->rtag = 1;
                tmp->right = cur;
            }
        }
        //若左子树为空,则直接与传递下来pre建立联系
        else
        {
            cur->ltag = 1;
            cur->left = pre;
            if (pre != nullptr && nullptr == pre->right)
            {
                pre->rtag = 1;
                pre->right = cur;
            }
        }
        //递归线索化右子树,同时将最右结点以返回值形式传递上去
        if (cur->right != nullptr)
        {
            return inThread(cur->right, cur);
        }
        else
        {
            return cur;
        }
    }
    

    使用线索二叉树中序遍历的方法:
    ①将指针移动到最左边的结点,即中序遍历的起点,访问此结点;
    ②右指针指向后继结点时直接访问;
    右指针指向右孩子时,先移动到右孩子,然后以此为起点移动到最左边的结点,访问此结点;
    ③重复②直到右指针为空。

    1. 对bst执行删除结点时的操作,对AVL树进行插入时的操作
      bst 树结点的删除:
      ①若待删除结点 z 为叶子结点,则直接删除;
      ②若待删除结点 z 只有一棵子树,则让 z 的子树代替 z;
      ③若待删除结点 z 有两棵子树,则让 z 的中序遍历序列的直接后继结点代替 z,再删除该直接后继结点。
      AVL 插入新结点:
      只对最小不平衡子树进行调整,四种平衡旋转详见书
    2. 并查集,哈夫曼树
      并查集要用到双亲表示法
      哈夫曼树,每次选取权值最小的子树合成新树。(求 n 叉树(n>2)的最小带权路径长度时,可按最后一章“最佳归并树”的构建方式,先补充权值为 0 的结点,再构建哈夫曼树!)详见全文最后三行
    图:
    1. 四种图的表示法——邻接矩阵,邻接表,邻接多重表(表示无向图),十字链表(表示有向图)。
    2. 在无权图邻接矩阵中,A^n[ i ][ j ] 为从 i 到 j 的长度为n的路径的数量。
    3. 广度优先搜索可以确定无权图的单源最短路径。
    4. 最小生成树算法——Prim,Kruskal;最短路径算法——Dijkstra,Floyd详见书
    5. 关键路径:
      从头至尾计算事件(结点)的最早发生时间;(取最大值)
      从尾至头计算事件(结点)的最晚发生时间;(取最小值)
      ③计算活动(边)的最早发生时间 = 其弧尾事件的最早发生时间;
      ④计算活动(边)的最晚发生时间 = 其弧头事件的最晚发生时间 - 边的权重;
      ⑤计算活动(边)的最晚发生时间 - 最早发生时间,等于 0 的则为关键路径。
    查找:
    1. 顺序查找,折半查找(二分查找),分块查找
      其中分块查找:
      ①将待查数据分成若干个块,要求每个块中的最大元素均小于下一个块中的最小元素,并记录每个块中最大元素的值;
      ②利用记录的最大值在分块间进行查找,确定目标元素所在的分块;
      ③在该分块内进行查找,找到目标元素或查找失败。
    2. m 阶 B 树的特性:
      • 每个结点最多有 m 棵子树;
      • 若根结点不是终端结点,则至少有两棵子树;
      • 除根以外的非叶结点至少有⌈m/2⌉棵子树。
        B 树的查找、插入、删除操作。
        插入的可能情况——直接插入,向上分裂
        删除的可能情况——终端结点(直接删除,兄弟够借,兄弟不够借)非终端结点(前驱、后继结点替换再删除,左右子树合并)

        详见课本或视频
        注:B 树的所有终端结点必须在同一层
    3. B+ 树和 B 树的区别:
      ①B+ 树的结点中n 个关键字对应 n 个分支,B 树的结点中n 个关键字对应 n+1 个分支
      ②B+ 树的叶结点包含所有的关键字,B 树中分支结点和叶子结点包含的关键字不重复;
      ③B+ 树的叶结点记录信息,分支结点仅起索引作用,记录子树的最大关键字和指向子树的指针。(类似分块查找)
      ④B+ 树有两种查找方式,可以从根结点开始查找,也可以从叶子结点顺序查找
      • 散列函数的选取——直接定址法、除留取余法、数字分析法、平方取中法、折叠法。
        除留取余法:Hash(key)=key%p,一般选<=表长的最大质数作为 p。
      • 处理冲突的方法——开放定址法(线性探测法、平方探测法、再散列法、伪随机法),拉链法
      • 填装因子 = 表中记录数 / 表长
    4. kmp算法
      参考视频
    排序:
    1. 9种内部排序算法的实现、时间复杂度、空间复杂度、稳定性
      在理解的基础上将书中的表熟记于心!
      总结:(折半插入参考直接插入)
      最好情况下时间复杂度为O(n)——直接插入,冒泡排序
      平均情况下时间复杂度为O(nlogn)——快排,堆排,归并
      最坏情况下时间复杂度为O(n^2)——快排
      空间复杂度——快排O(logn),归并O(n),基数O(r)
      不稳定排序——希尔,快排,堆排,简单选择

    注:关于做题的补充
    关于排序,被问过最多的一道题——下列选项中,不可能是快速排序第 2 趟排序结果的是 __,需要注意的几点就是:

    • 如果第一趟快排后确认的元素 k 的位置在序列中间,则第二趟排序将分别确认 k 左边和 k 右边的一个元素,即第二趟排序完成后会至少有 3 个元素在最终位置;
    • 如果第一趟快排后确认的元素 k 的位置在序列边缘(如最左边,升序的话就是 k 为最小值),那显然,第二趟排序只能确认 k 右边的一个元素,即第二趟排序完成后会至少有 2 个元素在最终位置;
    • 综上所述,若只有2个元素确认了最终位置,那么这2个元素中必定至少有一个元素在序列的边缘位置!!按此思路解题即可!

    1. 大顶堆/小顶堆的构建——从最末分支结点向根结点循环,每次均自上而下调节
      堆在排序中的操作——将根结点与末端结点互换,而后自根向下调节
      堆的插入——新元素插入末端,自下而上调节
    2. 外部排序
      • 败者树——可视为一棵完全二叉树。叶结点存数,每个分支结点保存当前“回合”的败者,最顶端保存胜者;当删去胜者时,从胜者所在序列取出新元素放入叶结点,从叶子结点向上调节,确立新的胜者。
      • 置换-选择排序——“只以递增的形式选择元素确立归并段”
      • 最佳归并树——只有度为 0 和度为 m 的结点。将置换-选择排序得到的归并段的长度作为权值,构建哈夫曼树,为最佳归并树。
        最佳归并树的总IO次数 = 带权路径长度之和 * 2(I一次O一次)
        当叶子结点不够时,增加权值为 0 的结点(虚段)。对于n 个结点构建 m 叉归并树:
        • 若 (n-1)%(m-1) == 0,则可以直接构建 m 叉归并树;
        • 若 (n-1)%(m-1) == u 不为 0,则需补充 m-u-1 个虚段。

    相关文章

      网友评论

          本文标题:数据结构-408

          本文链接:https://www.haomeiwen.com/subject/snsluctx.html