07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树

b站视频:路飞IT学城
https://www.bilibili.com/video/BV1mp4y1D7UP

文章目录

  • #61 链表总结
  • #62 哈希表
  • #63 哈希表实现
  • #64 哈希表应用
  • #65 树的概念
  • #66 树的实例:模拟文件系统
  • #67 二叉树概念
  • #68 二叉树遍历
  • #69 二叉搜索树的概念
  • #70 二叉搜索树:插入

个人博客
https://blog.csdn.net/cPen_web

#61 链表总结

###### 链表——复杂度分析
# 顺序表(列表/数组)与 链表
  #   按元素值查找     #注:复杂度都是O(n)
  #   按下标查找       #注:顺序表快 顺序表O(1),链表O(n)
  #   在某元素后插入    #注:顺序表O(n) ,链表O(1)
  #   删除某元素        #注:顺序表O(n) ,链表O(1)

#注:顺序表:Python里的列表 ,C里的数组  是 挨个存的 

# 顺序表(列表/数组)与 链表
#   按元素值查找
#       注:复杂度都是O(n),一个一个查找,找到为止 (顺序表 不考虑排序)
#   按下标查找
#       注:顺序表快 顺序表O(1),链表O(n)因为下标是10,得从头开始数
#   在某元素后插入    顺序表O(n) ,链表O(1)
#   删除某元素        顺序表O(n) ,链表O(1) 
#注:链表相对于 顺序表,在插入和删除 2方面非常快
###### 链表与顺序表
# 链表在插入和删除的操作上明显快于顺序表
# 链表的内存可以更灵活的分配
#注:链表节点通过指针/next串起来,是分开的内存,而其他语言队列满了就没办法了
    # 试利用链表重新实现栈和队列
# 链表这种链式存储的数据结构对树和图的结构有很大的启发性

#62 哈希表

#注:哈希表(散列表)
#注:Python的字典、集合 都是用哈希表 实现的

###### 哈希表
# 哈希表一个通过哈希函数来计算数据存 储位置的数据结构,通常支持如下操作:
# insert(key, value):插入键值对(key,value)
# get(key):如果存在键为key的键值对则返回其value,否则返回空值
# delete(key):删除键为key的键值对
#注:这个操作 就是字典的操作
#注:哈希表有key、有value。没有value  只有key 那就是集合(而且key不能重复)
###### 直接寻址表
# 当关键字的全域U比较小时,直接寻址是一种简单而有效的方法。

07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第1张图片

#注:左边的圆 是 U全域 和 key。根据这个U建了一个T 列表
#注:插入很快,查找很快,删除很快(把元素置成空)

# 直接寻址技术缺点:
#   ·当域U很大时,需要消耗大量内存,很不实际   #注:T列表的大小 根据U的,很大的U  列表很大 浪费内存
#   ·如果域U很大而实际出现的key很少,则大量空间被浪费 #注:U很大,但字典值很少,浪费内存
#   ·无法处理关键字不是数字的情况 #注:key是字符串  有问题

#注:直接寻址表 上面 + 哈希函数,就成为 哈希表
###### 哈希
# 直接寻址表:key为k的元素放到k位置上   #注:比如 key是2的 就放到2的位置上。哈希不是
# 改进直接寻址表:哈希(Hashing)
#   ·构建大小为m的寻址表T
#   ·key为k的元素放到h(k)位置上
#   ·h(k)是一个函数,其将域U映射到表T[0,1,...,m-1]	#注:即 输出的值 是0到m-1,m是T列表的长度
#注:之前T 是根据U的大小建的,现在T 自己选个大小。之前 key是k的元素放到k的位置上,现在key为k的元素放到h(k)位置上
#注:h是个函数,能传的参数 是U域里所有的值,输出的值是  0到m-1,  m是T列表的大小
#注:h是哈希函数
###### 哈希表
# 哈希表(Hash Table,又称为散列表),是一种线性表的存储结构。哈希表由一个直接寻址表和一个哈希函数组成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标。
#注:哈希表是线性的,不是图 也不是树

# 假设有一个长度为7的哈希表,哈希函数h(k)=k%7。元素集合{14,22,3,5}的存储方式如下图。

哈希表

# 注:如果是直接寻址表,14存不了,只能存0到6
# 14 % 7 = 0 存到 0这个位置
# 22 % 7 = 1 存到1这个位置
# 3……、5……
# #注:哈希函数h(k)的取值范围  只能是0到6 (因为取余)
# #注:这就是非常经典的哈希函数,除法哈希

#注:再插入一个0 怎么办? 0 这个位置 已经有值了
###### 哈希冲突
# 由于哈希表的大小是有限的,而要存储的值的总数量是无限的,因此对于任何哈希函数,都会出现两个不同元素映射到同一个位置上的情况,这种情况叫做哈希冲突。
# 比如h(k)=k%7, h(0)=h(7)=h(14)=...

