学习python中的数据结构
创始人
2024-02-24 12:06:07
0

数据结构

链表和数组

  • 数组

    Python的list是由数组来实现的

    有序的元素序列, 在内存中表现为一块连续的内存区域;

  • 链表

    通过指针将无序的列表链接起来, 每个节点都存储着当前节点的值下一个节点的内存地址

    wwFny.png

  • 链表和数组有什么区别?

    • 实现有序的方式是不一样的, 数组是连续的内存. 链表通过持有下一个节点的内存地址来达到有序的目的;
    • 基于上述的特性, 数组在进行增删改查的时候钥耗费大量的系统资源来移动元素, 而链表只需要修改保存的地址即可.

栈的特点是后入先出LIFO last in first out

可以将栈想象为一个有底的玻璃瓶, 那我们存取东西都必须遵守后入先出.

wGeMl.png

队列

队列的特点是先入先出FIFO first in first out

可以将队列想象为一个没有封口的玻璃管, 但是该玻璃管只有一个口可以添加元素, 一个口吐出元素. 那么队列获取元素必然遵守先入后出.

wGLnn.png

散列表

python中的dict本质就是散列表

散列表也叫hashmap. 通过将key值映射到数组中的一个位置来访问. 这个映射函数就叫做散列函数, 存放记录的数组也叫散列表

wqqUG.png

树和堆

树是一种特殊的链表结构, 每个节点下有若干个子节点

wG0V7.png

  • 树的分类

    CZN2O.png

  • 二叉树

    每个节点下最多只有两个节点

    wGDW2.png

    • 平衡二叉树

      二叉树节点下可以只有一个子节点, 如果二叉树中节点1-> 2 -> 3 -> 4 -> 5, 那么当前的树结构退化成了链表, 为了解决这么一个情况, 就有了平衡二叉树.

      平衡二叉树的任意节点的左子树的高度与右子树的高度差不可以超过1.

      wGa0P.png

      • 红黑树

        因为平衡二叉树要严格保证左右子树的高度不超过1, 在实际场景中, 平衡二叉树需要频繁地进行调整.

  • 二叉堆

    二叉堆是一个完全二叉树, 满足当前任意节点要<=或者>=左右子节点, 一般使用数组来实现.

    • 最大堆

      当前任意节点要>=左右子节点

      wGMxS.png

    • 最小堆

  • B树

    • B树解决了什么问题?

      B树的目的是在搜索树的基础上优化了磁盘获取的效率

      大部分数据查询的瓶颈在磁盘IO上, 从磁盘中读取1kb数据和1b数据消耗的时间基本是一样的, 在平衡二叉树的基础上, 每个节点尽可能多地存储数据

      wGpND.png

  • B+树解决了什么问题?
    为了优化B树的查找速度, B树的每一个节点都是数据, 而B+树非子节点存储的是数据的地址(索引值), 子节点存储的是数据, 而且子节点会指向相邻的子节点, 都成一个有序链表.

    B树适合作文件系统. B+树适合作遍历和查找.

    wGkhL.png

链表

通过指针将无序的列表链接起来. 每个节点都存储着当前节点的值下一个节点的地址

  • 链表的缺点

    链表的查找是从头节点逐个节点遍历, 查找效率低.

  • 链表的应用

    • 系统的文件系统

      我们存放在磁盘上的文件并不是连续的, 我们通过链表来对文件进行目录归类.

    • git的提交节点

    • 其他数据结构的基础, 比如树结构

链表的种类

  • 单链表

    wwFny.png

  • 双链表

    ww1B3.png

  • 环形链表

    whkhQ.png

