【014】Python全栈日记-数据结构

本文深入讲解数据结构核心概念,涵盖栈、队列、树、二叉树与图的基础与应用,解析不同数据结构特点,助您掌握高效算法设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、数据结构概述

 

1、数据结构的起源:

(1)为什么要学习数据结构

阿基米德说过:“给我一个支点,我就能翘起地球”。那么给我一个程序,我就能用好程序,给我一个结构,我就能把内容填充完成。打个比方,一个excel表,如果已经有了结构只是填数据,就很简单了,谁都可以去填数据.那么你是要做建结构的那个人还是去填数据的那个人呢?不言而语,我们要做的是那个建结构的人。那么怎么给程序搭建好数据的结构?那么就来学习数据结构吧!

 

所以数据结构的意义通过在前人大量实践总结得出的一系列解决问题的公用基本元素,学习数据结构,对数学建模会有很大的好处,对程序设计也会有很大的好处。

 

(2)什么是数据结构

数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成。记为:

Data_Structure=(D,R)

其中D是数据元素的集合,R是该集合中所有元素之间的关系的有限集合

 

2、基本概念和术语

我们知道了什么 数据结构!那么数据结构中数据是什么样的数据,还有其它我们要知道的基本概念有哪些,以及一些专用术语有哪些,我们先来看一个表。《学生成绩表》

(1)数据

数据:是能被计算机识别,并输入给计算机处理的符号集合。

 

数据不仅仅包括整型、实型等数值类型,还包括字符及声音、图像、视频等非数值类型。

 

(2)数据元素

数据元素:是数据的的基本单位,也被称为记录。

 

(3)数据项

数据项:一个数据元素可以由若干个数据项组成。数据项是数据不可分割的最小单位。

 

比如学生这样的数据元素,也可以有姓名、年龄、性别、出生地址、联系电话等数据项,具体有哪些数据项,要根据你做的系统来决定。

 

(4)数据对象

数据对象:是性质相同的数据元素的集合,是数据的子集。

 

什么叫性质相同呢,是指数据元素具有相同数量和类型的数据项,比如,还是刚才的例子,人都有姓名、生日、性别等相同的数据项。既然数据对象是数据的子集,在实际应用中,处理的数据元素通常具有相同性质,在不产生混淆的情况下,我们都将数据对象简称为数据。好了,有了这些概念的铺垫,我们的主角登场了。说了数据的定义,那么数据结构中的结构又是什么呢?

 

(5)数据结构

我们已经知道了数据结构是相互之间存在一种或多种特定关系的数据元素的集合。

 

在计算机中,数据元素并不是孤立、杂乱无序的,而是具有内在联系的数据集合。数据元素之间存在的一种或多种特定关系,也就是数据的组织形式。为编写出一个“好”的程序,必须分析待处理对象的特性及各处理对象之间存在的关系。这也就是研究数据结构的意义所在。定义中提到了一种或多种特定关系,具体是什么样的关系,这正是我们下面要讨论的问题。逻辑结构与物理结构

 

[1]、逻辑结构

    逻辑结构:是数据元素之间的相互关系。其实这也是我们今后最需要关注的问题。逻辑结构分为以下四种:

 

①集合结构

集合结构:集合结构中的数据元素除了同属于一个集合外,它们之间没有其他关系。各个数据元素是“平等”的,它们的共同属性是“同属于一个集合”。数据结构中的集合关系就类似于数学中的集合。

 

 

②线性结构

线性结构:线性结构中的数据元素之间是一对一的关系。

 

 

③树形结构

树形结构:树形结构中的数据元素之间存在一种一对多的层次关系。

 

④图形结构

图形结构:图形结构的数据元素是多对多的关系。

 


 

我们在用示意图表示数据的逻辑结构时,要注意两点:  

将每一个数据元素看做一个结点,用圆圈表示。