#注:哈希冲突一定会存在,因为开的哈希表  大小是有限的,肯定会有哈希函数2个key 对应1个位置。
#注:哈希函数 2个key 都到了一个位置上,这种现象叫做哈希冲突
###### 解决哈希冲突 -- 开放寻址法
# 开放寻址法:如果哈希函数返回的位置已经有值,则可以向后探查新的位置来存储这个值。
# 线性探查:如果位置i被占用,则探查i+1, i+2,……
# 二次探查:如果位置i被占用,则探查i+1^2 ,i-1^2 ,i+2^2 ,i-2^2 ,……
# 二度哈希:有n个哈希函数,当使用第1个哈希函数h1发生冲突时,则尝试使用h2, h3,……
#注:如果插入  有值 ,插入到其他地方。线性探查:i+1……;二次探查;二度哈希
#-----------
### 注:开放寻址法的 线性查找
#注:哈希表如何查值?比如:22,先找它的哈希函数 h(22),22%7=1,那就到1的位置上去找,直接找 找到了
#注:现在 比如说找0,0%7=0,0 位置找不到,怎么办?先在这查,如果不是 往后找,如果不是,往后找,直到找到空位为止。找到空位如果还没有找到,那么这个哈希表里面肯定没有0
#注:查找也需要 按照 线性探查方式 查找。插入的探查方式 和 查找的探查方式一样,到空位为止
#注:线性探查的效率并不是很高,它的装载因子过大。它的好多东西都特别密,很多东西都在一块
#-----------
### 注:开放寻址表的 二次探查
#注:i+1, i-1, i+4, i-4. i+9, i-9……
#注:它是跳着的,越跳越多
#-----------
### 注:开放寻址表 二度哈希
#注:有好多个哈希函数,如果一个哈希函数冲突了,换另一个

#注:开放寻址表  不太好,哈希表可能满了
###### 解决哈希冲突 -- 拉链法
# 拉链法:哈希表每个位置都连接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后。
#注:拉链法:哈希表的一个格子里不是存一个元素,而是存一个链表
#注:冲突元素  如果发现冲突了,就放到这个链表后面 或者前面
#注:插入元素,如果位置是空的,就往后接一个;如果不是空,就在链表最后面或者最前面插一个 (头插尾插 都可以)
#注:查找的话 ,比如 查 155 在这个表里是否有。首先带入哈希函数,发现值是11,那就到11这个位置上来找,然后  就遍历这个链表, 91 不是 ,155 找到了
#注:删除:删除 先找到之后  再删。找到155 后 把它删除,链表的删除  也是O(1)的
#注:拉链法很经典,虽然没有直接寻址表快,但是直接寻址表 可能实现不了
#注:查找复杂度 不好说:如果哈希函数 足够好 ,节点足够平均分到哈希表上,整个的 有n个数,表长度是m。一个位置 有n/m个元素,那最多 只要查n/m次
#注:图示的 哈希函数 是对 16取余数

07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第2张图片

###### 哈希表 -- 常见哈希函数 *
# 除法哈希法:
#   ·h(k) = k % m
# 乘法哈希法:
#   ·h(k) = floor(m*(A*key%1))
# 全域哈希法:
#   ·ha,b(k) = ((a*key + b) mod p) mod m   a,b=1,2,...,p-1

#注:除法哈希 很经典,最好理解的一种哈希。直接对哈希表的长度m 取余数,拿到的值 就是它的下标
#注:乘法哈希 k是 关键字,m是 哈希表大小 ,A是一个值(A是一个小数), A*key%1, A乘以k对1取模,对1取模 就是取它的小数部分,再向下取整。floor是向下取整,比如说 3.6 向下取3
#注:全域哈希 ha,b(k) = ((a*key + b) mod p) mod m   a,b=1,2,...,p-1
#注:a、b 2个参数 a*key + b,mod p 对p取模 (%),括起来对 m取模

#63 哈希表实现

#注:写哈希表 之前,先写个链表
#注:链表的创建
class LinkList: #注:链表类
    class Node: #注:链表里的节点
        def __init__(self, item=None):
            self.item = item
            self.next = None

    class LinkListIterator: #注:这个类是一个迭代器 因为 支持__next__
        def __init__(self, node):
            self.node = node

        def __next__(self):
            if self.node:   #注:如果node不是空
                cur_node = self.node
                self.node = cur_node.next
                return cur_node.item
            else:
                raise StopIteration
        def __iter__(self):
            return self

    def __init__(self, iterable=None):  #注:构造函数。传一个列表
        self.head = None
        self.tail = None
        if iterable:    #注:如果有列表
            self.extend(iterable)

#注:extend()接受一个列表参数  [1,2].extend([1,2,3]) [1,2,1,2,3]
#注:append()接受一个对象参数  [1,2].append([1,2,3]) [1,2,[1,2,3]]
    def append(self, obj):  #注:尾插
        s = LinkList.Node(obj)  #注:创建节点
        if not self.head:   #注:如果head是空
            self.head = s
            self.tail = s
        else:               #注:如果head不是空,插到尾巴上
            self.tail.next = s
            self.tail = s

    def extend(self, iterable): #注:循环调appdend 就有extend了
        for obj in iterable:
            self.append(obj)

    def find(self, obj):    #注:在链表里查找,for循环查
        for n in self:      #注:self是linklist对象,self是迭代的支持这种写法
            if n == obj:
                return True
        else:
            return False

    def __iter__(self): #注:写迭代器的 支持迭代
        return self.LinkListIterator(self.head)

    def __repr__(self):     #注:转换成字符串
        return "<<"+", ".join(map(str, self))+">>"
        #注:map对于可迭代对象的每个元素 转换成字符串str

lk = LinkList([1,2,3,4,5])  #注:可迭代对象
for element in lk:
    print(element)