单链表的实现

  • 声明Node类(自定义一个数据结构Node)

    class Node:def __init__(self, data):self.data = dataself.next = Nonedef __str__(self):return f""
    
  • 实现LinkedList数据结构

    class LinkedList:def __init__(self):self.head = Noneself.end = self.headdef append(self, node):"""向链表尾部添加一个节点1. 尾部添加: end.next -> node, end -> node2. 当前头部没有节点: head -> node:param node::return:"""if not self.head:self.head = nodeelse:self.end.next = nodeself.end = nodedef insert(self, index, node):"""向index值插入一个节点1. 插入的是中间节点: 找到index值的节点, cur.next -> node   node->next2. 遍历的过程当中, 结果index值超过了当前链表的长度, 我们抛出异常3. 在头部插入节点: head -> node, node.next = head4. 在尾部插入节点: 跟中间节点是一样的, 但是end -> node:param index::param node::return:"""# 在原index值元素的左边插入 -> 在原index-1值对应元素的右边插入cur = self.headif index == 0:node.next = self.headself.head = nodereturnfor i in range(index-1):cur = cur.nextif cur is None:raise IndexError("LinkedList insert node exceed max length")node.next, cur.next = cur.next, nodeif node.next is None:self.end = nodedef remove(self, node):"""通过遍历删除给定的节点1. 移除的是中间节点: cur.next -> None, prev.next -> cur.next2. 移除的是头节点: head -> cur.next, cur.next -> None3. 移除的是尾节点: cur.next本身指向的就是None,和1一致, end -> prev:param node::return:"""cur = self.headprev = Nonewhile cur:if cur.data == node.data:if prev is None:self.head = cur.nextelse:prev.next = cur.nextcur.next = Noneif prev and prev.next is None:self.end = prevreturnprev = curcur = cur.nextdef reverse(self):"""翻转当前链表1. 中间节点: cur.next -> prev2. 头节点: cur.next -> prev3. 尾节点: cur.next -> prev4. 处理原本的head和end:return:"""# 能被翻转说明链表长度 > 1if self.head and self.head.next:cur = self.head.nextprev = self.head# 原本头节点的next需要断开self.head.next = None# 原本头节点就变成了尾节点self.end = prevwhile cur:# 这里设计到next,cur, prev三个节点, 所以引入中间变量nextnext = cur.nextcur.next = prevprev = curcur = next# 翻转后,头节点指向了原本的尾节点self.head = prevelse:returndef __str__(self):"""通过遍历的方式打印当前链表初始节点为Head, 如果当前指针指向的是NULL, 说明我们到达了结尾:return:"""cur = self.headresult = ""while cur:result += str(cur) + "\t"cur = cur.nextreturn resultif __name__ == "__main__":node_1 = Node(1)node_2 = Node(2)node_3 = Node(3)node_4 = Node(4)linked_list = LinkedList()linked_list.append(node_1)linked_list.append(node_2)linked_list.append(node_3)# linked_list.insert(3, Node(1.5))linked_list.append(node_4)# linked_list.remove(Node(1.5))linked_list.reverse()print(linked_list)
    

链表和数组

  • 数组

    Python的list是由数组来实现的

    有序的元素序列, 在内存中表现为一块连续的内存区域;

  • 链表

    通过指针将无序的列表链接起来, 每个节点都存储着当前节点的值下一个节点的内存地址

    wwFny.png

  • 链表和数组有什么区别?

    • 实现有序的方式是不一样的, 数组是连续的内存. 链表通过持有下一个节点的内存地址来达到有序的目的;
    • 基于上述的特性, 数组在进行增删改查的时候钥耗费大量的系统资源来移动元素, 而链表只需要修改保存的地址即可.

栈的特点是后入先出LIFO last in first out

可以将栈想象为一个有底的玻璃瓶, 那我们存取东西都必须遵守后入先出.

wGeMl.png

  • list来实现一个栈

    class MyStack:"""栈有固定长度, 需要考虑栈溢出和空栈的情况top属性用来指示栈顶的索引值"""def __init__(self, _size=10):self.stack = []self.top = -1self.size = _sizedef is_full(self):return self.top == self.size - 1def push(self, item):"""首先判断当前栈是否已满:param item::return:"""if self.is_full():raise Exception("StackOverflow")self.stack.append(item)self.top += 1def is_empty(self):return self.top == -1def pop(self):"""首先要判断当前栈是不是空栈:return:"""if self.is_empty():raise Exception("StackUnderflow")self.top -= 1return self.stack.pop()if __name__ == "__main__":# 1 + 2 * 3my_stack = MyStack()my_stack.push(1)my_stack.push("+")my_stack.push(2)my_stack.push("*")my_stack.push(3)# 简单示例运算过程, 实际情况执行要复杂的多, 背后的指令也更优雅# 栈只有一个元素的时候, 运算正式结束while my_stack.top > 0:item_1 = my_stack.pop()operator = my_stack.pop()item_2 = my_stack.pop()if operator == "*":my_stack.push(item_1 * item_2)elif operator == "+":my_stack.push(item_1 + item_2)# 结果就在栈底print(my_stack.pop())
    

队列

队列的特点是先入先出FIFO first in first out

可以将队列想象为一个没有封口的玻璃管, 但是该玻璃管只有一个口可以添加元素, 一个口吐出元素. 那么队列获取元素必然遵守先入后出.

wGLnn.png

散列表

python中的dict本质就是散列表

散列表也叫hashmap. 通过将key值映射到数组中的一个位置来访问. 这个映射函数就叫做散列函数, 存放记录的数组也叫散列表

