学习python中的数据结构

news2024/11/24 20:56:39

数据结构

链表和数组

  • 数组

    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 = data
            self.next = None
    
        def __str__(self):
            return f"<Node {self.data}>"
    
  • 实现LinkedList数据结构

    class LinkedList:
        def __init__(self):
            self.head = None
            self.end = self.head
    
        def append(self, node):
            """
            向链表尾部添加一个节点
            1. 尾部添加: end.next -> node, end -> node
            2. 当前头部没有节点: head -> node
            :param node:
            :return:
            """
            if not self.head:
                self.head = node
            else:
                self.end.next = node
            self.end = node
    
        def insert(self, index, node):
            """
            向index值插入一个节点
            1. 插入的是中间节点: 找到index值的节点, cur.next -> node   node->next
            2. 遍历的过程当中, 结果index值超过了当前链表的长度, 我们抛出异常
            3. 在头部插入节点: head -> node, node.next = head
            4. 在尾部插入节点: 跟中间节点是一样的, 但是end -> node
            :param index:
            :param node:
            :return:
            """
            # 在原index值元素的左边插入 -> 在原index-1值对应元素的右边插入
            cur = self.head
            if index == 0:
                node.next = self.head
                self.head = node
                return
    
            for i in range(index-1):
                cur = cur.next
                if cur is None:
                    raise IndexError("LinkedList insert node exceed max length")
    
            node.next, cur.next = cur.next, node
    
            if node.next is None:
                self.end = node
    
        def remove(self, node):
            """
            通过遍历删除给定的节点
            1. 移除的是中间节点: cur.next -> None, prev.next -> cur.next
            2. 移除的是头节点: head -> cur.next, cur.next -> None
            3. 移除的是尾节点: cur.next本身指向的就是None,和1一致, end -> prev
            :param node:
            :return:
            """
            cur = self.head
            prev = None
            while cur:
                if cur.data == node.data:
                    if prev is None:
                        self.head = cur.next
                    else:
                        prev.next = cur.next
                    cur.next = None
    
                    if prev and prev.next is None:
                        self.end = prev
                    return
    
                prev = cur
                cur = cur.next
    
        def reverse(self):
            """
            翻转当前链表
            1. 中间节点: cur.next -> prev
            2. 头节点: cur.next -> prev
            3. 尾节点: cur.next -> prev
            4. 处理原本的head和end
            :return:
            """
            # 能被翻转说明链表长度 > 1
            if self.head and self.head.next:
                cur = self.head.next
                prev = self.head
    
                # 原本头节点的next需要断开
                self.head.next = None
                # 原本头节点就变成了尾节点
                self.end = prev
                while cur:
                    # 这里设计到next,cur, prev三个节点, 所以引入中间变量next
                    next = cur.next
                    cur.next = prev
    
                    prev = cur
                    cur = next
    
                # 翻转后,头节点指向了原本的尾节点
                self.head = prev
            else:
                return
    
        def __str__(self):
            """
            通过遍历的方式打印当前链表
            初始节点为Head, 如果当前指针指向的是NULL, 说明我们到达了结尾
            :return:
            """
            cur = self.head
            result = ""
            while cur:
                result += str(cur) + "\t"
                cur = cur.next
            return result
    
    
    if __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 = -1
            self.size = _size
    
        def is_full(self):
            return self.top == self.size - 1
    
        def push(self, item):
            """
            首先判断当前栈是否已满
            :param item:
            :return:
            """
            if self.is_full():
                raise Exception("StackOverflow")
            self.stack.append(item)
            self.top += 1
    
        def is_empty(self):
            return self.top == -1
    
        def pop(self):
            """
            首先要判断当前栈是不是空栈
            :return:
            """
            if self.is_empty():
                raise Exception("StackUnderflow")
            self.top -= 1
            return self.stack.pop()
    
    if __name__ == "__main__":
        # 1 + 2 * 3
        my_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 + b
      
      f(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 Thread
        
        my_dict = {"a": 0}
        
        
        def foo(a):
            for i in range(10**6):
                my_dict['a'] += a
        
        
        if __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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/43172.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]SSM计算机毕业设计网上鞋店管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Pan-cancer image-based detection of clinically actionable genetic alternations

