精华内容
下载资源
问答
  • 本文主要从使用与场景两方面来介绍Redis种基本数据类型与三种特殊数据类型,这些数据类型使用看着很简单,但还是要敲一遍才能更好了解。文末附带这八种数据类型的思维导图。 String String是Redis最基本...

    本文主要从使用与场景两方面来介绍Redis的五种基本数据类型与三种特殊数据类型,这些数据类型的使用看着很简单,但还是要敲一遍才能更好的了解。文末附带这八种数据类型的思维导图。

    String

    String是Redis最基本的数据结构,他采用K-V的形式来存储数据,当然了,虽然是String,但是他的value也可以为int、float形式,也可以存储json、图片等,但是不能超过521MB。

    使用场景:
    1、做缓存。
    2、计数,比如粉丝数、文章阅读量
    3、分布式session。

    在这里插入图片描述

    在这里插入图片描述

    Hash

    键值对形式的存储结构,可以使用hash来存储个人信息,比String更加直观清楚。

    在这里插入图片描述

    List

    与我们使用的list一样,他就是一个存储数据的队列,他可以存储多个有序的字符串,可以实现消息队列、栈、公众号的文章列表。

    在这里插入图片描述

    Set

    set与list大致类似,但是set是不允许有重复的元素。set有并集、交集等操作,可以使用交集来找到共同好友、关注人等。

    set也提供随机返回一个集合中的元素,可以使用这个性质来完成抽奖功能。

    在这里插入图片描述

    ZSet

    zset与set类似,但是他里面的元素是有序的,每个元素会设置一个score,根据这个score来进行排序。比如可以实现排行榜的功能。

    在这里插入图片描述

    Geospatial

    地理位置。通过经纬度可以计算得到两地之间的距离。也可以计算在某个位置的指定距离中有多少元素,比如附近的人的功能。

    在这里插入图片描述

    Hyperloglog

    基数,不可重复的元素。比如一个用户多次访问一个网站,会保存多个用户id,使用Hyperloglog可以将这些重复的用户id看作一个人,减少重复。

    在这里插入图片描述

    bitmap

    bitmap:位图。可以将key放在位图的指定位置,也就是offset,然后指定他的值,在位图中,值只能为0或者1。使用位图可以方便地进行统计数量、获取指定时间内地活跃用户。

    在这里插入图片描述

    上面的所有内容已转换为思维导图放到github上:
    https://github.com/PonnyDong/Mind/tree/master/src/redis
    格式为PDF。

    展开全文
  • 共性就是这三种基本形式都存在这共同的思维规则,即同一律、矛盾律、排中律。 2.同一律 同一律在逻辑学中尤为重要。同一律是地基第一块砖,矛盾律和排中律都是从同一律中得出来。同一律:在同一个思维中,每一...

    1.概述


    思维具有三种基本心时:概念、判断、推理。
    三种基本的形式有共性也有特性。共性就是这三种基本形式都存在这共同的思维规则,即同一律、矛盾律、排中律。

    2.同一律

    同一律在逻辑学中尤为重要。同一律是地基的第一块砖,矛盾律和排中律都是从同一律中得出来的。
    同一律:在同一个思维中,每一思想与其自身必须保持同一(内涵和外延必须保持一致)。
    通俗地讲,同一律就是说话不能含糊其辞,必须一是一,二是二;必须丁是丁,卯是卯。不能把不同的东西当成同一个东西看待,也不能把同一个东西当成不同的东西对待。
    对同一个东西有不同的理解。将一个字、一个词、一段话、一篇文章有着不同的理解,违反了同一律。
    我的理解:同一律本质上就是要求确定性。逻辑追求的是确定性。正是同一律保证了思维的确定性。
    人的情感系统、理性思维系统追求的都是确定性,确定性能给人带来安全感。
    -------------------------------------------------------------------
    违反同一律的行为,如果是无意的,就是混淆概念;如果是有意的,就是偷换概念。

     

    直觉和常识与科学相冲突的时候,科学总是鼓励人们抛弃直觉和常识,去相信科学。时至今日,科学依然是人们所找到的认识事物的最好工具。
    而科学是基于逻辑学的,逻辑学作为整个现代社会认识事物的方法论的基础地位,至今不能动摇。

    为什么说逻辑是思维的规律?
    逻辑仅仅是人的思维中存在的规律,客观事物本身不存在着什么同一律。【笛卡尔——“心无二物”     王阳明——“心无外物”】
    唯物和唯心

     

    3.矛盾律

    3.1 什么是矛盾律?

    在同一思维过程中,一个思想及其否定不能都是真的,必有一假。
    通俗地讲,在同一确定的前提下,一个人的言行不能既表达一种肯定,又否定了这种肯定,二者只能只能选择一个。
    矛盾律 = 不允许自相矛盾
    矛盾律是思维的规律,与事实如何无关(也就是说,客观事物无所谓矛盾),与动机如何无关。

    3.2 矛盾律和同一律是什么关系?

    矛盾律 = 同一律
    同一律公式: A = A
    矛盾律公式:A ≠ 非A

      3.3  矛盾律使用要点

        看问题的时候需要使用动态观点看问题(不一定是非此即彼)。

      

      形而上学是Metaphysics 翻译过来的。Metaphysics从字面上看是一门讨论基础存在的哲学。

      在孙正聿的《哲学通论》中叙述如下:人们通常是在两种不同的意义上使用“形而上学”这个概念:
      其一,是在近似于“哲学”的意义使用这个概念。在这个意义上,“形而上学”是一种追求和论证超验的“存在”即超越经验的关于世界的统一性原理的理论。由于传统的思辨哲学家都把“哲学”视为关于超验的世界统一性的理论,所以他们也在这个意义上把“形而上学”视为哲学的同义词或代名词。
      其二,是在与“辩证法”相对立的意义上使用“形而上学”这个概念。在这个意义上,“形而上学”是指一种以否认矛盾的观点看待世界的哲学理论,是指一种在“绝对不相容的对立中思维”的思维方式。
     
    3.4  对违反矛盾律的几种情况的总结
    本来结果在逻辑上是依赖于前提的,如果同时让前提依赖于结果,那么就有可能导致悖论。
     

    4.排中律

     

    4.1 什么是排中律
      在同一思维过程中,两个互为矛盾的思想不能同假,必有一真。
      排中律公式:A或者非A、
      这里,A可以是一个概念,也可以是一个判断。

     

    4.2 同一律、矛盾律和排中律之间的关系

     


    如果学了半天逻辑学,什么也没有记住,只要记住“逻辑追求的是确定性”就行了。

     

      4.3  理解排中律的注意事项
      1)排中律针对的是两个互为矛盾的思想或者观点而言。
      2)排中律要求不能都否定。
      3)排中律要求必选其一。

      

    5. 充足律

      

      

    转载于:https://www.cnblogs.com/helloIT/articles/4996536.html

    展开全文
  • 二叉树的遍历是二叉树的基本操作,也是不仅是面试的常考考点,也是程序员用来锻炼思维的小把戏。 二叉树的定义是递归的,即满足如下条件的树是二叉树: 一棵树当中的每个节点,最多有2棵子树; 如果一个节点有子树...

    二叉树及其数据结构定义

    二叉树是计算机当中最重要的数据结构之一,其应用非常广泛,例如数据库的索引使用的B+树是一种特殊的二叉树,堆排序所使用的堆是一种特殊的二叉树,Java当中HashMap使用的红黑树是一种特殊的二叉树。可见,二叉树在计算机编程当中有着重要地位。二叉树的遍历是二叉树的基本操作,不仅是面试的常考考点,也是程序员用来锻炼思维的小把戏。

    二叉树的定义是递归的,即满足如下条件的树是二叉树:

    1. 一棵树当中的每个节点,最多有2棵子树;
    2. 如果一个节点有子树,那么子树必须是二叉树。

    我们可以看到,二叉树的定义是递归的,递归的边界是一个节点没有子树。如下图所示,是一棵二叉树:
    在这里插入图片描述
    可以看到,二叉树当中的每个节点,有值,也可能有左子节点,也可能有右子节点,因此我们经常用二叉链表的形式存储。上述二叉树在内存当中将以以下形式存储。
    在这里插入图片描述
    图中,每个节点对应三个字段,或者叫域(field),包括:值(val)、左子节点的地址(left)、右子节点的地址(right),地址用ax表示,表示节点x的内存地址(address)。如果一个节点没有左子节点,那么对应的字段为null

    二叉树的节点对应的Python类可以写成如下形式:

    # 节点的数据结构
    # val表示值、left表示当前节点的左子节点地址、right表示右子节点地址
    class Node(object):
        def __init__(self, val, left, right):
            self.val = val
            self.left = left
            self.right = right
    

    既然二叉树用上面这种形式存储起来了,那么给定一个根节点(上图当中的第1个节点,值为1的节点),我们就可以知道这个二叉树的所有信息了,这和链表类似:对于一个单链表,只给一个头指针节点,就可以知道这个单链表的所有信息。因此,当我们说给定一个二叉树的时候,其实就是给定一个二叉树的根节点,因为知道根节点就知道这棵二叉树的全部了。

    三种递归方式

    二叉树的遍历方式可以分为两类:深度优先遍历(Depth First Search, DFS)和广度优先遍历(Breadth First Search, BFS)。广度优先遍历BFS会在后续的博客中再谈,而深度优先遍历又包含前序、中序、后序遍历,是今天的重点。

    前序、中序、后序遍历中的“序”指的是在遍历过程中,根节点遍历的时间,相对左子树和右子树的次序。三者的定义分别如下:

    • 前序遍历:先遍历根节点,再遍历左子树,最后遍历右子树。(根节点在前)
    • 中序遍历:先遍历左子树,再遍历根节点,最后遍历右子树。(根节点在中)
    • 后序遍历:先遍历左子树,再遍历右子树,最后遍历根节点。(根节点在后)

    说明:我们规定,无论是哪种顺序,左子树和右子树的顺序是,先左子树后右子树。这两个的顺序不是很关键,不重要。

    可以看到,上面三种遍历方式的定义,也是递归的,“遍历左子树”、“遍历右子树”,我们知道二叉树的某个节点的子树,也是二叉树,这就是递归,但是问题规模减小了,所以这个递归是可解的。那么,递归边界是什么?边界是:当一棵二叉树的左子节点和右子节点,都不存在,都为null的时候,就不继续往下遍历了。

    前序遍历

    针对上图的例子,前序遍历的过程如下:

    首先遍历根节点1,如下图,将1存储在结果数组的最开始位置。然后发现有两棵子树,先不考虑两棵子树怎么遍历,我们只需要知道,左子树遍历的结果排在数组当中根节点1的后面,右子树的遍历结果存储在左子树遍历结果的后面,如下图所示。
    在这里插入图片描述
    上图中,绿色长条表示我们编程时候使用的结果数组。遍历分为三大步骤:

    1. 先遍历根节点,把根节点1,存储在数组最开始的位置;
    2. 然后遍历左子树,把左子树进行前序遍历(具体怎么遍历先不考虑),将结果存储在1的后面;
    3. 最后遍历右子树,将右子树进行前序遍历(具体怎么遍历先不考虑),将结果存储在左子树遍历结果的后面。

    那么左子树、右子树该怎么遍历呢,采用和上述步骤相同的步骤。拿左子树举例,如下图所示:
    在这里插入图片描述

    左子树也是一棵二叉树,根节点是2,新的根节点有左右两棵子树。类似地,这棵树的遍历分为三大步骤:

    1. 遍历根节点2,写到结果数组的最开始,注意这里的最开始依然是在上图中上半部分的数组中的节点1的后面;
    2. 遍历2的左子树,将结果写到节点2的位置的后面;
    3. 遍历2的右子树,将结果写到上一步的后面。

    因此,这棵树的遍历结果是:2 (4) (5 7),括号表示左子树和右子树,展开就是2 4 5 7

    而原二叉树的右子树的遍历结果是:3 () (6),其中括号()表示左子树是空的,没有元素,展开就是3 6

    所以将(2 4 5 7)(3 6)追加到根节点1的后面,就得到了原二叉树的前序遍历结果:1 (2 4 5 7) (3 6)

    下面再来看中序遍历和后序遍历。方便起见,二叉树的图再贴到下面:
    在这里插入图片描述

    中序遍历

    上面我们已经得到了前序遍历的结果是1 (2 4 5 7) (3 6),其中(2 4 5 7)是左子树的前序遍历的结果,(3 6)是右子树前序遍历的结果。中序遍历的结果就是先遍历左子树,再遍历根节点,最后遍历右子树。那么是不是可以根据前序遍历直接得到中序遍历的结果呢:(2 4 5 7) 1 (3 6)?不能!因为这里要求左子树和右子树也必须是中序遍历的,而(2 4 5 7)是前序遍历的结果。

    中序遍历的结果,可以分为三大步骤:

    1. 中序遍历左子树:先遍历4,再遍历2,最后遍历5和75相对7是根节点,7相对5是左子树,应该先遍历7,再遍历5,所以是(7 5))。因此左子树中序遍历的结果是:(4) 2 (7 5)
    2. 遍历根节点:即1
    3. 中序遍历右子树:右子树是36,发现这颗子树当中,根节点3的左子树为空,那么就遍历根节点3,再遍历右子树6,而右子节点6没有子树了,结束了。所以这颗右子树的中序遍历结果是:3 (6)

    所以,上述二叉树的中序遍历结果是:(4 2 7 5) 1 (3 6) 。打括号知识为了标记谁是子树,最终结果不需要括号。

    后序遍历

    后序遍历,将根节点放到最后,那么大致的顺序是(2 4 5 7) (3 6 ) 1,根节点1的位置确定放到最后了。

    左子树(2 4 5 7),根节点是2放到最后,剩下左子树4(遍历左子树,发现为空,遍历右子树,发现为空,最后遍历根节点4)和右子树(5 7)(遍历根节点5的左子树7,遍历右子树为空,最后遍历根节点5),因此右子树(5 7)的后续遍历结果是(7 5)。因此这整个左子树的结果是(4 7 5 2)

    右子树(3 6)先遍历6,最后遍历3,因此是(6 3)

    所以,上述二叉树的后序遍历结果是:(4 7 5 2) (6 3) 1。打括号知识为了标记谁是子树,最终结果不需要括号。

    递归实现

    根据上面的讨论,我们已经了解了三种遍历方式。三种遍历的定义本身就是递归的,因此用递归的方式编写相对更简单,三种遍历方式的递归实现Python代码如下(经测试有效):

    # 全局变量记录遍历的结果
    result = []
    
    # 前序
    def dfs_before(root):
        if root == None:    # 遇到None,说明不用继续搜索下去
            return
        result.append(root)
        dfs_before(root.left)
        dfs_before(root.right)
    
    
    # 中序遍历
    def dfs_middle(root):
        if root == None:
            return
        dfs_middle(root.left)
        result.append(root)
        dfs_middle(root.right)
    
    
    # 后序遍历
    def dfs_after(root):
        if root == None:
            return
        dfs_after(root.left)
        dfs_after(root.right)
        result.append(root)
    
    
    # 节点的数据结构
    # val表示值、left表示当前节点的左子节点地址、right表示右子节点地址
    class Node(object):
        def __init__(self, val, left, right):
            self.val = val
            self.left = left
            self.right = right
    
    
    if __name__ == "__main__":
        node7 = Node(7, None, None)
        node5 = Node(5, node7, None)
        node4 = Node(4, None, None)
        node2 = Node(2, node4, node5)
        node6 = Node(6, None, None)
        node3 = Node(3, None, node6)
        node1 = Node(1, node2, node3)
    
        # dfs_before(node1)
        # dfs_middle(node1)
        dfs_after(node1)
    
        for e in result:
            print(e.val, end=' ')
        print()
    

    非递归实现

    既然已经有了递归方式的实现,为什么还要使用非递归形式实现遍历呢?拿Java举例说明(Java当中的方法相当于函数),函数存储在栈当中,每个函数对应一个栈帧,这个栈帧存储着函数签名、局部变量和引用、前驱函数签名、后继函数签名,前驱和后继函数签名记录着函数嵌套的关系,当一个函数执行完毕,其对应栈帧立即出栈,继续执行下一个函数。

    然而,栈的大小是有限的,而且一般被设置成较小的值,因为里面存储的是临时信息。递归就是函数自己调用自己,如果递归深度为10,就有10个栈帧存储在栈当中,虽然这些栈帧对应同一个函数。因此,如果递归深度太深,容易造成栈溢出。例如数据库索引使用的B+树的深度是100,再递归搜索的时候需可能要存储100个栈帧,容易造成栈内存溢出。

    所以,在大规模场景下,为了程序能够稳定运行,一般不允许使用递归,甚至在工程代码中写递归代码的程序员可能面临着失业。所以,非递归的方法是有必要掌握的。非递归算法中,一个函数就对应一个栈帧,没有嵌套回溯等过程,不会占据大量的栈内存空间。而为了将递归的算法写成非递归形式,有两种办法,第一种是用循环就可以实现,例如斐波那契数列的非递归实现只需要循环就可以改成非递归形式。第二种是使用栈,这里的栈是用户定义的对象,不会存放在线程栈当中,而是存放在堆当中,堆的空间一般比较大,比栈大很多,而且可调节,可以放心地使用。

    前序遍历的非递归实现

    下面拿前序遍历举例实现非递归的遍历。

    我们需要一个栈记录遍历的状态,还需要一个结果数组用于记录遍历的结果。方便起见,不再用数字表示节点,以字母表示节点,二叉树示意图再次贴在下面。
    在这里插入图片描述
    这是初始时刻状态,进行了初始化操作:将根节点A入栈,并追加到结果数组当中。

    1. 如下图左侧。获取栈顶元素top=Atop.left=B,判断B不在结果数组当中,没有被遍历过,因此入栈并追加到结果数组当中,如下图右侧所示。
      在这里插入图片描述
    2. 如下图左侧。获取栈顶元素top=Btop.left=D,判断D不在结果数组当中,即D没有被遍历过,因此入栈并追加到结果数组当中,如下图右侧所示。
      在这里插入图片描述
    3. 如下图左侧。获取栈顶元素top=Dtop.left=Null为空,继续判断top.right=Null为空,说明对 以当前节点为根节点的子树 的遍历已经结束,当前这个节点D出栈,如下图右侧所示。
      在这里插入图片描述
    4. 如下图左侧。获取栈顶元素top=B,判断左子树top.left=D,发现D已经在结果数组当中,即说明D已经被遍历过。左子树已经被遍历过,判断右子树top.right=E,发现E没有被遍历过,入栈并追加到结果数组当中,如下图右侧所示。
      在这里插入图片描述
    5. 如下图左侧。获取栈顶元素为top=E,判断左子树top.left=G发现G不在结果数组当中,即没有被遍历过,应该入栈,并追加到结果数组当中,如下图右侧所示。
      在这里插入图片描述
    6. 如下图左侧。获取栈顶元素为top=G,判断左子树top.left=Null为空,继续判断右子树top.right=Null为空,说明对 以G为根节点的子树 的遍历已经结束,应当将G出栈,如下图右侧所示。
      在这里插入图片描述
    7. 如下图左侧。获取栈顶元素为top=E,判断左子树top.left=G已经被遍历过了,继续判断右子树top.right=Null为空,说明对以E为根节点的子树 的遍历已经结束(看到了吗,是不是对E的判断有点多余,早该出栈了,这里先按下不表,在后记中会进行讨论),应当将E出栈,如下图右侧所示。
      在这里插入图片描述
    8. 如下图左侧。获取栈顶元素为top=B,判断左子树top.left=D已经被遍历过了,继续判断右子树top.right=E已经被遍历过了,说明对以B为根节点的子树 的遍历已经结束,应当将B出栈,如下图右侧所示。(看到了吗,是不是觉得对B的判断有点多余,早该出栈了,这里先按下不表,在后记中会进行讨论)
      在这里插入图片描述
    9. 如下图左侧。获取栈顶元素为top=A,判断左子树top.left=B已经被遍历过了,继续判断右子树top.right=C没有被遍历过,将C加入到栈当中,并追加到结果数组当中,如下图右侧所示。
      在这里插入图片描述
    10. 如下图左侧。获取栈顶元素top=C,判断左子树top.left=Null为空,继续判断右子树top.right=F发现F不在结果数组当中,没有被遍历过,应当将F入栈并追加到结果数组当中,如下图右侧所示。
      在这里插入图片描述
    11. 如下图左侧。 获取栈顶元素为top=F,判断左子树top.left=Null为空,继续判断右子树top.right=Null为空,说明对 以F为根节点的子树 的遍历已经结束,应当将F出栈,如下图右侧所示。
      在这里插入图片描述
    12. 如下图左侧。获取栈顶元素为top=C,判断左子树top.left=Null为空,继续判断右子树top.right=F已经遍历过了,说明对 以C为根节点的子树 的遍历已经结束,应当将C出栈,如下图右侧所示。
      在这里插入图片描述
    13. 如下图左侧。获取栈顶元素为top=A,判断左子树top.left=B已经遍历过了,继续判断右子树top.right=F已经遍历过了,说明对 以A为根节点的子树(其实就是整棵树) 的遍历已经结束,应当将A出栈,如下图右侧所示。
      在这里插入图片描述
    14. 如下图左侧。获取栈顶元素失败,因为栈空了,遍历结束。
      在这里插入图片描述

    上述过程对二叉树的前序遍历可以总结为如下几条规则:

    初始化:如果根节点为空,结束遍历。如果根节点不空,将根节点入栈,追加到结果数组当中。

    循环获取栈顶元素top,不同情况采取下面不同的动作,直到栈为空,退出即可。

    1. 如果top.left为空或者top.left不空但被遍历过,判断top.right
      1.1 如果top.right为空或不为空但已经遍历过了,则将top出栈。
      1.2 如果top.right不空且没有被遍历过,则将top.right入栈,并追加到结果数组当中。
    2. 如果top.left不为空且没有被遍历过,则将top.left入栈,并追加到结果数组当中。

    1、 如果一个节点没有被遍历过,应该入栈,并且被追加到结果数组当中。入栈是为了下次循环遍历 以这个节点为根节点的子树,因为我们每次循环总是获取栈顶元素;追加到结果数组当中是因为这是前序遍历,遍历这棵子树就要最先遍历这个节点(即这棵子树的根节点)
    2、如果栈顶元素的左、右节点为空或者已经都被遍历过了,总之就是如果栈顶元素的左、右子树不用再管了,说明栈顶元素应该出栈了。(这里有个小优化在后记中提到)

    前序遍历的非递归实现Python代码如下所示(测试通过):

    # 前序遍历非递归实现:完全按照上述规则进行编写
    def dfs_before_nonrecursive(root):
        s = []
        result = []
        if root==None:
            return []
        s.append(root)
        result.append(root)
    	
    	# 循环获取栈顶元素
        while len(s) > 0:
            top = s[-1]	# 获取栈顶元素
            if top.left == None or (top.left!=None and top.left in result):
                if top.right==None or (top.right!=None and top.right in result):
                    s.pop(len(s)-1)	# 只有左右子树都完事了,才可以将当前元素出栈。
                else:	# 如果左子树完事,但是右子树不空且没有被遍历过,那么就入栈并加入结果数组
    	            # s.pop(len(s)-1)	# 这就是后记当中所说的优化:右子树遍历之前就将根节点出栈
                    s.append(top.right)			# 入栈
                    result.append(top.right)	# 遍历它
            else:	# 如果左子树不空且没有被遍历过,那么就入栈并加入结果数组
                s.append(top.left)				# 入栈
                result.append(top.left)			# 遍历它
        return result
    
    
    # 节点的数据结构
    # val表示值、left表示当前节点的左子节点地址、right表示右子节点地址
    class Node(object):
        def __init__(self, val, left, right):
            self.val = val
            self.left = left
            self.right = right
    
    
    if __name__ == "__main__":
        node7 = Node('G', None, None)
        node5 = Node('E', node7, None)
        node4 = Node('D', None, None)
        node2 = Node('B', node4, node5)
        node6 = Node('F', None, None)
        node3 = Node('C', None, node6)
        node1 = Node('A', node2, node3)
    
        result = dfs_before_nonrecursive(node1)
    
        for e in result:
            print(e.val, end=' ')
        print()
    

    结果是:

    A B D E G C F 
    
    Process finished with exit code 0
    

    中序遍历的非递归实现

    中序遍历和前序遍历有什么不同呢?就是栈当中的元素,我们获取栈顶元素后,栈顶的元素不一定被遍历过了。在前序遍历当中,栈当中的元素一定是被变过的,因为是前序遍历,而中序遍历中,栈当中的元素需要等到其左子树完事了,才可以被遍历,然后再遍历右子树。

    总结一下:

    初始化:如果根节点为空,结束遍历。如果根节点不空,见根节点入栈,暂时不要追加到结果数组当中。

    循环获取栈顶元素top,判断:

    1. 如果左子节点top.left为空或top.left不空但top.left不空且已经遍历过了,那么就判断栈顶元素top是否已经遍历,如果没有则追加到结果数组当中,如果有则进行下一步:判断右子树的情况。如果右子节点为空或已经遍历过了,则将栈顶元素top出栈;如果右子节点top.right不空且没有被遍历过,则将top.right入栈,暂时不要追加到结果数组当中,因为是中序遍历。
    2. 如果左子节点top.left不空且没有被遍历过,那么就将top.left入栈,暂时不要追加到结果数组当中,因为是中序遍历。

    中序遍历的非递归实现Python代码如下所示(测试通过):

    # 中序遍历非递归实现
    def dfs_middle_nonrecursive(root):
        s = []
        result = []
        if root == None:
            return []
        s.append(root)
        while len(s) > 0:
            top = s[-1]
            if top.left == None or (top.left!=None and top.left in result):
                if top not in result:
                    result.append(top)
                if top.right == None or (top.right!=None and top.right in result):
                    s.pop(len(s)-1)
                else:
                	s.pop(len(s)-1)		# 这就是后记当中所说的优化:右子树遍历之前就将根节点出栈
                    s.append(top.right)
            else:
                s.append(top.left)
        return result
    
    
    # 节点的数据结构
    # val表示值、left表示当前节点的左子节点地址、right表示右子节点地址
    class Node(object):
        def __init__(self, val, left, right):
            self.val = val
            self.left = left
            self.right = right
    
    
    if __name__ == "__main__":
        node7 = Node('G', None, None)
        node5 = Node('E', node7, None)
        node4 = Node('D', None, None)
        node2 = Node('B', node4, node5)
        node6 = Node('F', None, None)
        node3 = Node('C', None, node6)
        node1 = Node('A', node2, node3)
    
        result = dfs_middle_nonrecursive(node1)
    
        for e in result:
            print(e.val, end=' ')
        print()
    

    结果是:

    D B G E A C F 
    
    Process finished with exit code 0
    

    后序遍历的非递归实现

    后续遍历,类似的道理,总结一下规律:

    初始化:如果根节点为空,结束遍历。如果根节点不空,见根节点入栈,暂时不要追加到结果数组当中。

    循环获取栈顶元素top,判断:

    1. 如果左子节点为空或左子节点已经被遍历过了则判断:
      1.1 如果右子节点不空且没有被遍历过,则将右子节点入栈
      1.2 如果右子节点为空或已经遍历过,那么此时左右子树都完事了,判断当前节点是否已经被遍历过了,如果没有则追加到结果数组当中并出栈;如果有,则直接出栈。
    2. 如果左子节点不空且没有被遍历过则将左子节点入栈。

    后序遍历的非递归实现Python代码如下所示(测试通过):

    # 后续遍历的非递归实现
    def dfs_after_nonrecursive(root):
        result = []
        s = []
        if root == None:
            return []
    
        s.append(root)
        while len(s) > 0:
            top = s[-1]
            if top.left == None or (top.left!=None and top.left in result):
                if top.right !=None and top.right not in result:
                    s.append(top.right)
                else:
                    if top not in result:
                        result.append(top)
                        s.pop(len(s)-1)
                    else:
                        s.pop(len(s)-1)
            else:
                s.append(top.left)
        return result
    
    
    if __name__ == "__main__":
        node7 = Node('G', None, None)
        node5 = Node('E', node7, None)
        node4 = Node('D', None, None)
        node2 = Node('B', node4, node5)
        node6 = Node('F', None, None)
        node3 = Node('C', None, node6)
        node1 = Node('A', node2, node3)
    
        result = dfs_after_nonrecursive(node1)
    
        for e in result:
            print(e.val, end=' ')
        print()
    

    结果是:

    D G E B F C A 
    
    Process finished with exit code 0
    

    后记

    1. 上述代码当中有一个值得思考的地方,在前序遍历和中序遍历当中都有这个问题。拿前序遍历举例,如果某个节点top左子节点完事了,之后需要遍历右子节点,如果是前序遍历的话,此时可以直接让top出栈,然后再遍历右子树,不然后面遍历完右子树之后,在右子节点出栈之后,还会面临着栈顶元素为top,还需要再继续判断,那做法肯定让top出栈,这么做逻辑没问题但这就多了一次循环,没这个必要,不如在遍历右子树之前就将top出栈,反正总要出栈的而且不用判断。有兴趣的朋友可以对比一下循环次数,如果不提前出栈,上述这个例子要循环13次;如果提前出栈,要循环10次:无论前序还是中序。
    2. 这篇博客的代码,我们使用Pythonlist数据类型作为栈和数组使用,Python的这个数据类型功能的确很丰富,还能做队列,在后面讲BFS的博客当中会看到。
    展开全文
  • 解决IP地址耗尽问题措施有以下三种: ①采用无类别编址CIDR,使IP地址分配更加 合理; ②采用网络地址转换(NAT)方法以节省全球IP地址; ③采用具有更大地址空间新版本IPv6。 其中前两种方法只是延长了IP.

    文章目录

     


    0.思维导图

    在这里插入图片描述

    1.为什么要有IPV6?

    解决IP地址耗尽问题的措施有以下三种:

    • ①采用无类别编址CIDR,使IP地址的分配更加
      合理;
    • ②采用网络地址转换(NAT)方法以节省全球IP地址;
    • ③采用具有更大地址空间的新版本的IPv6。

    其中前两种方法只是延长了IPv4 地址分配结束的时间,只有第三种方法从根本上解决了IP地址的耗尽问题
    在这里插入图片描述

    2.IPV6的数据报格式

    • 在此之前先复习一下IPV4的数据报格式,以便做对比
      在这里插入图片描述
      在这里插入图片描述
      IPV6的数据报格式:
      在这里插入图片描述
      在这里插入图片描述

    3.IPV6与IPV4的比较

    • 虽然IPv6与IPv4不兼容,但总体而言它与所有其他的因特网协议兼容,包括TCP、UDP、ICMP、IGMP、OSPF、BGP和DNS,只是在少数地方做了必要的修改(大部分是为了处理长的地址)。

    在这里插入图片描述

    4.IPV6的地址表示形式

    在这里插入图片描述

    5.IPV6基本地址类型

    IPv6数据报的目的地址可以是以下三种基本类型地址之一.:

    • 1)单播。单播就是传统的点对点通信。
    • 2)多播。多播是一点对多点的通信,分组被交付到一-组计算机的每台计算机。
    • 3)任播。这是IPv6增加的一-种类型。任播的目的站是-组计算机,但数据报在交付时只交付其中的一台计算机,通常是距离最近的一台计算机。
      在这里插入图片描述

    6.IPV6向IPV4过滤的策略

    IPv4向IPv6过渡可以采用双协议栈隧道技术两种策略

    在这里插入图片描述

    展开全文
  • 逻辑学是一个哲学分支学科。其是对思维规律的研究。逻辑和逻辑学的发展,经过了具象逻辑—抽象逻辑—具象逻辑与抽象逻辑相统一的对称逻辑三大阶段。...从逻辑学角度看,抽象思维的三种基本形式是概念,命题和推理。
  • 逻辑学

    2019-05-22 15:17:38
    从逻辑学角度看,抽象思维的三种基本形式是概念,命题和推理。 逻辑学作为一门科学的逻辑,是既古老又年轻的。历史悠久,源远流长。它有三大源泉:古希腊的形式逻辑,中国先秦的名辩逻辑,古印度的因明。 概念:给...
  • 第一:求极限无论数学一、数学二还是数学,求极限是高等数学的基本要求,所以也是每年必考内容。区别在于有时以4分小题形式出现,题目简单;有时以大题出现,需要使用方法综合性强。比如大题可能需要用到等价...
  • 从零开始学Python(基本数据类型 思维导图: 一、数字类型及操作 1、整数类型:与数学中整数概念一致 ①可正可负,没有取值范围限制 ②pow(x,y),计算x^y ③4进制表示形式:十进制,二进制(以0B或0b开头)...
  • 金字塔思维学习总结

    千次阅读 2018-09-19 12:15:30
    4 归纳的三种逻辑顺序 41 时间顺序应用场景和形式 42 结构顺序的常见应用 5 归纳顺序案例 搭建金字塔结构的两种方式 1 自上而下 2 自下而上 3 两种方式结合使用   很久之前在公司参加的培.....
  • 它在形式上是参赛双方就某一问题进行辩论竞赛活动,实际上是围绕辩论问题而展开知识竞赛,思维反映能力竞赛,语言表达能力竞赛,也是综合能力竞赛。2 基本要素 参赛人员 近年来流行大型...
  • 面向对象的基本概念

    2020-12-29 22:03:02
    面向对象:编码种思维方式’’ 面向过程:注重过程(一步一步实现某个功能) 面向对象:注重结果(把所有和这个对象相关所以功能都封装在一个对象中,使用时候直接调用就可以) 组成: 属性 — 特征描述,...
  • 从技术方面来讲,VR的基本原理是利用软件模拟真实场景形成一个维空间虚拟空间,有视觉效果、听觉系统、触感等感观仿真系统,从而让用户有身临其境体验感。通过VR技术应用,新闻媒体能够领着受众群体进到...
  • 、代码验证 要保证架构稳定和成功,利用代码对架构进行验证是一实用手段。代码验证核心是测试,特别是单元测试。而测试的基本操作思路是测试优先,它是敏捷方法中非常重要一项实践,是重构和稳定核...
  • 图片:图虫创意全自动甘特图excel模板 .xls甘特图其内在思想简单,基本是一条线条图,横轴表示时间,纵轴表示活动(项目),线条表示在整个期间上计划和实际活动完成情况,它直观地表明任务计划在什么时候进行,及实际...
  • 当它找到一抽象来定义事物时,旧事物哪怕没有形式与内容上变化,却在思维框架中有了新位置、新理解,以及新矛盾与冲突。而所谓问题,就来自这些外在视角变化和内在冲突产生。架构目标最终就是...
  • 在多类回归模型中,基于自变量和因变量类型,数据维数以及数据其它基本特征情况下,选择最合适技术非常重要。 以下是你要选择正确回归模型关键因素: 1. 数据探索是构建预测模型必然组成部分...
  • 代码调整是一局部的思维方式;基本上不触及算法层级;它面向是代码,而不是问题; 所以:语句调整,用汇编重写、指令调整、换一语言实现、换一个编译器、循环展开、参数传递优化等都属于这一级; 这个级别...
  • 通过本篇的阅读,你将获得以下五个埋点设计思维的认知。漏斗思维层次思维扩展思维分类思维0x01 漏斗思维漏斗思维即分阶段思维,是从流水线的角度考虑问题,追踪整个链条,具体有以下两种形式。* 同一事件的不同阶段...
  • 面对无限的三维空间和可能的高维空间,本文提出了一假设思维,通过运用辩证法的基本形式,通过物理学,神学和许多其他研究领域,倡导人类放弃内部冲突,以便形成一个统一的星球文明。
  • 关于Python那些话

    2019-10-04 12:10:34
    1、第一个选择:版本2还是3,我选择2,保守谨慎,3成熟周期会很长2、三种基本的文本操作: 2.1、解析数据并将数据反序列化到程序数据结构中 2.2、将数据以某种方式转化为另一种相似的形式,数据本身发生了改变。...
  • 除了监督学习和无监督学习,强化学习是三种基本的机器学习范式之一。 数据科学 数据分析和机器学习已成为现代科学方法中不可或缺一部分,它提供了基于过去观察来预测现象自动化程序,揭示了数据基本模式并...

空空如也

空空如也

1 2 3 4 5 ... 11
收藏数 208
精华内容 83
关键字:

思维的三种基本形式