元素之间的逻辑关系用结点之间的连线表示,如果这个关系是有方向的,那么用带箭头的连线表示。从之前的例子也可以看出,逻辑结构是针对具体问题的,是为了解决某个问题,在对问题理解的基础上,选择一个合适的数据结构表示数据元素之间的逻辑关系。

 

 

[2]、物理结构

物理结构:是指数据的逻辑结构在计算机中的存储形式。

 

数据是数据元素的集合,那么根据物理结构的定义,实际上就是如何把数据元素存储到计算机的存储器中。存储器主要是针对内存而言的,像硬盘、软盘、光盘等外部存储器的数据组织通常用文件结构来描述。数据的存储结构应正确反映数据元素之间的逻辑关系,这才是最为关键的,如何存储数据元素之间的逻辑关系,是实现物理结构的重点和难点。

 

数据元素的存储结构形式有两种:顺序存储和链式存储。

 

①顺序存储结构

顺序存储结构:在逻辑上相邻的元素在物理上也相邻。

 

对于线性表的顺序存储结构来说,如果我们要实现获取元素的操作(GetElem),即将线性表L中的第i个位置元素值返回。就程序而言,只要i的数值在数组下标范围内,就是把数组第i-1下标的值返回即可。

 

插入数据:

插入算法的思路:

(1)   如果插入位置不合理,抛出异常;

(2)   如果线性表长度大于等于数组长度,则抛出异常或动态增加容量;

(3)   从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置;

(4)   将要插入元素填入位置i处;

(5)   表长加1;

 

删除数据:

删除算法的思路:

(1)   如果删除位置不合理,抛出异常;

(2)   取出删除元素;

(3)   从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;

(4)   表长减1;

 

用python来模拟顺序存储结构的数据添加与删除:

 

 

②链式存储结构

链式存储结构:是把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。

 

数据元素的存储关系并不能反映其逻辑关系,因此需要用一个指针存放数据元素的地址,这样通过地址就可以找到相关联数据元素的位置。                                                                                                                  

链表创建:

创建单链表的过程是一个动态生成链表的过程。即从“空表”的初始状态起,依次建立各元素结点,并逐个插入链表。

单链表创建的算法思路:

1、声明一个结点p和计数器变量i;

2、初始化一个空链表L;

3、让L的头结点的指针指向NULL,建立一个带头结点的单链表;

4、循环;

(1)       生成一新节点赋值给p;

(2)       随机生成一个数字赋值给p的数据域p->data;

(3)       将p插入到头结点与前一新结点之间;

 

链表插入:

如图,如果我们想在插入一个e应该如何?

可以通过如下方法实现:

然后链表变为:

单链表第i个元素数据插入结点的算法思路:

(1)   声明一个结点p指向链表第一个结点,初始化j从1开始;

(2)   当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;

(3)   若到链表末尾p为空,则说明第i个元素不存在;

(4)   否则查找成功,在系统生成一个空结点s;

(5)   将数据元素e赋值给s->data;

(6)   单链表的插入标准语句s->next=p->next  p->next=s  ;

(7)   返回成功了;

 

链表删除:

单链表第i个数据删除结点的算法思路:

(1)   声明一个结点p指向链表第一个结点,初始化j从1开始;

(2)   当j<1时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;

(3)   若到链表末尾p为空,则说明第i个元素不存在;

(4)   否则查找成功,将欲删除的结点p->next赋值给q;

(5)   单链表的删除标准语句p->next=q->next;

(6)   将q结点中的数据赋值给e,作为返回;

(7)   释放q结点;

(8)   返回成功;

 

让我们用python来实现一下链式结构的创建:(有能力者可以尝试,不是必须的内容:)

 

显然,链式存储就灵活多了,数据存在哪里不重要,只要有一个指针存放了相应的地址就能找到它了。逻辑结构是面向问题的,而物理结构就是面向计算机的,其基本的目标就是将数据及其逻辑关系存储到计算机的内存中。

 

 

二、栈

1、为什么要学习栈?