#结果为
# 1
# 2
# 3
# 4
# 5
print(lk)
#结果 <<1, 2, 3, 4, 5>>
#链表创建 精简代码
class LinkList:
    class Node:
        def __init__(self, item=None):
            self.item = item
            self.next = None

    class LinkListIterator:
        def __init__(self, node):
            self.node = node

        def __next__(self):
            if self.node:
                cur_node = self.node
                self.node = cur_node.next
                return cur_node.item
            else:
                raise StopIteration

        def __iter__(self):
            return self

    def __init__(self, iterable=None):
        self.head = None
        self.tail = None
        if iterable:
            self.extend(iterable)

    def append(self, obj):
        s = LinkList.Node(obj)
        if not self.head:
            self.head = s
            self.tail = s
        else:
            self.tail.next = s
            self.tail = s

    def extend(self, iterable):
        for obj in iterable:
            self.append(obj)

    def find(self, obj):
        for n in self:
            if n == obj:
                return True
        else:
            return False

    def __iter__(self):
        return self.LinkListIterator(self.head)

    def __repr__(self):
        return "<<"+", ".join(map(str, self))+">>"
#注:在这基础上 写 哈希表

# 类似于集合的结构
class HashTable:
    def __init__(self, size=101):   #注:哈希表的构造函数,size哈希表的大小
        self.size = size
        self.T = [LinkList() for i in range(self.size)]   #注:开一个T列表,每个位置都是一个链表(拉链法)
        #注:刚开始T列表 每一个位置都是一个空链表 LinkList()

    def h(self, k): #注:哈希函数
        return k % self.size    #注:对self.size取模

    def insert(self, k):    #注:插入
        #注:计算哈希函数 返回的哈希值
        i = self.h(k)
        #注:k这个元素 要插到i这个位置上去
        #注:判断这个元素在不在里面
        if self.find(k):    #注:如果找到了,我就不插入。达到哈希 去重的目的
            print("Duplicated Insert.") #注:重复插入 提醒
        else:   #注:如果没找到,插入
            self.T[i].append(k) #注:插入

    def find(self, k):  #注:先写 查找函数
        i = self.h(k)   #注:先找到k的哈希值
        return self.T[i].find(k)    #注:T[i] 是个链表

#注:哈希表 删除功能 没写。写删除的话 链表就得支持删除的功能

ht = HashTable()    #注:创建HashTable对象

ht.insert(0)
ht.insert(1)
# ht.insert(0)
# #注:输入第3条语句时 ,提示 Duplicated Insert.
ht.insert(3)
ht.insert(102)
ht.insert(508)

print(",".join(map(str, ht.T)))
#注:打印这个哈希表,1和102在一个链表里,因为 哈希表的长度是101,102对101取余剩1
#注:508对101取余剩3
# <<0>>,<<1, 102>>,<<>>,<<3, 508>>,<<>>,<<>>,<<>>,…………

print(ht.find(3))
#结果为 True
print(ht.find(102)) #注:也能找到,它是个链表, 先去1那个位置上找,发现1 不是,102 是,找到了
#结果为 True
print(ht.find(203))
#结果为 False      #注:因为发现 1 不是,102 不是,没了  返回一个false

#注:集合实现 跟它差不错
#哈希表 精简代码

# 类似于集合的结构
class HashTable:
    def __init__(self, size=101):
        self.size = size
        self.T = [LinkList() for i in range(self.size)]

    def h(self, k):
        return k % self.size

    def insert(self, k):
        i = self.h(k)
        if self.find(k):
            print("Duplicated Insert.")
        else:
            self.T[i].append(k)

    def find(self, k):
        i = self.h(k)
        return self.T[i].find(k)

ht = HashTable()

ht.insert(0)
ht.insert(1)
ht.insert(3)
ht.insert(102)
ht.insert(508)

#print(",".join(map(str, ht.T)))
print(ht.find(203))

#64 哈希表应用

#注:哈希表 的应用:Python里的 集合和字典 底层就是哈希表
###### 哈希表的应用 -- 集合与字典
# 字典与集合都是通过哈希表来实现的。
#   ·a = {'name': 'Alex', 'age': 18, 'gender': 'Man'}
# 使用哈希表存储字典,通过哈希函数将字典的键映射为下标。假设h('name') = 3, h('age') = 1, h('gender') = 4,则哈希表存储为[None, 18, None, 'Alex', 'Man']	#注:3 1 4 号位置
# 如果发生哈希冲突,则通过拉链法或开发寻址法解决
#注:用哈希表来存,首先 把key传到 哈希函数里去(比如说key='name'),得到一个值  比如说等于3 ( h('name')=3 ),把'Alex'放到3号位置上 ……
#注:之前讲的哈希函数  它的参数 都是整数,字符串 也可以转换成一个整数,以某种方式

a = {'name': 'Alex', 'age': 18, 'gender': 'Man'}
h('name') = 3, h('age') = 1, h('gender') = 4
[None, 18, None, 'Alex', 'Man']             #注:哈希表存储

#注:如果说 字典 条目比较少的话,哈希函数够好,一个内存里 基本上不会拉链拉特别长,可能拉一个到两个,这样  你的按键查找会非常快,包括集合的查找
#注:一个列表 跟一个集合 去查,肯定是集合要快,因为这是 哈希表的查找速度指定的