目录 摘要 前言 结果 深度学习模型优化 从组织病理图像预测泛癌基因突变 摘要 肿瘤的分子突变可导致肿瘤细胞及其微环境的表型变化。常规组织病理切片可以反映出这种形态学改变。本研究表明深度学习方法能直接从常规病理图像中推断出广泛的基因突变、肿瘤分子亚型、基因表达…

中纺集团×StarRocks:构建企业级数据资产管理的实践

中纺集团经过“十三五”信息化建设&#xff0c;生成了大量宝贵的数据资源&#xff0c;但也存在信息孤岛、系统壁垒、数据质量等问题。中纺结合企业实际需求&#xff0c;按照集团“十四五”信息化规划中有关数据中台的建设计划与目标要求&#xff0c;历经半年多的测试比选&#…

使用扩展有效对齐 SwiftUI 内容,创建自定义 SwiftUI 方法以快速对齐项目并使您的代码看起来简洁明了(教程含源码)

在开发 iOS 应用程序时,对齐内容可能是一个耗时的过程。如果应用程序有多个屏幕,则需要在不同的地方完成这件事,并可能导致看起来杂乱无章的视图。 作为一个始终致力于让我的代码看起来简单和流线型的人,实现目标所需的大量Spacer()元素常常让我恼火,这就是为什么当我发…

配置Nginx和其他应用的HTTPS访问

使用tomcat或者weblogic部署的应用默认都是http访问的&#xff0c;如果通过https访问&#xff0c;需要ssl证书。tomcat或者weblogic可以配置&#xff1b; 同时&#xff0c;另一种方法&#xff0c;https网站中&#xff0c;如果接口服务是http的&#xff0c;那么请求接口就会被拒…

[美国访问学者J1]签证的材料准备

对于美国访问学者J1签证材料的准备&#xff0c;在这里知识人网老师和大家分享一下&#xff1a; 1. 有效护照&#xff1a;如果您的护照将在距您预计抵美日期的六个月内过期、或已损坏、或护照上已无空白的签证签发页, 请在前来面谈之前先申请一本新护照。 2. DS-160表格确认页。…

vulnhub靶机darkhole

靶机下载地址&#xff1a;DarkHole: 1 ~ VulnHub Kali ip:192.168.174.128 靶机ip&#xff1a;192.168.174.135 靶机ip发现 sudo arp-scan -l 开放端口扫描 nmap -p- -sV -A 192.168.174.135 发现开启了22端口和80端口 目录扫描 gobuster dir -u http://192.168.174.135…

Seal库官方示例(一):bfv_basics.cpp解析

尽量理论来理解代码。 完整代码或者\native\examples里面 说到前面的话 两段官方的话 大致意思就是&#xff0c;这个库有门槛&#xff0c;需要先学会同态的概念&#xff0c;提供的例子必须要看要理解。必看的例子如下&#xff0c; 代码解析 基础加密 参数设置 三个核心…

flutter系列之:在flutter中使用流式布局

文章目录简介Flow和FlowDelegateFlow的应用总结简介 我们在开发web应用的时候&#xff0c;有时候为了适应浏览器大小的调整&#xff0c;需要动态对页面的组件进行位置的调整。这时候就会用到flow layout&#xff0c;也就是流式布局。 同样的&#xff0c;在flutter中也有流式布…

真题集P93---2017年计专真题

真题集P93---2017年计专真题六思路&#xff1a;模拟代码七思路一&#xff1a;哈希表法二&#xff1a;排序法 (利用排序去重)三&#xff1a;拓展代码&#xff08;仅思路一&#xff09;六 思路&#xff1a;模拟 1、接口介绍 int turnNum(int num[], int nums)&#xff1a;拿来一…

数据库约束