栈是什么?为什么要学习它?现在先来说说栈的辉煌作用吧!在计算机领域中,栈是一种不可忽略的概念,无论从它的结构上,还是存储数据方面,它对于学习数据结构的人们来说,都是非常重要的。那么就会有人问,栈究竟有什么作用,让我们这么重视它?

 

首先,栈具有非常强大的“记忆”功能,它可以保存对你有作用的数据,也可以被叫做保存现场;其次,当咱们调用一个带参函数时候, 被调用的函数的形参,在编译器编译的时候,这些形参都需要一定的空间存放他们,这时计算机就会默认帮你保存到栈中了!

 

2、栈的定义

      栈的作用,这是一个咱们生活中处处用到,但是却又没发现的一种现象,例如当你拿个篮子去买苹果,那么你最先挑选的苹果就是在篮子的最底下,最后挑选的苹果就在篮子的最上边,那么这就造成了这么一种现象:先拿进篮子的苹果,要最后才能取出来;相反,最后拿进篮子的苹果,就能最先取出来!

 

栈是限定只能在表尾进行插入和删除的线性表。

 

我们把允许插入和删除的一端称作栈顶(Top),另一端称作栈底(bottom)。不含任何数据元素的栈被称作空栈,栈也被称为先进后出的线性表(具有现线性关系)。

 

而栈的特殊性,就是在表中想进行插入和删除的操作,只能在栈顶进行。这也就使得了:栈底是非常稳定的,因为先进来的元素都被放在了栈底。

 

栈的插入操作:叫做进栈,也叫作压栈,入栈。

栈的删除操作:叫做出栈,也叫弹栈。

 

 

通过python来实现一下栈的进栈和出栈:

 

 

 

三、队列

1、为什么要学习队列?

你们在用电脑时有没有经历,机器有时会处于疑似死机的状态,鼠标点什么似乎都没用,双击任何快捷方式都不动弹。就当你失去耐心,打算rest时。突然他像酒醒了一样,把你刚才点击的所有操作全部按顺序执行一遍。这其实是因为操作系统中的多个程序因需要通过一个通道输出,而按先后次序排队等待造成的。

 

再比如向移动、联通、电信等客服电话,客服人员与客户相比总是少数,在所有的客服人员都占线的情况下,客户会被要求等待,直到有某个客户人员空下来,才能让最先等待的客户接通电话。这里也是将所有当前打客服电话的客户进行排队处理。

 

操作系统和客服系统中,都是应用了一种数据结构来实现刚才提到的先进先出的排队功能,这就是队列。

 

队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

 

队列是一种先进先出(First in First Out)的线性表,简称FIFO。

 

允许插入的一端称为队尾,允许删除的一端称为队头,队尾总是指向最后一个元素的下一位,所以对于一个空间为9的队列,当输入第8个数是,队列就已经满了。

 

假设队列是q=(a1,a2,…,an),那么a1就是队头元素,而an是队尾元素。这样我们就可以删除时,总是从a1开始,而插入时,列在最后。这也比较符合我们通常生活中的习惯,排在第一个的优先出列,最后来的当然在队伍的最后。

 

队列在设计程序中用的非常频繁。比如用键盘进行各种字母或数字的输入,到显示器如记事本软件上的输出,其实就是对列的典型应用。

 

用python来模拟队的插入删除:

 

2、循环队列

 

队尾为:dw = (dw+1)%size

队列元素数为:n = (dw-dt+size)%size

队满:(dw+1)%size = dt

 

 


 

四、树

1、为什么学习树?

树是一对多的逻辑结构,在人机对弈、家族族谱、树形信息等应用非常广泛。学习它有很重要的意义。

 

2、树的定义

由n(n>=0)个结点的有限集。n=0表示空树。

 n>1 满足:

 (1) 有且只有一个根结点。

 (2) 其余结点分成互不相交的m个子集T1、T2、...、Tm,每个集合又都是一颗树。

     注意:1)树可以是空树。

           2)树的定义具有递归性 (树中有树)。

 