#注:哈希表的应用 -- md5算法
#注:密码学 主要2个方面:1、加密 解密  2、哈希 比如说md5

07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第3张图片

#注:md5值 用的最多的是文件,可以用md5值 判断文件是不是一样的,如果这2个文件哈希值一样 其实不能说 这2个文件肯定一样,只能说很大很大概率 这2个文件一样。
#注:因为 md5值 也是一个哈希,有哈希  就一定有哈希冲突。文件可以哈希、字符串也可以哈希,什么都可以md5,哈希值一定是有限的,虽然是128位,2的128次方,但是必然会有2个文件的哈希值 是一样的。
#注:但是 md5值 要求 不能 有限的时间 人工构造,偶然碰上 概率太小
#注:防止的是 人工构造 ,或者说是欺骗
#注:md5 已经被破解了。在安全的方面不是特别重要的 地方可以用:比如 不太重要的网站、比较文件  ;但是  保密特别强的场合  不要用了

07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第4张图片

#注:2 云存储服务商  比如说 百度云 上传 电影

#注:密码学的哈希函数

07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第5张图片

#注:SHA-224 就是224位 ,256  就是256 位
#注:SHA-2、MD5 也是 哈希的一个应用,只不过 它的应用不是在存储方面,主要 用在 安全方面的一些校验 、比对 上

07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第6张图片

#注:哈希值不能反解的,相当于 证明SHA2 安全性 是不可破解的
#注:Python  hashlib库里 有对应的函数

#注:哈希表 是一个  很高效的 做查找的数据结构

#65 树的概念

#注:之前讲了很多 线性的 数据结构:列表、链表、栈、队列、哈希表
#注:接下来看  树状数据结构

#注:树是一种数据结构     比如:目录结构
#注:树是一种可以递归定义的数据结构
#注:树是由n个节点组成的集合:
    # 如果n=0,那这是一棵空树     #注:相当于递归的终止条件
    # 如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树

07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第7张图片

#注:n > 0,A是根节点,下面分了m个,m是6。最后都递归完了 汇拢在一起 发现是树

# 一些概念
    # 根节点    #注:汇拢起来,最头上那个节点
    # 叶子节点  #注:没有孩子的都叫做 叶子节点,因为叶子到头了
    # 树的深度(高度)  #注:示图 是4  往下走几层。A的深度是1,E的深度是2,J的深度是3,Q的深度是4
    # 树的度    #注:节点的度 就是这个节点  分几个叉,比如E节点分2个叉 这个节点的度是2;F节点的度是3
                #注:树的度  是这个树里边 所有节点的 最大的那个度。图示 树的度是 6
    # 孩子节点/父节点   #注:A是D的父亲节点,D是A的一个孩子节点
    # 子树      #注:E I J P Q 也是一棵树,以E为根节点的一棵树,这个树 是整个树的一棵子树,因为它是递归定义的,所以任意一个节点也是一棵子树  比如 节点 B

#66 树的实例:模拟文件系统

#注:树是由节点组成的,树的核心是节点
class Node: #注:先定义树的节点
    def __init__(self, name, type='dir'):
        self.name = name    #注:文件名
        self.type = type    #"dir" or "file"  #注:文件类型 默认dir
        #注:节点和节点之间的关联
        self.children = []
        #注:链表有一个self.next 找它的下一个但是 树 这个下面可能有很多 不是有一个
        #注:所以搞一个列表,好多next都放到这个列表里
        self.parent = None  #注:相当于 指向它的父母
        # 链式存储

    def __repr__(self):
        return self.name    #注:只打印 文件名

n = Node("hello")    #注:文件夹 hello
n2 = Node("world")
n.children.append(n2) #注:这样 把hello文件夹下面的world文件夹 连起来
#注:这个操作 就相当于单链表,只不过 这里有很多个next
#注:这就是从 hello 找到 world,children 把n 连到n2
n2.parent = n   #注:父母只有一个,树的结构决定的,所以不是列表
#注:这样 n2 也可以往回找了
#注:这样就有点像  双链表了。children往下找,相当于 链表 往后找;parent往上找,相当于链表的往前找
#注:一般来说  实现树的时候  children都是有的,parent属性有没有 看自己的需求。实现一棵只是往下链的树也行,只是不能往回找。往回找他的父亲的话,加一个parent

#注:linux文件系统
# /abc.txt
# /var/
class FileSystemTree:   #注:写 树的类
    def __init__(self): #注:初始化
        self.root = Node("/")    #注:首先 维护一个根
        self.now = self.root     #注:还需要 存现在 在哪个目录下

    #注:模拟构造操作系统的命令
    def mkdir(self, name):  #注:传一个name
        # name 以 / 结尾
        if name[-1] != "/": #注:如果name结尾不是斜杠
            name += "/"     #注:就补上 /
        node = Node(name)   #注:创建一个文件夹
        #注:再把这个文件夹 跟现在这个文件夹 连起来
        self.now.children.append(node)  #注:正着连上
        node.parent = self.now          #注:反着连过去

    def ls(self): #注:展示
        return self.now.children    #注:打印当前目录的children

    def cd(self, name): #注:切换目录  绝对路径与相对路径。这里 只可以进入下一级
        "../var/python/"
        #注:先按斜杠split 然后再一条一条进去,先执行.. 再循环执行这个函数
        #注:如果支持绝对路径,还是先按照斜杠split,然后 第一个斜杠 前面肯定是个空,判断如果有空,你不是从now 走,而是从root开始走
        #注:相对路径是从now开始走,绝对路径是从root开始走
        #注:如果是文件进来的话,还要控制文件,不能让他append东西
        if name[-1] != "/":
            name += "/"
        if name == "../":   #注:cd支持向上返回一级
            self.now = self.now.parent
            return
        for child in self.now.children: #注:如果children里有这个name
            if child.name == name:
                self.now = child    #注:就切换目录
                return
        raise ValueError("invalid dir")  #注:如果没有 就报错

