Python带你了解数据结构【二】

简介: Python带你了解数据结构【二】

上次我们介绍了线性的数据结构,数组,链表,栈,队列,这次我们来看看非线性的数据结构。

非线性的数据结构主要是:树(Tree),图(Graph),堆(Heap),散列表(Hash)

640.png





树(Tree)


谈到树,先给大家看幅图:


640.png


上图就是一个简单的家谱图,这就是一个简单的树。在数据结构中,树的定义是:它是由n(n>0)个有限节点组成一个具有层次关系的集合。

就像上图一样,家谱中的每个人都是一个节点,每个节点又可以再生出其他节点。作为树,应该包含下面几个特点:


1、家谱中都有应该最原始的祖先,也就是这个家中的第一人(即每个树都有固定的根节点

2、家谱中的每个人都可以有自己的孩子或者不生孩子(即每个节点都只有有限个子节点或者没有子节点

3、每个人在家谱中都只有唯一的父母(非根结点只有唯一的一个父节点

4、家谱中的每个人都可以组成自己的家庭,他们都是自己家庭里最有辈分的那个人(树中的每个非根节点,都可以有自己的子树例如上图中的女儿和外孙女就可以构成一个子树)

5、家谱里的人不可以近亲结婚或者乱伦(树里面没有环路

以上便是数据结构中树的介绍,但是在常用的数据结构中,我们会经常使用一个特别的树————二叉树。二叉树是什么意思呢?就是树中的每个节点最多只能有两个孩子节点(换个说法就是在家谱里,你最多只能生个二胎,不能再生更多了!)

640.png

如上图,每个根节点最多只能有两个孩子节点,这就是二叉树,二叉树也有两个很特别的形式,一个叫满二叉树,也就是上图,每个节点都有两个孩子。另外一个叫完全二叉树,完全二叉树是什么意思呢?将所有节点按照上图进行编号,1-15,如果有新的树按照上面这样的形式编号,并且每个节点的编号都和上图的编号一样,这就是完全二叉树,(例如上图,我把L和M节点去掉,K以及之前的的编号都不变,N和O的编号变成了12,13,那N,O和之前的编号不一样了,所以他们就不是完全二叉树,如果把N和O节点去掉,我们发现因为他们是末尾的节点,根本不需要改变前面节点的编号,所以还是原来的编号,这就是完全二叉树)说白了就是,要么就全满,如果缺了,那后面就都不能再有了。

640.png


关于二叉树,我们可以使用什么物理结构来存储呢?链表和数组都是可以实现的。

下面我们来使用python代码来构建一个二叉树:


# 二叉树类
class BTree(object):
    # 初始化
    def __init__(self, data=None, left=None, right=None):
        self.data = data    # 数据域
        self.left = left    # 左子树
        self.right = right  # 右子树
    # 二叉树的高度
    def height(self):
        # 空的树高度为0, 只有root节点的树高度为1
        if self.data is None:
            return 0
        elif self.left is None and self.right is None:
            return 1
        elif self.left is None and self.right is not None:
            return 1 + self.right.height()
        elif self.left is not None and self.right is None:
            return 1 + self.left.height()
        else:
            return 1 + max(self.left.height(), self.right.height())
    # 二叉树的叶子节点
    def leaves(self):
        if self.data is None:
            return None
        elif self.left is None and self.right is None:
            print(self.data, end=' ')
        elif self.left is None and self.right is not None:
            self.right.leaves()
        elif self.right is None and self.left is not None:
            self.left.leaves()
        else:
            self.left.leaves()
            self.right.leaves()


这样我们也就初步构建好了一个二叉树,下面我们来聊聊,二叉树常见的几种遍历方式。遍历(遍历的意思就是沿着某条搜索路线,依次对节点进行访问)主要有两种方式,深度优先遍历广度优先遍历

640.png



深度优先遍历一共有三种,分别是前、中、后序遍历。

前序遍历:先输出根节点,再输出左子树,最后输出右子树。

对应上图的顺序是:ABDECFG


# 前序遍历
    def preorder(self):
        if self.data is not None:
            print(self.data, end=' ')
        if self.left is not None:
            self.left.preorder()
        if self.right is not None:
            self.right.preorder()


中序遍历:先输出左子树,在输出根节点,最后输出右子树。

对应上图的顺序是:DBEAFCG


# 中序遍历
    def inorder(self):
        if self.left is not None:
            self.left.inorder()
        if self.data is not None:
            print(self.data, end=' ')
        if self.right is not None:
            self.right.inorder()


后序遍历:先输出左子树,再输出右子树,最后输出根节点。

对应上图的顺序是:DEBFGCA


# 后序遍历
    def postorder(self):
        if self.left is not None:
            self.left.postorder()
        if self.right is not None:
            self.right.postorder()
        if self.data is not None:
            print(self.data, end=' ')


除了深度优先遍历,还有广度优先遍历,层序遍历就是属于广度优先遍历。

层序遍历:比较简单,一层一层的遍历即可。当层遍历结束进入下一层

对应上图的顺序是:ABCDEFG


# 层序遍历
    def levelorder(self):
        # 返回某个节点的左孩子
        def Left_Child_Of_Node(node):
            return node.left if node.left is not None else None
        # 返回某个节点的右孩子
        def Right_Child_Of_Node(node):
            return node.right if node.right is not None else None
        # 层序遍历列表
        level_order = []
        # 是否添加根节点中的数据
        if self.data is not None:
            level_order.append([self])
        # 二叉树的高度
        height = self.height()
        if height >= 1:
            # 对第二层及其以后的层数进行操作, 在level_order中添加节点而不是数据
            for _ in range(2, height + 1):
                level = []  # 该层的节点
                for node in level_order[-1]:
                    # 如果左孩子非空,则添加左孩子
                    if Left_Child_Of_Node(node):
                        level.append(Left_Child_Of_Node(node))
                    # 如果右孩子非空,则添加右孩子
                    if Right_Child_Of_Node(node):
                        level.append(Right_Child_Of_Node(node))
                # 如果该层非空,则添加该层
                if level:
                    level_order.append(level)
            # 取出每层中的数据
            for i in range(0, height):  # 层数
                for index in range(len(level_order[i])):
                    level_order[i][index] = level_order[i][index].data
        return level_order





堆(Heap)


堆的存储方式就是上面我们介绍的完全二叉树。堆主要分为最大堆和最小堆,这个很好区分,最大值都放在堆顶,且任何一个子节点的值都必须小于或者等于父节点的值,这就是最大堆。最小堆正好相反。(堆的根节点我们叫做堆顶)

堆的两个特性:

1、堆中某个节点的值总是不大于或不小于其父节点的值

2、堆总是一棵完全二叉树

堆里面也有特别的————二叉堆,二叉堆是一种特殊的堆,是一棵完全二叉树或者是近似完全二叉树,并且同时还满足堆的特性:父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左子树和右子树都是一个二叉堆。


640.jpg



如何去构建一个堆呢?我们看下图,用一个数组作为例子:

首先我们需要将数组构建成一个完全二叉树,之后我们将完全二叉树做成一个最大堆:

640.jpg



接下来呢,我们需要将无序的完全二叉树构建成有序的最大堆,操作的主要内容就是让所有的非叶子节点往下走,一直走到适合自己的位置。


640.jpg


下面我们使用Python代码来构建一个堆:


class BinHeap:
    def __init__(self):
        self.heapList = [0]
        self.currentSize = 0
    def up_adjust(self, i): # 二叉堆上浮操作
        while i // 2 > 0:
            if self.heapList[i] <= self.heapList[i // 2]:
                self.heapList[i], self.heapList[i // 2] = self.heapList[i // 2], self.heapList[i]
                i = i // 2
    def insert_value(self, k):
        self.heapList.append(k)
        self.currentSize = self.currentSize + 1
        self.up_adjust(self.currentSize)
    def down_adjust(self, i): # 二叉堆下沉操作
        while (i * 2) <= self.currentSize:
            mc = self.get_min_child(i)
            if self.heapList[i] > self.heapList[mc]:
                tmp = self.heapList[i]
                self.heapList[i] = self.heapList[mc]
                self.heapList[mc] = tmp
            i = mc
    def get_min_child(self, i):# 获取最小值
        if i * 2 > self.currentSize:
            return i * 2
        else:
            if self.heapList[i * 2] < self.heapList[i * 2 + 1]:
                return i * 2
            else:
                return i * 2 + 1
    def del_min(self):# 删除最小值
        retval = self.heapList[1]
        self.heapList[1] = self.heapList[self.currentSize]
        self.currentSize = self.currentSize - 1
        self.heapList.pop()
        self.down_adjust(1)
        return retval
    def build_heap(self, array):# 构建堆
        i = len(array) // 2
        self.currentSize = len(array)
        self.heapList = [0] + array[:]
        while i > 0:
            self.down_adjust(i)
            i = i - 1为节点类,要具备一些常用的方法,获取节点数据,获取下个节点,更新节点的数据,更新下个节点,这些都可以定义在node类里面。


堆的主要用途就是实现堆排序和优先队列。




图(Graph)


图,也是一种非线性的数据结构,是数据结构中最强大的结构之一,树就属于图的一种。

在图的结构中,任意两个结点之间都可能相关,即结点之间的邻接关系是任意的。而在树形结构中,结点之间具有层次关系,每一层结点只能和上一层中的至多一个结点相关,但可能和下一层的多个结点相关。图的结构可以描述多种复杂的数据对象,应用较为广泛。图的结构非常多,例如:邻接矩阵、邻接表、十字链表和邻接多重表等。


在生活中,图的应用也是十分广泛的,下面两个例子就是最常用的图的思想:


640.png


640.png


图对我们来说,其实主要是一种思想,了解了图的思想之后,再选择对应的物理存储结构去解决问题。


# 图的一种:邻接矩阵
class Graph:
    def __init__(self, n_vertices):
        self._n_vertices = n_vertices
        self._adj = [[] for _ in range(n_vertices)]
    def add_edge(self, s, t):
        self._adj[s].append(t)






散列表(Hash)


散列表,又叫哈希表。它在python里面存在的主要形式就是字典了,根据key来查找对应value的值。通过计算key的函数,将需要查询的值映射到一个固定的位置。这个映射就是散列表。最常见的用途就是哈希函数,例如MD5,SHA1等。


640.png



哈希表的本质其实也是一个数组,和数组不同的是我们需要通过一些中间函数进行转换,转换过后取到对应的值。而这个中间函数就是哈希函数。

哈希表的更新,删除,取值对应的python种字典的对应操作:

640.png



谈到哈希表,我们有个问题就不得不说一下,那就是哈希冲突。

什么是哈希冲突呢?我们来看下图:

我们要在五个箱子内放六个球,每个箱子只能放入一个球。但我们发现,如果真的想把球都放进去,就必须要有一个箱子里面装两个,这就冲突了。那对于哈希表而言,也可能会出现这样的情况,当存储区域小于需要存储数据的时候,就会发生哈希冲突。


640.gif



如何解决哈希冲突呢?我们右两种办法:链表法和开放寻址法。

链表法:哈希表的每个元素不仅是对象,也是一个链表的头节点,每个对象通过next指针指向下个对象节点,当新的元素进来产生冲突时,只需要将其插入到对应的链表中就OK了。

开放寻址法:当某个哈希值已经被占用的情况下,继续寻找下一个空着的位置。以此类推。直到找到空的为止。python里面的字典就是采用的该方法。


相关文章
|
12天前
|
存储 缓存 监控
局域网屏幕监控系统中的Python数据结构与算法实现
局域网屏幕监控系统用于实时捕获和监控局域网内多台设备的屏幕内容。本文介绍了一种基于Python双端队列(Deque)实现的滑动窗口数据缓存机制,以处理连续的屏幕帧数据流。通过固定长度的窗口,高效增删数据,确保低延迟显示和存储。该算法适用于数据压缩、异常检测等场景,保证系统在高负载下稳定运行。 本文转载自:https://www.vipshare.com
103 66
|
2月前
|
存储 索引 Python
Python编程数据结构的深入理解
深入理解 Python 中的数据结构是提高编程能力的重要途径。通过合理选择和使用数据结构,可以提高程序的效率和质量
153 59
|
3月前
|
算法 开发者 计算机视觉
燃爆全场!Python并查集:数据结构界的网红,让你的代码炫酷无比!
在编程的世界里,总有一些数据结构以其独特的魅力和高效的性能脱颖而出,成为众多开发者追捧的“网红”。今天,我们要介绍的这位明星,就是Python中的并查集(Union-Find)——它不仅在解决特定问题上大放异彩,更以其优雅的设计和强大的功能,让你的代码炫酷无比,燃爆全场!
46 0
|
2月前
|
存储 开发者 Python
Python 中的数据结构与其他编程语言数据结构的区别
不同编程语言都有其设计理念和应用场景,开发者需要根据具体需求和语言特点来选择合适的数据结构
|
16天前
|
存储 运维 监控
探索局域网电脑监控软件:Python算法与数据结构的巧妙结合
在数字化时代,局域网电脑监控软件成为企业管理和IT运维的重要工具,确保数据安全和网络稳定。本文探讨其背后的关键技术——Python中的算法与数据结构,如字典用于高效存储设备信息,以及数据收集、异常检测和聚合算法提升监控效率。通过Python代码示例,展示了如何实现基本监控功能,帮助读者理解其工作原理并激发技术兴趣。
50 20
|
2月前
|
存储 开发者 索引
Python 中常见的数据结构
这些数据结构各有特点和适用场景,在不同的编程任务中发挥着重要作用。开发者需要根据具体需求选择合适的数据结构,以提高程序的效率和性能
|
2月前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
2月前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
3月前
|
Python
Python 中常见的数据结构(二)
Python 中常见的数据结构(二)
30 4
|
3月前
|
存储 索引 Python
Python 中常见的数据结构(一)
Python 中常见的数据结构(一)
47 3

热门文章

最新文章