3、结点分类

树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树称为结点的度(Regree)。度为0的结点称为叶结点(Leaf)或终端结点;度不为0的结点称为非终端结点或分支节点。除根结点之外,分支节点也将称为内部结点。树的度是内各结点的度的最大值。如图所示,因为这棵树结点的度的最大值是结点D的度,为3,所以树的度也为3。

4、结点间关系

结点的子树的根称为该结点的孩子(Child),相应地,该结点称为孩子的双父亲(Parent)。嗯,为什么不是父或母,叫双亲呢?呵呵,对于结点来说其父母同体,惟一的一个,所以只能把它称为双亲了。同一个双亲的孩子之间互称兄弟(Sibling)。结点的祖先是从根到该结点所经分支上的所有结点。所以对于H来说,D、B、A都是它的祖先。反之,以某结点为根的子树中的任一结点都称为该结点的子孙。B的子孙有D、G、H、I。如图所示

5、树的其他相关概念

结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层,若某结点在第一层,则其子树的根就在第l+1层。其双亲在同一层的终点互为堂兄弟。显然图中的D、E、是堂兄弟,而G、H、I、J也是。树中结点的最大层次称为树的深度(Deoth)或高度,当前树的深度为4。

 

如果将树中结点的各子树看成从左至右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。森林(Forest)是m(m≥0)颗互不相交的树的集合。对于树中每个结点而言,其子树的集合即为森林。

 

五、二叉树(重点)

1、为什么要学习二叉树

现在我们来做个游戏,我在纸上已经写好了一个100以内的正整数,请大家想办法猜出我写的是哪一个?注意你们猜的数字不能超过7个,我的回答只会告诉你是“大了”还是“小了”。其实这是一个很经典的折半查找算法。如果我们用下图(下三层省略)的办法,就一定能在7次以内,猜出结果来。

         

我们发现,如果用这种方式进行查找,效率高的不是一点点。对于这种在某个阶段都是两种结果的情形,比如开和关、0和1、真和假、上和下、对和错、正和反等,都适合用树状结构来建模,而这种树是一种很特殊的树状结构,叫做二叉树。

 

2、什么是二叉树

二叉树是n(n≥0)个节点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两颗互不相交的、分别称为根结点的左子树和右子树的二叉树组成。

 

3、二叉树的特点:

    (1)每个结点最多有两个子树,所以二叉树中不存在度大于2的结点(注意:不是只有两颗子树,而是最多有。没有子树或者有一颗子树都是可以的)。

    (2)左子树和右子树是有顺序的,次序不能任意颠倒。

    即使树中某个结点只有一颗子树,也要区分它是左子树还是右子树。如下图所示:树1和树2是同一棵树,但他们却是不同的二叉树。

  

    由此,我们可知二叉树的特点是每个节点至多只有二棵子树(即二叉树中不存在大于2的结点), 并且, 二叉树的子树有左右之分, 其次序不能任意颠倒。

 

二叉树具有五种基本形态:

(1)空二叉树(如图(a)所示)。

(2)只有一个跟结点(如图(b)所示)。

(3)根结点只有左子树(如图(c)所示)。

(4)根结点只有右子树(如图(d)所示)。

(5)根结点既有左子树又有右子树(如图(e)所示)。

 

4、特殊二叉树:

(1)斜树

       顾名思义,斜树一定要是斜的,但是往哪斜还是有讲究的。所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。斜树的特点:每一层都只有一个结点,结点的个数与二叉树的深度相同。

       线性表结构就可以理解为是树的一种极其特殊的表现形式。

              

(2)满二叉树

       在一棵二叉树中,如果所有的分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。

满二叉树的特点有:

1)叶子只能出现在最下一层。出现在其他层就不可能达成平衡。

2)非叶子的结点的度一定是2。

3)在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。

(3)完全二叉树

