python自学之《21天学通Python》(15)——第18章 数据结构基础

本文介绍了Python中如何利用列表实现数据结构,如表、栈和队列,并讨论了链表在插入和删除操作中的优势。此外,文章讲解了栈的后进先出(LIFO)特性和队列的先进先出(FIFO)特性,并给出了创建堆栈和队列的实例。接着,文章探讨了树和二叉树的构建,包括二叉树的遍历方法。最后,提到了二分查找和二叉树排序算法,展示了如何使用Python实现这些算法。
摘要由CSDN通过智能技术生成

数据结构是用来描述一种或多种数据元素之间的特定关系,算法是程序设计中对数据操作的描述,数据结构和算法组成了程序。对于简单的任务,只要使用编程语言提供的基本数据类型就足够了。而对于较复杂的任务,就需要使用比基本的数据类型构造更加复杂的数据结构了。

18.1 表、栈和队列

表、栈和队列都是基本的线性数据结构。由于Python设计良好的数据结构,其列表就可以当作表来使用,而且列表的某些特性跟链表都相似,因此在Python中表的实现非常简单。对于栈和队列,则可以自己编写程序来构建。

18.1.1 用列表来创建表

表是最基本的数据结构,在Python中可以使用列表来创建表。而在C语言中,一般使用数组来创建表。由于使用数组创建表,对表中元素进行插入和删除操作的开销较大。当插入一个元素时,要先将该元素后的所有元素,从最后一个元素开始,依次向后移动一个位置。完成元素移动后,再将元素插入到数组中。同样,要删除表中的元素时,首先删除元素,然后将位于该元素之后的元素从前向后,依次向前移动一个位置。

如果一个表含有的元素较多,而要进行插入或删除的位置又比较靠近表的前端,则移动表中元素的操作将耗费大量的时间。为了减少插入和删除元素的线性开销,于是就出现了使用链表代替表。在C语言中,链表中不仅保存数据,还保存了指向下一个元素的指针,如图18.1所示。当进行插入操作时,要先将位于插入元素前的元素的指针赋值给插入元素。完成赋值后再将插入元素的地址赋值给位于其前的元素,如图18.2所示。当进行删除元素时,只需将要删除元素的指针赋值给其前边的元素即可,如图18.3所示。

在这里插入图片描述

使用链表,可以降低插入删除、元素的线性开销。然而,由于链表中不仅存储了数据,而且还保存了指向下一个元素的指针,因而使用链表将占用更大的存储空间。而在Python中,列表本身就提供了插入和删除操作。因此,在Python中列表也可以充当链表使用,而不用自己另外编写程序来构建。

还有一种链表,称之为双向链表,如图18.4所示。双向链表中不仅保存了指向下一元素地址的指针,而且还保存了指向其上一个元素地址的指针。相对于单向链表,双向链表需要占用更多的存储空间,但使用双向列表可以完成正序和倒序扫描链表。

在这里插入图片描述

18.1.2 自定义栈数据结构

栈可以看作在同一位置上进行插入和删除的表,这个位置一般就称为栈顶。栈的基本操作是进栈和出栈,栈可以看作是一个容器,如图18.5所示。先入栈的数据保存在容器底部,后入栈的数据保存在容器顶部。在出栈的时候,后入栈的数据先出,而先入栈的数据则后出,因此栈有一个特性叫做后进先出(LIFO)。

在这里插入图片描述
在Python中,仍然可以使用列表来存储堆栈数据。通过创建一个堆栈类,实现对堆栈进行操作的方法。例如,进栈PUSH方法、出栈POP方法,编写检查栈是否为满栈,或者是否为空栈的方法等。

【实例18-1】 演示了在Python中创建了一个简单的堆栈结构,代码如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
【代码说明】代码中共定义了一个堆栈类PyStack和一个堆栈异常类StackException,堆栈类有一个初始化参数,即堆栈大小,在堆栈类中定义了关于堆栈操作的相关方法。

【运行效果】运行pystack.py程序后,将输出如下内容:

在这里插入图片描述

18.1.3 实现队列功能

队列与栈的结构类似,如图18.5所示。但不同的是队列的出队操作是在队首元素进行的删除操作。因而对于队列而言,先入队的元素将先出队。因此队列的特性可以称之为先进先出(FIFO)。