tree = FileSystemTree()   #注:新建一个树
tree.mkdir("var/")
tree.mkdir("bin/")
tree.mkdir("usr/")
print(tree.root.children)
#结果为 [var/, bin/, usr/]
print(tree.ls())
#结果为 [var/, bin/, usr/]
tree.cd("bin/") #注:进入 bin目录
tree.mkdir("python/") #注:在bin目录下创建 文件夹python
print(tree.ls())
#结果为 [python/]
tree.cd("../")
print(tree.ls()) #注:cd 回去
#结果为 [var/, bin/, usr/]

#注:树 在绝大部分的存储,都是跟链表一样 链式存储。通过往下走 往后指childre,往上 指parent。通过节点和节点之间相互连接的方式 组成树的数据结构
#精简代码
class Node:
    def __init__(self, name, type='dir'):
        self.name = name
        self.type = type    #"dir" or "file"
        self.children = []
        self.parent = None
        # 链式存储

    def __repr__(self):
        return self.name

class FileSystemTree:   
    def __init__(self): 
        self.root = Node("/")    
        self.now = self.root    

    def mkdir(self, name):
        # name 以 / 结尾
        if name[-1] != "/":
            name += "/"
        node = Node(name)
        self.now.children.append(node)
        node.parent = self.now

    def ls(self):
        return self.now.children

    def cd(self, name):
        if name[-1] != "/":
            name += "/"
        if name == "../":
            self.now = self.now.parent
            return
        for child in self.now.children:
            if child.name == name:
                self.now = child
                return
        raise ValueError("invalid dir")

tree = FileSystemTree()  
tree.mkdir("var/")
tree.mkdir("bin/")
tree.mkdir("usr/")

tree.cd("bin/")
tree.mkdir("python/")

tree.cd("../")

print(tree.ls())
#结果为 [var/, bin/, usr/]

#67 二叉树概念

#注:二叉树 特殊的树,度不超过2的树,一个节点 最多分2个叉  有区别 左孩子和右孩子
#注:堆排序那里讲了 二叉树的线性存储方式,堆的存储  把数存成一个列表 [0, 1, 2, 3, ……],父亲找孩子 是 0 找 2i+1 ,2i+2。这种存储方式比较适用于完全二叉树(右边少,左边不会少东西)

07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第8张图片

#注:这个树  A左边少了好多节点,那这些节点就为空的,如果这个树非常不完全 差很多,那这些空的地方 就浪费空间,所以用另一种存储方式  链式存储
#注:跟树的存储方式是一样的。 只不过 刚才的树  是self.children是个列表,这里二叉树 不用列表 lchid ,rchild  2孩子
#注: lchid ,rchild  2 孩子 左孩子 右孩子 ,往下指2个指针。如果还需要往上面的话,加一个self.parent,不需要 就不用了
#注:data用来存节点的数据

###### 二叉树
# 二叉树的链式存储:将二叉树的节点定义为一个对象,节点之间通过类似链表的链接方式来连接。
# 节点定义:

节点
07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第9张图片

class BitreeNode:   #注:二叉树的节点
    def __init__(self, data): #注:构造函数
        self.data = data    #注:存放数据
        self.lchild = None  # 左孩子
        self.rchild = None  # 右孩子

a = BitreeNode("A") #注:手动创建节点
b = BitreeNode("B")
c = BitreeNode("C")
d = BitreeNode("D")
e = BitreeNode("E")
f = BitreeNode("F")
g = BitreeNode("G")

e.lchild = a    #注:连起来 ,e的左孩子
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f    #注:这个树连接完事了

root = e

print(root.lchild.rchild.data)  #注:打印root左孩子的右孩子 其实就是节点
#结果为 C
#精简代码
class BitreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None  # 左孩子
        self.rchild = None  # 右孩子

#68 二叉树遍历

#注:线性的数据结构 都比较好遍历 :列表 好遍历 for循环,链表 也好遍历 一直去next 取到空为止,栈、队列都好遍历
#注:二叉树怎么遍历?注:二叉树的遍历方式 有4种
###### 二叉树的遍历方式:
# 前序遍历:EACBDGF
# 中序遍历:ABCDEGF
# 后序遍历:BDCAFGE
# 层次遍历:EAGCFBD

class BitreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None  # 左孩子
        self.rchild = None  # 右孩子

a = BitreeNode("A") #注:创建节点
b = BitreeNode("B")
c = BitreeNode("C")
d = BitreeNode("D")
e = BitreeNode("E")
f = BitreeNode("F")
g = BitreeNode("G")

e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f

root = e

