学习python中的数据结构
创始人
2024-02-24 08:00:17

数据结构

链表和数组

  • 数组

    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

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...