在这里插入图片描述
和堆栈类似,在Python中同样可以使用列表来构建一个队列,并完成对队列的操作。

【实例18-2】 演示了一个创建简单的队列的实例,代码如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
【代码说明】该实例中的代码与实例18-1也是相似的,定义了队列类及其相关的操作方法,以及一个异常类。

【运行效果】运行pyqueue.py程序后输出如下:

在这里插入图片描述
在这里插入图片描述

18.2 树和图

树和前边所讲的表、栈和队列等这些线性数据结构不同,树不是线性的。在处理较多数据时,使用线性结构较慢,而使用树结构则可以提高处理速度。不过,相对于线性的表、栈和队列等线性数据结构来说,树的构建便显得较为复杂了。

18.2.1 用列表构建树

树是一种非线性的数据结构,如图18.7所示。之所以称之为树,是因为其形状像一颗倒置的树。每棵树都有一个根节点,如图18.7所示的树中,Root为根节点;A、B、C为Root的儿子,Root为A、B、C的父亲,A、B、C为兄弟;同样,A为D、E的父亲,D、E为A的儿子,D、E为兄弟;D、E为Root的孙子,Root为D、E的祖父。在树中,如果一个元素没有儿子,则称之为树的叶子。

在这里插入图片描述

在Python中,树的实现可以使用列表或者类的方式。使用列表的方式较为简便,但树的构建过程较为复杂。使用类的方式构建树时,需要首先确定树中的节点所能拥有的最大儿子数。因为每个节点所拥有的儿子数量并不一定相同,因此使用类的方法将占用更大的存储空间。

【实例18-3】 演示了以列表的形式构建了如图18.7所示的树,代码如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

18.2.2 实现二叉树类与遍历二叉树

二叉树是一类比较特殊的树,在二叉树中每个节点最多只有两个儿子,分为左和右,如图18.8所示。相对于树而言,二叉树的构建和使用都要简单得多。

在这里插入图片描述
任何一棵树,都可以通过变换转换成一棵二叉树。
在Python中,二叉树的构建和树一样,可以使用列表或者类的方式。由于二叉树中的节点具有确定的儿子数,因此,使用类的方式要更为简便。

【实例18-4】 演示了用比较简单的方式生成了如图18.8所示的树,代码如下:

在这里插入图片描述
在这里插入图片描述
【代码说明】代码中首先定义了一个表示节点的类BTree,在该类构造方法中,建立了三个实例变量,分别用来保存其节点值、左子节点和右子节点,然后通过实例化根点,不断从根开始构造该二叉树。

当创建好一棵二叉树后,可以按照一定的顺序对树中所有的元素进行遍历。按照先左后右,树的遍历方法有三种:先序遍历、中序遍历和后序遍历。

先序遍历的次序是:如果二叉树不为空,则访问根节点,然后访问左子树,最后访问右子树;否则,程序退出。

中序遍历的次序是:如果二叉树不为空,则先访问左子树,然后访问根节点,最后访问右子树;否则,程序退出。

后序遍历的次序是:如果二叉树不为空,则先访问左子树,然后访问右子树,最后访问根节点。

【实例18-5】 演示了使用三种遍历方式遍历如图18.8所示的树,代码如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
【代码说明】代码中定义了与实例18-4相同的类,之后分别定义了先序遍历、中序遍历、后序遍历的函数,在遍历函数中采用了递归调用的方式来完成功能。

【运行效果】演示了使用三种遍历方式遍历如图18.8所示的树,其遍历结果如下:

运行TreeTraversal.py程序后输出如下。

在这里插入图片描述
在这里插入图片描述

18.2.3 用字典构建与搜索图

图也是非线性的数据结构,图是由顶点和边组成的。如果图中的顶点是有序的,那么图是有方向的,称之为有向图,如图18.9所示;否则,图是无方向的,称之为无向图。在图中,由顶点组成的序列称之为路径。

在这里插入图片描述
图和树相比,少了树明显的层次结构。在Python中,可以采用字典的方式来创建图,图中的每个元素是字典中的键,该元素所指向的是图中其他元素组成键的值。

同树一样,对于图来说,也可以对其进行遍历。除了遍历以外,可以在图中搜索所有的从一个顶点到另一个顶点的路径。图中的每一顶点可以看作为一个城市,路径可以看作是城市到城市之间的公路。因此,通过搜索所有的路径,可以找到一个顶点到另一个顶点的最短路径,即城市到城市间的最短路线。

