zoukankan      html  css  js  c++  java
  • 《数据结构》张明瑞 清华大学 计算机科学与技术专业 大二

    授课教师:邓俊辉 

    教师介绍:清华2014年度十大优秀老师!MOOC上数据结构公开课报名已超5万人!你还不来看

    笔记简介:哈希表,红黑树,斐波那契二分查找,从基本到高端的数据结构全都有! 有重点,有插图,还有鄙人学习感悟,看完这笔记中的战斗机你还想啥!

    20140603

    ————————————————————

    ADT是抽象数据类型,为一组数据模型,加上一组操作,不涉及具体的储存方式,就像是用户使用的产品(黑箱),只考虑抽象层面

    DS是数据接收,是根据特定语言实现ADT的算法,设计复杂度和各种具体机制。

    (计算幂次方的方法:

    pow(a,b)

    we can devide b into binary type and pow(a,2^n) = pow(a,2^(n-1))

    so we can do it in O(log2 n), each time rotate 1 time.

    vector 向量数据结构,可以对不同数据类型操作,并且封装了许多操作,比如remove(r),sort(),search(e)无序去重,deduplicate(),uniquify()有序去重,traverse()遍历

    search(n) 返回不大于n的元素的最后的rank

    put(a,b) 将rank为a的数改为b

    平均复杂度:根据数据结构个操作出现概率分布进行加权平均,每个操作作为独立事件,割裂了相关性与连贯性

    分摊复杂度:将数据结构连续地实施足够多的操作,所需总体成本分摊至单次操作。对一系列操作整体考量,更加精确。

    vector 插入算法:

    从固定插入位置开始,先扩容,然后从rank = n downto 固定位置k

    vector[n] = vector[n-1]逐个后移

    最后执行插入。

    vector 删除:

    区间删除:

    删除从lo 到 hi 的区间

    则执行如下:

    _elem[lo++] = elem[hi++]

    即从前向后逐次移位

    不能更改次序,因为如果要删除的区间与想向前移动的区间有重复区间,从后向前的话会覆盖掉重复区间的部分。

     

    有序相向量的唯一化

    低效版:

    有序向量,重复的元素构成一个区间,因此每个区间保留一个即可。

    于是可以从前向后依次检查来逐个删除相同元素

    O(N^2)

    高效版:

    成批删除雷同元素,将他们视为一个整体。

    i = j = 0

    如果二者不相等,则_elem[++i] = _elem[++j],相当于忽略了所有重复元素(没有直接显示调用删除)。

    _____________________________________

    Fabonacci 查找法:

    由于二分查找在大于、小于和等于的情况下比较次数不同,所以造成内部比较次数的不平均,因此为了平衡比较和查找次数,可以构建一个看起来不平衡的数列来平衡比较次数。这就是Fabonacci查找法

     

    while(lo < hi)

    {

         while (hi - ol < fib.get()) fib.previous();

         rank mi = lo + fib.get() - 1;

         if (e < A[mi]) hi = mi;

         else if (A[mi] < e) lo = mi + 1;

         else return mi;

    }

    return -1;

    证明:运用递推*

    ————————————————————————

    二分查找的改进版:

    为了隐藏比较次数,只比较一次:

    while(1 < hi - lo)

    {

    rank mi = (lo + hi) >>1;

    ( e < A[mi] ) ? hi = mi : lo = mi; (不再是 mi + 1)

    }出口时 hi = lo + 1

    return (e == A[lo]) ? lo : -1;

    只是平均情况减少,但最好情况增加。

    最优化版:如果不在其中,还可返回最近的小于此元素的坐标

     

    冒泡排序改进:可以记录前面是否有逆序对,如果没有就证明不用再排序了

    再改进:可以增加一个last,也就是最后一个逆序对的位置,然后以后就只用排开始到last位置的元素了。

    ————————————————————————

    第三章:列表

    双向列表:两个哨兵:header trailer(-1 和 n)

    列表的查找:没有高效方法,只能按位置依次向后找。

    列表的排序:

    选择排序

    一直找当前最大的。

    以及 插入排序

    一次找一个,然后进行对比排序。

    逆序对个数:可以决定插入排序的复杂度

    设想,当一个元素前面有n个比他大的元素

    则在插入排序的时候,会从后向前比较n次。

    一次这时候设总逆序对为I,复杂度就为O(I+N)

     

    ____________________________________

    第四章:队列与栈

     

    应用:进制转换

     

    括号匹配

    栈混洗:将A栈中元素移动到B栈中,通过中间栈S,规定:只能移动到S再全部移动到B

    那么有多少种方法呢?打个比方,1先入栈,然后又入了一些,则此时让一些栈,则当1出栈时,假设前面已经进入了k个元素,则后面n-k个只能排列在B的后n-k个位置上

    所以这样推算,也就得到了递推关系:S(N) = ∑S(K-1)S(N-K)

    这样一个递推数叫做Catalan数,其结果为 (2n)!/(n!(n+1)!)  

    推导过程详见:

    http://blog.sina.com.cn/s/blog_497689ad01000azu.html

    判断是否是一个栈混洗

    O(n)的算法:直接借助A,B,S模拟混洗过程,运用贪心的原则,如果每次S.pop()时候已经空了或者需要弹出的元素不在S最顶端,则是非法的。

    栈混洗与括号的联系:

    一个栈混洗的过程可以表示为一个合法的括号表达式:

     

    每次push看作是(,pop看作是)

    由此可以预见:有多少种栈混洗结果,n对括号可以构成的表达式(合法)也就有多少种。

    中缀表达式求值:

    将数字压入栈,并且当遇到运算符时,先判断之前是否有运算符的优先级比当前运算符更大,如果是则首先进行上一个运算符的运算,然后递推进行判断再前一个运算符(运算级相同也要运算),直到不成立,就接着压栈。直到最后到头(压尽元素)。

    引入两个栈。

    优先级操作符的比较:制表

     

    遇到小于则入栈,遇到大于则计算(栈顶元素 < > = 当前元素)

    》》》》》》》》》》》》》》》》》》

    部分习题笔记:

    1.归并排序的算法复杂度证明:O(nlogn)

     

    2.归并排序的优化:

    对于两段已经排好顺序并且合起来也已经有序的情况,我们只需要增加一条语句:

    if (a[mi-1] <= a[mi]) merge(lo,mi,hi);

    3.链表访问的优化:

    由于对数据结构的操作往往都限定于一个较小的子集,所以可以将每次查找的链表元素移到首元素。在这种情况下,经常被访问的元素将集中在前端,大大提高访问效率。

     

    第四章 树

    1 树的表示方法:

    可以将各个节点组成数组结构,包含孩子节点数据集与父节点的标号,如果有某个节点孩子节点,那么此节点里面的孩子节点数据集(可以为列表或者向量)就存储又大到小的孩子。再存储此节点的父亲节点。这时候向下查找与孩子的数目线性相关,向上查找与深度有关。

     

     

    改进:每个节点主需要记录两个引用:纵向的firstchild以及横向的nextsibling(相邻兄弟),这时候储存结构的规整性大大增加。

    2 二叉树:

    真二叉树:每个节点都是2个度或者0个度(如果不够则在下面补满2个孩子)。更加规整(实际操作其实是假想的,并不存在)。

    如何用二叉树来 描述多叉树

    将长子作为左节点,次子作为右结点。

    二叉树的表示:

    binode类:表示树的结点

     

    父亲、左右孩子、高度、颜色(红黑树)、npl(左式堆)

    父亲、孩子均为引用。

    树形结构最重要的就是遍历

    定义一个树类,里面有树根(节点类)、树的高度、规模、判空函数、各种遍历方法、子树的删除插入与分离等。

    前序遍历:

    。。。。。。。

     

    VLR顺序 

    中序:LVR

    后序:LRV

    即V(父亲)的顺序在哪里就是什么序遍历

     

    先序遍历:

    visit(x->data);

    traverse(x->lchild,visit);

    traverse(x->rchild,visit);

    O(N)复杂度

    但是递归在运行栈中占用空间很大。

    所以非常有必要从递归改为迭代。

    注意:尾递归

    化解为迭代:

    利用栈的方法,既然需要先处理左边,就先把右边压入栈

     

    新的构思:

    先便利左侧链,在自下而上遍历右子树。

     

    每次访问左孩子,然后将右孩子一次压入栈。

    想法:其实也可以先储存A,B表示当前层的两个节点,对A(左)节点判断有无孩子,如果有,则访问,并且A = LA, B = RA; 反之访问B结点,并且A = LB, B = RB;如果都没有,B为父节点的右兄弟节点,然后再次寻找。(自编)

    对于 中序遍历:

    同样构建一个栈,存储右子树,

    不同的是,左子树一直遍历,同时一直压入栈,直到尽头,取节点值

    这时候再在返回后再pop弹出栈顶节点的值,然后将此节点右结点变为活跃节点继续进行左子树遍历。

    对于 后序遍历:

    首先将根压栈(因为根没有右结点),然后依次:如果有左节点,就把右结点压栈,再将左节点,如果没有,就把右结点压栈,直到为空;然后对当前节点进行pop,如果pop!=父节点,那么就代表pop的是右结点,就向右结点下方继续遍历,如果是,就直接pop然后输出;

    我的方法:先向左搜索到头,然后一路push,最后的直接输出,将当前节点改为父节点的右结点,在一路push,最后push后子节点已经为空,返回。输出。然后再将top(此时出来的为父节点)的节点与刚刚输出的节点比较,如果相同,证明父节点的所有子节点已经遍历完了,就再pop输出父节点,然后继续push;如果不相同,证明刚刚输出的是左端,右结点还没有遍历,遍历右结点。直到栈空。

    树的重构:

    中序遍历+先序/后序遍历 任意一种 便可以忠实还原原来的树形结构。

    (分而治之,找到左右子树)

     

    证法:归纳假设

    对于真二叉树,可以使用先序+后序还原。(分而治之)

     

    测试题:

    并查集:

     

    第六章:图

    第一部分 图的表示

    邻接矩阵

    邻接表:每个顶点有一个链表,链表储存了他的相邻接点(出度)

    也可用数组的方式来表示,一个数组A[]表示点,A[1]表示A的第一个相邻接点,一个数组B[]表示A[]中每个点的临界点个数,比如B[N]表示A[N]的临界点个数,然后提取A[N]相邻接点信息时可以这样做:

    IF (B[I++] != -1)  A[B[I]] ......

    <div style="word-wrap: break-word; font-family: 微软雅黑, 'Microsoft Yahei', 'Helvetica Neue', 'Hiragino Sans GB', 宋体, simsun,

  • 相关阅读:
    kubernetes集群系列资料20-metric介绍
    kubernetes集群系列资料19-dashboard介绍
    kubernetes集群系列资料18--K8S证书
    kubernetes集群系列资料16--helm介绍
    云安全产品使用---文件存储
    kubernetes集群系列资料15--安全机制介绍
    kubernetes集群系列资料14--scheduler介绍
    kubernetes集群系列资料17--prometheus介绍
    云安全产品使用---云安全中心
    kubernetes集群系列资料13--存储机制介绍
  • 原文地址:https://www.cnblogs.com/freenovo/p/4469801.html
Copyright © 2011-2022 走看看