对一棵具有n个结点的二叉树按层序编号,如果编号为i(1≤i≤n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。

 

(3)完全二叉树

注意:满二叉树一定是一棵完全二叉树,但完全二叉树不一定是满二叉树。

       完全二叉树的特点:

1)叶子结点只能出现在最下面两层。

2)最下层的叶子一定集中在左部连续位置。

3)倒数2层,若有叶子结点,一定都在右部连续位置。

4)如果结点度为1,则该节点只有左孩子,即不存在只有右子树的情况。

5)同样结点树的二叉树,完全二叉树的深度最小。

 

5、二叉树的性质:

二叉树的性质1

在二叉树的第i层上至多有2^(i-1)个结点(i≥1)。

 

二叉树的性质2

    深度为k的二叉树至多有2^k-1个结点(k≥1)

 

二叉树的性质3

对任何一棵二叉树T,如果其终端结点数(即叶子结点数)为n0,度为2的结点数为n2,则n0=n2+1。

 

二叉树的性质4

具有n个结点的完全二叉树的深度为[log2n] + 1([x]表示取整)。

 

二叉树的性质5

如果对一棵有n个结点的完全二叉树(其深度为[log2n] + 1)的结点按层序编号(从第一层到第[log2n] + 1层,每层从左到右),对任一结点i(1≤i≤n)有:

(1)如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则双亲是结点[i/2]

(2)如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩是结点2i。

(3)如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。

 

六、二叉树的遍历(重重要)

 

对二叉树的遍历,即对二叉树的每个结点都访问,且只访问一次。遍历的目的是运算。有了遍历我们就能够访问每一个结点,对其数据惊醒计算和处理。

 

1、二叉树遍历的方法

(1)先序遍历

先序遍历:根节点、左子树、右子树。

先序遍历的序列是:A B D E C。

 

(2)中序遍历

中序遍历:左子树、根节点、右子树。

中序遍历的序列是:D B E A C。

 

(3)后序遍历

后序遍历:左子树、右子树、根节点。

后序遍历的序列是:D E B C A。

 

做个题:已知道二叉树的:先序:GDAFEMHZ、 中序:ADEFGHMZ 求后序

这种题的思路是先画出树状图,先序配合中序能看出G、D、M是根节点,然后顺着画出图就简单多了。

 

 

 

七、图

1、图的定义

一个图由顶点的非空集和边集构成,每条边有一个或两个顶点与它相连,这样的顶点称为边的端点.

 

图是较线性表和树更复杂的数据结构,任意两个顶点之间都可能有关系,顶点之间的关系用边来表示,是多对多的关系。      

 

注意:   

(1)图中的数据元素叫顶点

(2)图不能为空,是顶点的有穷集合

(3)     顶点之间的关系是边,边的集合可以为空

 

2、各种图定义

无向边:两顶点之间的边没有方向,则称为无向边

有向边:两顶点之间的边有方向,则称为有向边

无向图:任意两顶点之间的边都是无向边,则该图是无向图。

有向图:任意两顶点之间的边都是有向边,则该图是有向图。

简单图:不存在顶点到自身的边,或者重复的边。下图则不是简单图。我们研究的是简单图。

 

(1)、无向完全图:如果无向图任意两顶点之间都存在边,则称之为无向完全图。如下图所示:

(2)有向边又称之为弧,对于有向图,任意两顶点之间都存在互为相反的两条弧,则称之为有向完全图。如下图所示:

 

稀疏图:有很少条边或弧的图成为稀疏图,反之成为稠密图。

 

权:图的边或弧上的数称之为权。

网:带权的图成为网。如下图所示。

 

尽管描述图的术语可能区别很大,但是以下三个问题能够帮助我们理解图结构:

1、图的边是有向的还是无向的(还是两者皆有)?

2、如果是无向图,是否存在连接相同顶点对的多重边?如果是有向图,是否存在多重有向边?

3、是否存在环?

回答这些问题有助于我们理解图,而记住所使用的特定术语就不那么重要.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值