######------注:二叉树的 前序遍历
#注:这个其实是 递归的定义的
def pre_order(root):  #注:传一个节点进去
    if root:    #注:如果root不是空 (是空 就不打印了)
    #注:root是空 相当于 是递归的终止条件
        print(root.data, end=',')  #注:先打印根节点
        pre_order(root.lchild)  #注:然后访问它的左子树
        pre_order(root.rchild)  #注:然后访问它的右子树
#注:pre_order(root) 如果不是空,先访问根,再访问左子树,再访问右子树

pre_order(root)
#结果为 E,A,C,B,D,G,F,    #注:每个节点都打印一次
# 前序遍历:EACBDGF
#注:pre_order(root) 如果不是空,先访问根,再访问左子树,再访问右子树
#注:图示里面 先访问E,访问完了E, 再去访问 左子树A  递归的(A B C D) 再去访问右子树 (G F)
#注:在(A B C D) 里 ,先访问A ,然后递归左(没有) 递归右(B C D)
#注:(B C D) 里面 先访问 C ,再递归左 B 递归右 D
#注:访问G F ,先访问G ,递归左(没有),递归右(F)
#注:顺序 EACBDGF  前序遍历

遍历
07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第10张图片

######------注:二叉树的 中序遍历
#注:中序遍历 和 前序遍历不同的是 访问次序不一样
def in_order(root):
    if root:
        in_order(root.lchild)   #注:先 递归左子树 访问左子树
        print(root.data, end=",")   #注:然后访问自己
        in_order(root.rchild)   #注:然后 递归右子树
#注:先递归左子树 访问自己  递归右子树

in_order(root)
#结果为 A,B,C,D,E,G,F,
#注:和前序遍历 差别是 访问的顺序不一样了。没有前序遍历那么直观
#注:中序遍历不太直观
#注:先递归左子树 访问自己  递归右子树
#注:所以E在中间…… (先把根节点放中间) 如图

中序遍历
07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第11张图片

######------注:二叉树的 后序遍历
#注:前序遍历 访问自己在最前面;中序遍历 访问自己在中间;后序遍历 访问自己在最后
def post_order(root):
    if root:
        post_order(root.lchild) #注:先 递归左
        post_order(root.rchild) #注:然后 递归右
        print(root.data, end=",")   #注:最后 打印自己
#注:后序遍历,先 递归左;然后 递归右;最后 打印自己

post_order(root)
#结果为 B,D,C,A,F,G,E,
#注:先左(A B C D) 后右(G F) 最后自己 E ……
#注:先确定打印的 即最后的 放最后面

后序遍历
07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第12张图片

#注:这三种遍历 在面试中  被问到的比较多

#注:它还有一个别的类型的问题
#注:知道一棵二叉树的 前序遍历和 中序遍历,确定这棵树 并给出 后续遍历
#注:一但给2个遍历的序列,就能确定这个树

07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第13张图片

# 前序遍历:EACBDGF
# 中序遍历:ABCDEGF
#注:前序遍历 先访问自己,第1个一定是根  :E是根
#注:中序序列 根在最中间:知道E是根了 对应的去看中序序列;A B C D 是E的左孩子,G F 是E的右孩子
#注:前序序列 :A B C D 里,A是根  递归的
#注:再看 中序序列 :A B C D ,说明A 左子树 是空的,右子树 是 B C D
#注:接下来看B C D, 看前序序列 :C B D ,C是根
#注:再看中序序列, C是根  B C D ,B是C的左孩子,D是C的右孩子
#注:A、B、C、D 出来了,看G F
#注:看前序序列  G F,G是根
#注:F 看中序序列,G F ,F在右边,所以F是G的右孩子,G的左孩子是空
#注:这就是 前序序列 和 中序序列  还原出来这棵树,有了树之后,就可以画 后序序列了

#注:这是 给前中  可以推;同样给后序和中序 也可以画出来
#注:前序的时候 看第一个是根,后序的时候 最后一个是根,所以倒着看  同样的道理

#注:面试:实现 二叉树的 三种遍历方式:递归的写法。给2个序列,确定最后一个序列,或者说 给你2个序列,让你写函数 来构造出来这棵树
######------注:二叉树的 层次遍历
#注:很好理解  按层来  E  A G  C F  B D ,一层一层的 每一层 从左到右
#注:怎么写?用到 队列 :刚开始访问E  没有问题,E出队  接下来E的孩子们进队  A G进队,现在队列里是A G
#注:A出队,A的孩子C进队, 队列里剩下 G C;G出队 ,G的孩子F进队,队列里剩下 C F;接下来 C出队 ,B D 进队;F 出队,没有孩子进队;B出队 没有孩子进队;D出队 没有孩子进队
#注:所以这个序列就是 层次遍历的序列  :EAGCFBD
#注:层次遍历 不光适用于 二叉树  ,多叉树 也可以。二叉树 左孩子进队 右孩子进队;多叉树 children 挨个进队

from collections import deque   #注:使用之前queue队列
def level_order(root):
    queue = deque()   #注:首先创建空队列
    queue.append(root)  #注:然后 root进队
    while len(queue) > 0:   # 只要队不空,一直访问
        #注:先出队一个元素 ,然后把它的孩子进队
        node = queue.popleft()  #注:出队
        print(node.data, end=',')   #注:打印
        if node.lchild: #注:判断node的左孩子是不是有
            queue.append(node.lchild)   #注:如果有,就append  node左孩子
        if node.rchild: #注:如果node的右孩子有,它也进队
            queue.append(node.rchild)