wqqUG.png

  • 散列函数有哪些?

    一个好得散列函数要满足以下条件: 1. 均匀铺满散列表, 节约内存空间; 2. 散列冲突概率低

    • 直接定址法

      适合key是连续得或者当前表较小的情况, 否则会有巨大的空间浪费.

      f(n) = a*n + bf(1) = a + b
      f(10000) = 10000a + b
      
    • 数字分析法

      找出key值的规律, 构建冲突比较低的散列函数

      比如姓名, 显然姓很容易冲突, 所以根据名来定散列函数.

    • 平方取中法

      取关键字平方后的中间做为散列地址

    • 折叠法

      将关键字分割成位数相同的几个部分, 然后取这几个部分的叠加和做为散列函数

    • 随机数法

      选择一个随机函数, 取关键字的随机值做为散列地址, 通过用于关键字长度不同的场合

    • 除留余数法

      取关键字被某个大于散列表长度的数P除后得到余数做为散列地址.

  • Python用的是哪种散列函数呢?

    具体要看数据类型, 使用的散列函数大多也是混合方法.

  • 什么是散列冲突?

    不同的key理应得到不同的散列地址, 散列冲突就是不同key得到了同一个散列地址.

  • 如果解决散列冲突?

    • 开放寻址法(Python)

      线性地扫描散列表, 直到找到一个空单元.

    • 链表法(Java)

      所有散列值相同的元素都放到相同位置的链表中

      wqCvW.png

  • hashmap是线程安全的吗?

    • jdk1.8中, 内部使用的是数组+链表+红黑树, 当进行散列冲突的时候, 注定会有一个数据丢失.

    • python中, 由于GIL内置的数据结构都是线程安全的. 但是对于实际应用中, 我们将线程安全都是针对的操作.

      // 当前的函数是线程安全的
      def foo(a):my_dict.update({"a": a})// 通过dis库查看的字节码, 如果关键代码只有一条操作指令, 那就是线程安全的
      import dis
      print(dis.dis(foo))// 当前的函数不是线程安全的
      def foo(a):my_dict['a'] += 1
      
      • from threading import Threadmy_dict = {"a": 0}def foo(a):for i in range(10**6):my_dict['a'] += aif __name__ == "__main__":thread_1 = Thread(target=foo, args=(1, ))thread_2 = Thread(target=foo, args=(-1, ))thread_1.start()thread_2.start()thread_1.join()thread_2.join()print(my_dict)
        
  • 什么是线程安全?

    实际应用的角度来说, 加锁的就是线程安全, 不加锁的就是线程不安全.

  • 为什么在有GIL的情况, 线程仍然是不安全的?

    全局解释器锁只保证了同一个时刻一个线程在运行, 但是不能保证切换到下一个线程还原的现场还有效.

  • dict扩容过程以及安全问题?

    PyDict_SetItem会计算key的散列值, 然后把需要的信息传递给insertdict. 在插入之前根据ma_table剩余空间的大小来判断是否扩容, 一般超过2/3就会进行扩容.

    2/3开始扩容原因就是要给散列函数足够的空间.

树和堆

树是一种特殊的链表结构, 每个节点下有若干个子节点

wG0V7.png

  • 树的分类

    CZN2O.png

  • 二叉树

    每个节点下最多只有两个节点

    wGDW2.png

    • 平衡二叉树

      二叉树节点下可以只有一个子节点, 如果二叉树中节点1-> 2 -> 3 -> 4 -> 5, 那么当前的树结构退化成了链表, 为了解决这么一个情况, 就有了平衡二叉树.

      平衡二叉树的任意节点的左子树的高度与右子树的高度差不可以超过1.

      wGa0P.png

      • 红黑树

        因为平衡二叉树要严格保证左右子树的高度不超过1, 在实际场景中, 平衡二叉树需要频繁地进行调整.

  • 二叉堆

    二叉堆是一个完全二叉树, 满足当前任意节点要<=或者>=左右子节点, 一般使用数组来实现.

    • 最大堆

      当前任意节点要>=左右子节点

      wGMxS.png

    • 最小堆

  • B树

    • B树解决了什么问题?

      B树的目的是在搜索树的基础上优化了磁盘获取的效率

      大部分数据查询的瓶颈在磁盘IO上, 从磁盘中读取1kb数据和1b数据消耗的时间基本是一样的, 在平衡二叉树的基础上, 每个节点尽可能多地存储数据

      wGpND.png

  • B+树解决了什么问题?
    为了优化B树的查找速度, B树的每一个节点都是数据, 而B+树非子节点存储的是数据的地址(索引值), 子节点存储的是数据, 而且子节点会指向相邻的子节点, 都成一个有序链表.

    B树适合作文件系统. B+树适合作遍历和查找.

    wGkhL.png

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...