【实例18-6】 演示了使用字典的方式构建了如图18.9所示的有向图,并搜索图中的路径,代码如下:

在这里插入图片描述
在这里插入图片描述
【代码说明】代码中采用字典来构造图,并定义了搜索图的函数generatePath(),并且其中也使用了递归调用。
【运行效果】运行本例代码,将输出如下的内容。

在这里插入图片描述
在这里插入图片描述

18.3 查找与排序

查找和排序是最基本的算法,在很多程序中都会用到查找和排序。其实,在前边各章的例子中己多次使用到Python的函数查找字符串中的子字符串。尽管Python提供的用于查找和排序的函数能够满足绝大多数需求,但还是有必要了解最基本的查找和排序算法,以便在有特殊需求的情况下,可以编写自己的查找、排序程序。

18.3.1 实现二分查找

基本的查找方法有顺序查找、二分查找和分块查找等。其中,顺序查找是最简单的查找方法,就是按数据排列的顺序依次查找,直到找到所查找的数据为止(可查看数据表都未找到的数据)。

二分查找是首先对要进行查找的数据进行排序,有按大小顺序排好的9个数字,如图18.10所示。如果要查找数字5,首先与中间值10进行比较,5小于10,于是对序列的前半部分1~9进行查找。此时,中间值为5,恰好为要找的数字,查找结束。

在这里插入图片描述

分块查找,是介于顺序查找和二分查找之间的一种查找方法。使用分块查找时,首先对查找表建立一个索引表,再进行分块查找。建立索引表时,首先对查找表进行分块,要求“分块有序”,即块内关键字不一定有序,但分块之间有大小顺序。索引表是抽取各块中的最大关键字及其起始位置构成的,如图18.11所示。

在这里插入图片描述
分块查找分两步进行,首先查找索引表,因为索引表是有序的,查找索引表时可以使用二分查找法进行。查找完索引表以后,就确定了要查找的数据所在的分块,然后在该分块中再进行顺序查找。

【实例18-7】 演示了对一个有序列表使用二分查找实例,其代码如下:

在这里插入图片描述
在这里插入图片描述
【代码说明】代码很简单,仅定义了一个用于二分查找的函数BinarySearch(),然后在主程序中调用它进行测试和查找。

在这里插入图片描述

18.3.2 用二叉树排序

相对于查找来说,排序要复杂得多,排序的方法也较多,常用的排序方法有冒泡法排序、希尔排序、二叉树排序和快速排序等。其中二叉树排序是比较有意思的一种排序方法,而且也便于操作。二叉树排序的过程主要是二叉树的建立和遍历的过程。例如有一组数据“3,5,7,20,43,2,15,30”,则二叉树的建立过程如下。

(1)首先将第一个数据3放入根节点;
(2)将数据5与根节点中的数据3比较,由于5大于3,则将5放入3的右子树中;
(3)将数据7与根节点中的数据3比较,由于7大于3,则应将7放入3的右子树中,由于3已经有右儿子5,则将7与5进行比较,因为7大于5,应将7放入5的右子树中;
(4)将数据20与根节点3进行比较,由于20大于3,则应将7放入3的右子树,重复比较,最终将20放到7的右子树中;
(5)将数据43与树中的节点值进行比较,最终将其放入20的右子树中;
(6)将数据2与根节点3进行比较,由于2小于3,则应将2放入3的左子树;
(7)同样的对数据15和30进行处理,最终形成如图18.12所示的树。

在这里插入图片描述
当树创建好后,对树进行中序遍历,得到的遍历结果就是对数据从小到大的排序。如果要从大到小进行排序,则可以先从右子树开始进行中序遍历。

【实例18-8】 演示了一个采用二叉树排序的方式对数据进行排序的实例,其代码如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
【运行效果】运行该示例程序后,输出如下的排序结果:

在这里插入图片描述
在这里插入图片描述

18.4 小结

本章介绍了Python在处理基本数据结构方法的程序编写方法。由于Python设计良好的数据结构,通过列表就可以方便地完成表、栈、队列、树、图等数据结构的操作,因此,掌握了Python列表数据类型的使用,编写操作数据结构中的表、栈、队列、树、图的程序就比较简单了。最后,本章还介绍了用Python编写查找与排序的程序。

18.5 本章习题

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值