level_order(root)
#结果为 E,A,G,C,F,B,D,
#精简代码
class BitreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None  # 左孩子
        self.rchild = None  # 右孩子

a = BitreeNode("A") #注:创建节点
b = BitreeNode("B")
c = BitreeNode("C")
d = BitreeNode("D")
e = BitreeNode("E")
f = BitreeNode("F")
g = BitreeNode("G")

e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f

root = e

#------ 前序遍历
def post_order(root):
    if root:
        post_order(root.lchild)
        post_order(root.rchild)
        print(root.data, end=",")

pre_order(root)
# 前序遍历:EACBDGF

#------ 中序遍历
def in_order(root):
    if root:
        in_order(root.lchild)
        print(root.data, end=",")
        in_order(root.rchild)

in_order(root)
# 中序遍历:ABCDEGF

#------ 后序遍历
def post_order(root):
    if root:
        post_order(root.lchild)
        post_order(root.rchild)
        print(root.data, end=",")

post_order(root)
# 后序遍历:BDCAFGE

#------ 层次遍历
from collections import deque
def level_order(root):
    queue = deque()
    queue.append(root)
    while len(queue) > 0:   # 只要队不空
        node = queue.popleft()
        print(node.data, end=',')
        if node.lchild:
            queue.append(node.lchild)
        if node.rchild:
            queue.append(node.rchild)

level_order(root)
# 层次遍历:EAGCFBD
#注:层次遍历 不光适用于 二叉树 还适用于 多叉树

#69 二叉搜索树的概念

#注:具体的 二叉树  一种用法:二叉搜索树
###### 二叉搜索树 (binary search tree  bst)
# 二叉搜索树是一棵 二叉树 且满足性质:设x是二叉树的一个节点。如果y是x左子树的一个节点,那么y.key ≤ x.key;如果y是x右子树的一个节点,那么y.key ≥ x.key。
# 二叉搜索树的操作:查询、插入、删除
#注:y.key 就是y上存的那个值,可以存键值对 ,也可以只存一个键。左子树上的 所有节点的值 都比它小,右子树上的 所有节点的值 都比它大
#注:一个节点  左子树上的 所有节点的值 都比它小,右子树上的 所有节点的值 都比它大。如果所有节点 都满足这个性质,那么 他就是二叉搜索树

07_Python算法+数据结构笔记-链表总结-哈希表-树-二叉树-二叉搜索树_第14张图片

#注:有了这个性质后,可以做的操作:查询、插入、删除

#注:有了这个性质后,查询 非常好查:比如说 查11 在不在这个树里面,先和根比,11比根小,所以如果11在的话  一定在根的左边;那就到左边查,跟5比,发现11比5大,如果11存在,那一定在5的右边;然后发现11 找到了
#注:找7   ,跟17比 ,往左边找;跟5比 往右边找;跟11比 往左边找;跟9比 往左边找;跟8比往左边找  但是8左边是空了,没有了 找不到了
#注:所以 查询操作  只要执行  树的深度次 就可以了

#注:插入操作: 插入32 ,插肯定往叶子节点插 ,往最下面这层插;32 跟17比 ,往右边走,插到它的右子树上来;跟35 比,插到它的左边来;跟29比 ,插到它的右边 ,右边只要没有 ,就插到这来
#注:插入和查询差不多,反正就是 往深处走,走到一个地方,只要这个地方没有了 ,那就插在这

#注:查询 和 插入 复杂度 大概 logn  跟二分查找不一样 但是差不多,每次都少一半

#70 二叉搜索树:插入

class BiTreeNode:   #注:树的节点
    def __init__(self, data):
        self.data = data
        self.lchild = None  # 左孩子
        self.rchild = None  # 右孩子
        self.parent = None  #注:查询和插入操作 不用它也行,但删除操作要用
        #注:加了parent就是双链表