约束概述 为什么需要约束 数据完整性&#xff08;Data Integrity&#xff09;是指数据的精准性&#xff08;Accuracy&#xff09;和可靠性&#xff08;Reliability&#xff09;。它是防止数据库中存在不符合语义规定的数据和防止因错误信息的输入输出造成无效操作或错误信息而…

种草软文怎么写?分享一些超实用的种草软文写作技巧。

一提起“种草”这个词语&#xff0c;想必很多小伙伴都不陌生&#xff0c;我们都有“被种草”的经历&#xff0c;指的就是把一件商品推荐给大家&#xff0c;分享给需要的人&#xff0c;然后促成销售转化的过程。 在一些主流的内容平台&#xff0c;比如知乎、小红书、得物、公众…

Android 9.0 MediaPlayer播放流程分析

1.MediaPlayer初始化流程 EventHandler是后面处理数据回调的handler. 在AudioFlinger.cpp中获取nextUniqueId&#xff1a; audio_unique_id_t AudioFlinger::nextUniqueId(audio_unique_id_use_t use) {// This is the internal API, so it is OK to assert on bad parameter.…

18-1、k8s 对外服务之ingress

一、什么是ingress 原来的项目是部署在一台电脑上的&#xff0c;这样爬取速度虽然很快&#xff0c;但是我们还能提升&#xff0c;联想到分布式的思想&#xff0c;我们是否可以通过多台电脑进行配合爬取&#xff0c;这样我们的爬取速度就能大幅度提升。 …

【Mysql】数据库的基本操作和表的增删改查

本章内容是,用sql语言实现对数据库的基本操作和表的基本操作 文章目录前言1. 数据库的基本操作1.1 创建数据库1.2 查看数据库1.3 选中数据库1.4 删除数据库2. 数据库基本数据类型3. 表的基本操作3.1 创建表3.2 显示数据库中的表3.3 查看表的构造3.4 删表4. 表的增删改查4.1 增加…

基于PHP+MySQL汽车查询系统的设计与实现

随着时代的发展,汽车已经逐渐成为人们代步的主要工具之一,按时因为工业的发展,汽车的品牌和型号也层出不穷,如此多的汽车信息如何能够让爱车人士更好的 汽车查询系统的主要功能包含&#xff1a;汽车的类别管理、汽车的信息管理、留言管理、用户的管理等。网站分为管理员、会员用…

开放与融合趋势下,工业互联网安全破圈之道

作者 | 伍杏玲 出品 | CSDN 在工业 4.0 和数字经济的发展促进下&#xff0c;工业互联网作为连接工业经济的重要要素&#xff0c;成为推动数字经济发展的重要基础设施。据统计&#xff0c;到 2025 年&#xff0c;全球 IoT 连接设备数量达 519 亿。 然而在 OT 和 IT 融合趋势下…

1721. 交换链表中的节点-仅遍历一次链表-考研满分答案

1721. 交换链表中的节点-仅遍历一次链表 给你链表的头节点 head 和一个整数 k 。 交换 链表正数第 k 个节点和倒数第 k 个节点的值后&#xff0c;返回链表的头节点&#xff08;链表 从 1 开始索引&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], k…

第1关:Hbase数据库的安装

在安装HBase之前你需要先安装Hadoop和Zookeeper&#xff0c;如果你还没有安装可以通过这两个实训来学习&#xff1a;Hadoop安装与配置&#xff0c;Zookeeper安装与配置。 本次实训的环境已经默认安装好了Hadoop&#xff0c;接下来我们就开始安装配置HBase吧。 HBase安装 HBas…

浙大MPA常规批复试上岸经验分享

在经历笔试和面试的备考后&#xff0c;去年终于来到了复试环节&#xff0c;好在通过自己的不懈努力和不放松的精神&#xff0c;最终成功上岸&#xff0c;现在把个人的备考经验做整理为大家做个参考&#xff01; 一、复试前准备&#xff1a;在正式复试前建议一定要对浙大MPA项目…