class BST:
    def __init__(self, li=None):    #注:构造函数 可以传一个列表进来
        self.root = None    #注:构造函数 先创造一个根节点
        if li:  #注:如果li不是None
            for val in li:  #注:就使用非递归的方法  把val插进来 。因为递归方法 慢
                self.insert_no_rec(val)
    #注:递归的写法
    def insert(self, node, val):   #注:插入函数  ,node就是递归的插到哪个节点上去
        if not node:    #注:如果node是空,就找到这个位置了,把值插入
            node = BiTreeNode(val)  #注:相当于创建一个节点,但还没和树连起来
        #注:如果node不为空,分3种情况
        elif val < node.data:   #注:如果val比node节点小,往左走,递归调左孩子
            node.lchild = self.insert(node.lchild, val) #注:左孩子传进来,递归左孩子。并把值传给node.lchild 因为返回它自己
            #注:这是 插到node的左孩子里,让它两连起来;还要连parent
            node.lchild.parent = node  #注:它的左孩子的父亲是它
        elif val > node.data:   #注:如果 val > node.data , 递归右孩子,从右孩子里面找
            node.rchild = self.insert(node.rchild, val)
            node.rchild.parent = node   #注:连parent
        # else:   #注:第3种情况  如果说 等于了
        #注:如果说 等于的话,其实可以,但是一般情况下 默认键(值) 不能相等。等于 可以统一规定去右边(或者左边)。查找的话 只能说查找一个
        #注:一般说  要求是 键值对,键不能重复。如果有这种情况 但是键会重复 ,可以给这个节点 加一个数据域,叫做count  ,等于了 给这个节点count +1
        return node
        #注:这是 递归的写法

        #注:非递归的写法  一般来说 非递归的比递归的快一点
    def insert_no_rec(self, val):
        #注:首先 一层层往下找(指针也好,其他也好),找一个p
        p = self.root   #注:p刚开始等于root
        #注:如果来的节点 比它小,那p就往它左孩子走,……右孩子走
        if not p:   #注:空树的情况下,特殊处理下
            self.root = BiTreeNode(val) #注:直接给root赋值一个就可以了
            return
        while True: #注:如果不是空树,循环  3种情况
            if val < p.data:    #注:往左子树上走;需要判断它左子树上 有没有
                #注:如果左子树上有,那么p=左子树;如果左子树是None,就把val插到这
                if p.lchild:    #注:如果p的左子树 不是None
                    p = p.lchild    #注:p往左子树上走一下
                else:           #注:左孩子不存在
                    p.lchild = BiTreeNode(val)  #注:就把值插到这
                    p.lchild.parent = p #注:双向的指定 ,连接parent
                    return
            elif val > p.data:    #注:第2种情况,和上面一样的,把l换成r
                if p.rchild:    #注:如果 右孩子存在
                    p = p.rchild    #注:p就往右孩子上走
                else:           #注:如果 右孩子不存在
                    p.rchild = BiTreeNode(val)  #注:它的右孩子 就等于 一个新的节点
                    p.rchild.parent = p #注:再建立parent连接
                    return
            else:               #注:等于的情况,什么都不干
                return
                #注:Python的集合也是这样,插入的时候 如果重复,什么都不提示,但是它没有插入

    #注:前面讲的 三种遍历:前序遍历、中序遍历、后序遍历
    def pre_order(self,root):
        if root:
            print(root.data, end=",")
            self.pre_order(root.lchild)
            self.pre_order(root.rchild)

    def in_order(self,root):
        if root:
            self.in_order(root.lchild)
            print(root.data, end=",")
            self.in_order(root.rchild)

    def post_order(self,root):
        if root:
            self.post_order(root.lchild)
            self.post_order(root.rchild)
            print(root.data, end=",")

tree = BST([4,6,7,9,2,1,3,5,8])
tree.pre_order(tree.root)
print("")
tree.in_order(tree.root)
print("")
tree.post_order(tree.root)
#结果为
# 4,2,1,3,6,5,7,9,8,
# 1,2,3,4,5,6,7,8,9,
# 1,3,2,5,8,9,7,6,4,

#注:由前序和中序,可以画出这棵树,这肯定是一个满足二叉搜索树的情况
#注:中序遍历 1,2,3,4,5,6,7,8,9,  是排好序的,是巧合吗?
import random
li = list(range(500))
random.shuffle(li)
tree = BST(li)
tree.in_order(tree.root)    #注:中序序列  排好序的,不是巧合
#结果为 0,1,2,3,4,5,6,7,8,9,10

#注:二叉搜索树的 中序序列 一定是升序的
#注:中序序列 先左,再自己,再右。所以 先输出的 一定是整个序列最小的,因为 左孩子永远是最小的
#注:在 二叉搜索树里,左比中小,右比中大;左先出去 小的先出,然后中  右,从小到大
#精简代码
import random

class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None  # 左孩子
        self.rchild = None  # 右孩子
        self.parent = None

class BST:
    def __init__(self, li=None):
        self.root = None
        if li:
            for val in li:
                self.insert_no_rec(val)
    # 递归写法
    def insert(self, node, val):
        if not node:
            node = BiTreeNode(val)
        elif val < node.data:
            node.lchild = self.insert(node.lchild, val)
            node.lchild.parent = node
        elif val > node.data:
            node.rchild = self.insert(node.rchild, val)
            node.rchild.parent = node
        return node
    # 不递归的写法
    def insert_no_rec(self, val):
        p = self.root
        if not p:               # 空树
            self.root = BiTreeNode(val)
            return
        while True:
            if val < p.data:
                if p.lchild:
                    p = p.lchild
                else:           # 左孩子不存在
                    p.lchild = BiTreeNode(val)
                    p.lchild.parent = p
                    return
            elif val > p.data:
                if p.rchild:
                    p = p.rchild
                else:
                    p.rchild = BiTreeNode(val)
                    p.rchild.parent = p
                    return
            else:
                return
    # 三种排序
    def pre_order(self, root):
        if root:
            print(root.data, end=',')
            self.pre_order(root.lchild)
            self.pre_order(root.rchild)

    def in_order(self, root):
        if root:
            self.in_order(root.lchild)
            print(root.data, end=',')
            self.in_order(root.rchild)

    def post_order(self, root):
        if root:
            self.post_order(root.lchild)
            self.post_order(root.rchild)
            print(root.data, end=',')

li = list(range(500))
random.shuffle(li)

tree = BST(li)
tree.pre_order(tree.root)
print("")
tree.in_order(tree.root)
print("")
tree.post_order(tree.root)
#结果为
# 前序遍历……
# 中序遍历0,1,2,3,4,5,6,7,……
# 后序遍历……

你可能感兴趣的:(Python算法+数据结构笔记,python,数据结构与算法)