目录:
一、算法的概念
算法指的是解题方案的完整且准确的描述,是一系列解决问题的清晰指令。算法不等于数学上的方法,也不等于程序。
常见的算法设计方法有列举法、归纳法、递推法和递归法等。
在算法设计时,不能只考虑算法的效率,还要考虑其他因素。
算法是一系列解决问题的步骤或规则,用于指导计算机或其他设备完成特定任务。它可以被看作是一种计算过程,通过输入来产生输出。算法可以在各种领域中使用,包括计算机科学、数学、物理学、经济学等。
算法的特点包括:
可行性:算法必须能够在有限的时间和资源内完成。
确定性:算法的每个步骤都必须明确且具有确定的含义。
输入:算法接受一个或多个输入。
输出:算法产生一个或多个输出。
有限性:算法必须在有限的步骤内终止。
算法可以用自然语言、流程图、伪代码或特定的编程语言来描述和实现。设计一个高效的算法可以提高计算机程序的执行效率,节省时间和资源。
1.1算法的特性
可行性 | 算法中的每一个步骤必须能够实现,且执行的结果能够达到预期的目的。 |
确定性 | 算法中的每一个步骤都有它确定的含义,不存在一个步骤有多个含义的情况。不允许有模棱两可的解释,也不允许有多义性。 |
有穷性 | 算法必须在有限时间内完成,即算法必须能在执行有限个步骤之后终止。 |
拥有足够的情报 | 一个有效的算法应该有足够多的、正确的输入信息或初化信息,并且至少有一个输出结果。 |
1.2算法的复杂度
算法的复杂度主要包括时间复杂度和空间复杂度,但两者之间没有直接的联系。
时间复杂度 | 执行算法所需要的计算工作量,即算法语句执行次数。 |
空间复杂度 | 算法在执行过程中所需要的计算机存储空间。包括程序本身所占的存储空间、输入数据所占的存储空间、算法执行过程中所需要的额外空间。如果额外空间量相于问题规模(即输入数据所占存储空间)来说即额外空间量不随问题规模的变化而变化,则称该算法是原地工作的。 |
算法的复杂度是衡量算法运行效率的一个指标,主要分为时间复杂度和空间复杂度两个方面。
时间复杂度:衡量算法运行时间的增长率。通常使用大O符号(O)来表示,如O(1)、O(log n)、O(n)、O(n²)等。时间复杂度描述的是随着输入规模的增加,算法运行时间的增长趋势。
空间复杂度:衡量算法所需空间的增长率。同样使用大O符号来表示算法占用的额外空间,如O(1)、O(n)等。空间复杂度描述的是算法所需的额外空间随着输入规模的增加而增长的趋势。
算法复杂度的评估可以帮助我们选择合适的算法来解决问题,常见的复杂度有常数时间复杂度O(1)、对数时间复杂度O(log n)、线性时间复杂度O(n)、线性对数时间复杂度O(n log n)、平方时间复杂度O(n²)等。越低的复杂度意味着算法的效率越高。
二、数据结构
数据结构,即带有“结构”的数据元素的集合。一般来说,现实世界中客观存在的一切个体都可以是数据元素。例如,“春、夏、秋、冬”可以作为季节的数据元素。
在数据处理时,通常把数据元素之间的特殊关系称为前后件关系。以季节为例,在考虑一年四季的数据顺序关系时,“春”就是“冬”的前件,“冬”就是“春”的后件。
数据结构是指数据元素之间的关系,以及数据元素及其关系的操作。它是计算机存储、组织和操作数据的一种方式。
数据结构包括以下几个概念:
数据元素:数据的基本单位,可以是单个的数字、字符或者更复杂的结构体。
数据项:组成数据元素的最小单位,一个数据元素可以由一个或多个数据项组成。
数据结构的逻辑结构:数据元素之间的逻辑关系,包括线性结构、树形结构、图形结构等。
数据结构的物理结构:数据元素在计算机内存中的存储方式,包括顺序存储和链式存储两种。
数据结构的操作:对数据结构进行的操作,包括插入、删除、修改、查找等。
常见的数据结构包括数组、链表、栈、队列、树、图等。选择合适的数据结构可以提高算法的执行效率和程序的性能。
1.1数据结构的表示方法:
数据结构可以用以下几种方式来表示:
数组:数据元素按照顺序存储在一段连续的内存空间中,可以通过下标直接访问元素。
链表:数据元素以节点的形式存储,每个节点包含数据和指向下一个节点的指针。可以实现动态的插入和删除操作。
栈:一种特殊的线性表结构,只能在栈顶进行插入和删除操作,遵循先进后出的原则。
队列:一种特殊的线性表结构,只能在队尾进行插入操作,在队头进行删除操作,遵循先进先出的原则。
树:由节点和边组成的非线性数据结构,每个节点可以有多个子节点。
图:由节点和边组成的非线性数据结构,节点之间可以有多个连接关系。
堆:一种特殊的树形数据结构,用于快速找到最大或最小元素。
散列表:根据关键字直接访问数据的数据结构,通过散列函数将关键字映射到存储位置。
图表:用来表示数据之间的关系的图形结构。
以上是常见的几种数据结构表示方法,每种方法适用于不同的应用场景,选择合适的数据结构可以提高算法的效率。
1.2数据结构中的组与图表示方法:
二元组表示:
B=(D,R)
D=(1,2,3,4)
R={(1,2),(2,3),(3,4)}
D:数据结构里所有的数据元素
R:数据元素之间的关系
图形表示:
1->2->3->4
1.3数据结构分类
1.3.1逻辑结构
数据的逻辑结构是指反映数据元素之间的逻辑关系(前后件关系)的数据结构。数据的逻辑结构分为线性结构和非线性结构。
数据结构的逻辑结构描述了数据元素之间的关系和组织方式,是数据在计算机内部的抽象表示。
常见的数据结构逻辑结构包括以下几种:
线性结构(Linear Structure):数据元素之间呈线性关系,即一对一的关系。常见的线性结构有线性表、栈、队列、矩阵等。
非线性结构(Non-linear Structure):数据元素之间存在多对多的关系。常见的非线性结构有树、图、广义表等。
集合结构(Set Structure):数据元素之间没有任何关系,每个元素都是相互独立的。常见的集合结构有集合、多重集等。
层次结构(Hierarchical Structure):数据元素之间存在一种层次关系,即上下级关系。常见的层次结构有树结构等。
网状结构(Network Structure):数据元素之间存在多种关系,形成复杂的网络结构。常见的网状结构有图结构等。
这些逻辑结构在实际应用中可以通过不同的物理结构来实现,如数组、链表、树结构、图等。不同的逻辑结构适用于不同的问题和场景,选择合适的数据结构可以提高算法的效率和性能。
1.3.2存储结构
数据的逻辑结构在计算机存储空间中的存放形式被称为数据的存储结构,也可以称为数据的物理结构。
常见的存储结构有顺序存储、链式存储、索引存储和散列存储等。
数据结构的存储结构是指数据结构所使用的具体的物理存储方式。常见的数据结构存储结构有以下几种:
顺序存储结构:数据元素按照其逻辑顺序依次存储于一片连续的存储空间中,可以通过下标来访问元素。例如,数组就是一种顺序存储结构。
链式存储结构:数据元素存储在不连续的存储空间中,每个元素都包含一个指针,指向下一个元素的地址。通过指针的链接,可以顺序访问元素。例如,链表就是一种典型的链式存储结构。
索引存储结构:在顺序存储的基础上,增加一个索引表,索引表中的每个元素都包含了相应数据元素的地址或者位置信息。通过索引表,可以快速地访问数据元素。
散列存储结构:通过散列函数将数据元素的关键字映射到存储空间的地址,从而实现快速的查找和插入操作。
树型存储结构:通过节点和指针的方式组织数据元素,形成树的结构。例如,二叉树是一种常见的树型存储结构。
图型存储结构:通过节点和边的方式组织数据元素,形成图的结构。例如,邻接表和邻接矩阵都是图的存储结构的常见方式。
不同的存储结构适用于不同的数据结构和操作。选择合适的存储结构可以提高数据结构的效率和性能。
1.4线性表
1.4.1线性表概念
线性表是一种逻辑结构,它最简单、最常用的一种数据结构。在线性表中,数据元素之间是一对一的关系。
线性表是最基本、最常用的数据结构之一,它是由一组具有相同特性的数据元素组成的,这些元素按照线性的顺序存储在内存中。线性表包括顺序表和链表两种结构。
顺序表:顺序表是指在线性表中,元素之间按照元素在内存中的顺序依次存储的结构。顺序表中的元素可以是任意类型,例如整数、浮点数、字符串等。顺序表可以通过数组实现,也可以通过链表实现。顺序表的主要特点是查找元素效率高,但插入和删除元素的效率较低。
链表:链表是线性表的另一种实现方式,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。链表有单链表、双链表和循环链表等多种形式。链表的主要特点是插入和删除元素的效率高,但查找元素的效率较低。
线性表常用的操作包括插入元素、删除元素、查找元素、获取元素个数等。线性表还可以进行合并、拆分等操作,以满足不同的需求。线性表的应用非常广泛,例如数组、链表、栈、队列等都是线性表的具体实现。
线性表是一种基本的数据结构,它具有以下特点:
有序性:线性表中的相同数据类型的数据元素按照一定的顺序排列,每个元素都有一个前驱元素和一个后继元素(除第一个和最后一个元素外)。注意:【开头的元素叫:根节点;结尾的元素叫:终端结点】
可重复性:线性表中的元素可以重复出现,即同一个元素可以出现多次。
动态性:线性表的长度可以动态地增加或减少,可以根据需要动态地插入或删除元素。
顺序访问:线性表中的元素按照顺序存储,可以通过索引来访问任意位置的元素,访问效率高。
存储连续性:线性表中的元素在内存中是连续存储的,可以通过下标来访问元素。
大小固定:线性表的大小是固定的,即线性表的容量是有限的,不能无限增长。
可变性:线性表中的元素可以根据需要进行插入、删除、修改等操作,可以灵活地调整线性表的内容。
同质性:线性表中的元素具有相同的数据类型,即线性表中的元素类型是固定的。
1.4.2线性表的特性:
非线性表的结构特征:
有且只有一个根结点,它无前件。
有且只有一个终端结点,它无后件。
线性表的结点个数就是该线性表的长度。
当n=0时,线性表为空表。
非线性表结构特征是指数据元素之间存在多对一或者多对多的关系,数据元素之间的关系不仅仅是一对一的线性关系。非线性表结构特征主要有以下几点:
子节点或后继节点个数不确定:非线性表结构中的每一个节点可以有多个子节点或者后继节点,而不是只有一个。
数据元素的存储空间可以是连续的,也可以是离散的:非线性表结构中的数据元素可以存储在连续的存储空间中,例如数组;也可以存储在离散的存储空间中,例如链表。
数据元素之间的关系没有固定的顺序:非线性表结构中的数据元素之间的关系是通过指针或者引用来确定的,没有固定的顺序。
数据元素之间的关系可以是多对一或者多对多的关系:非线性表结构中的数据元素之间的关系不仅仅是一对一的线性关系,可以是多对一的关系,即一个节点可以有多个父节点;也可以是多对多的关系,即一个节点可以有多个前驱节点和后继节点。
非线性表结构的特点使得数据的组织和存储更加灵活和高效,适用于解决一些复杂的问题。常见的非线性表结构包括树、图等。
1.4.3线性表的顺序存储——顺序表
将线性表中的数据元素存储在一组地址连续的存储单元里,使得逻辑上相邻的两个元素在物理位置上也相邻,称为线性表的顺序存储,又顺序表。
在顺序表中可以进行插入、删除、修改和查询数据元素等操作,常用的操作是插入和删除。
----------------------------------
| 元素1 | 元素2 | 元素3 | ... | 元素n |
----------------------------------
顺序表是一种线性表的存储结构,它的元素在内存中的存储是连续的。顺序表可以用数组来实现,数组的每个元素对应顺序表的一个元素,通过数组的下标可以直接访问顺序表中的元素。
顺序表有两种类型:静态顺序表和动态顺序表。
静态顺序表是指在创建时设置了固定的容量,容量不能自动扩充或缩小。静态顺序表的优点是访问元素的时间复杂度为O(1),缺点是容量有限,无法动态调整。
动态顺序表是指在创建时没有固定的容量,可以根据需要动态调整容量。动态顺序表通常使用动态数组来实现,当元素个数超过容量时,会自动扩充容量。动态顺序表的优点是容量可以根据需要动态调整,缺点是扩充容量可能会导致元素的复制。
顺序表的操作包括插入、删除、查找、修改等。插入操作需要将插入位置之后的所有元素后移,删除操作需要将删除位置之后的所有元素前移,查找操作通常使用顺序查找或二分查找算法。
顺序表的特点是随机访问高效,但插入和删除操作需要移动大量元素,效率较低。在需要频繁插入和删除操作的场景中,可以考虑使用链表等其他数据结构来实现。
顺序表的插入操作
在顺序表中插入元素时,首先需找到要插入元素的位置。如果在该位置上没有元素,则直接插入新元素;若该位置上有元素,需要将该位置上的元素及其后面的元素向后移动,空出需要插入元素的位置,最后将新元素插入到指定的位置上。
在线性表的存储空间已满时,不能再继续插入新元素。若继续插入,则会产生“上溢”错误。
顺序表的删除操作
在线性表中删除已有的元素,然后将删除元素之后的所有元素依次向前移动相应个位置,并减少线性表的结点数。
若线性表为空时,不能再进行数据元素的删除。若继续删除,则会产生“下溢”错误。
1.4.4线性表链式序存储——线性链表
线性表的链式存储结构称为线性链表,用一组地址任意的存储单元存放线性表中的各个数据元素,不要求逻辑上相邻的两个元素在物理位置也相邻。
链式存储结构中的结点通常由两部分组成,用于存放数据的部分称为数据域,用于存放指针的部分称为指针域。
线性链表又分为了单向链表,双向链表,循环链表。
线性链表是一种常见的数据结构,它由一系列的节点组成,每个节点包含数据和指向下一个节点的指针。每个节点都只有一个后继节点,最后一个节点则指向空。
优点:
- 灵活性:线性链表的长度可以根据需要动态增加或减少。
- 插入和删除的效率高:在链表中插入和删除节点的操作只需要改变节点指针的指向,时间复杂度为O(1)。
- 不需要连续的内存空间:线性链表中的节点可以存储在不连续的内存空间中。
缺点:
- 访问效率低:虽然插入和删除节点的效率高,但是在链表中查找节点的效率较低,需要从头节点开始依次遍历,时间复杂度为O(n)。
- 空间开销大:每个节点都需要额外的指针来指向下一个节点,增加了额外的空间开销。
线性链表常用于实现栈、队列和其他数据结构,也可以用作缓存、哈希表等场景中的基础数据结构。
单向链表
单向链表是链表的一种,它的指向是单向的,对链表的访问是从头部开始顺序读取元素,其每个结点都有指针成员变量指向列表中的下一个结点。单向链表的head指针指向第一个成为表头结点,而终止最后一个指向NULL的指针。
节点1 -> 节点2 -> 节点3 -> ... -> 节点N
- 单向链表是一种常见的数据结构,它由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。
- 单向链表的特点是每个节点只能访问其后继节点,不能访问其前驱节点。链表的头节点是第一个节点,尾节点是最后一个节点的指针为空。
- 单向链表的优点是插入和删除节点的操作效率高,时间复杂度为O(1)。但是查找和访问节点的效率较低,时间复杂度为O(n),因为需要从头节点开始遍历链表。
- 常见的操作包括插入节点、删除节点、查找节点、遍历链表等。插入节点需要将待插入节点的指针指向原来节点的下一个节点,然后将原来节点的指针指向待插入节点。删除节点需要将待删除节点的上一个节点的指针指向待删除节点的下一个节点,然后将待删除节点释放。查找节点需要遍历链表,直到找到目标节点或者链表结束。遍历链表可以顺序访问每个节点的数据元素。
- 单向链表的应用场景包括实现队列、栈、图的邻接表等。
双向链表
双向链表有两个指针域,一个指向前件结点,一个指向后件结点。指向前件结点的指针称为左指针,指后件结点的指针称为右指针。
head -> [prev: NULL, data: NULL, next: &node1] -> [prev: &head, data: 1, next: &node2] -> [prev: &node1, data: 2, next: &node3] -> [prev: &node2, data: 3, next: &tail] -> [prev: &node3, data: NULL, next: NULL] <- tail
- 双向链表是一种常见的数据结构,它由一系列节点组成,每个节点都包含两个指针,一个指向前一个节点,一个指向后一个节点。这种链表允许从任意节点开始,可以沿着前向或后向指针遍历整个链表。
- 与单向链表不同,双向链表可以从前向后或后向前遍历,因此在某些场景下,双向链表比单向链表更加方便。
- 双向链表的节点通常包含两个字段,一个是数据存储字段,用于保存节点中的数据;另一个是指针字段,用于指向前一个节点和后一个节点。一个双向链表通常有一个头节点和一个尾节点,它们的前向指针和后向指针分别指向NULL。
- 在双向链表中,插入和删除节点的操作相对容易。插入操作可以在任意位置插入新节点,只需要修改前一个和后一个节点的指针即可。删除操作也类似,只需要修改前一个和后一个节点的指针即可将目标节点从链表中移除。
- 双向链表的优点是可以在O(1)的时间复杂度内实现在任意位置插入和删除节点的操作。然而,它的缺点是占用的内存空间相对较大,因为每个节点需要额外存储指向前一个节点的指针。此外,由于需要维护两个指针,操作起来稍微复杂一些。
- 总而言之,双向链表是一种灵活且功能强大的数据结构,适用于一些需要在任意位置插入和删除节点的场景。
循环链表
循环链表是将表中最后一个结点的指针域指向头结点,使得整个链表形成一个环。
在循环链表中,只要知道任意一个结点的位置,都可以从该结点出发,访问表中所有的结点。
循环链表插入和删除元素比单向链表更加简单。表头结点是循环链表中固有的结点,即使表中没有数据,表中至少都还有一个表头结点,这样就实现了空表和非空表的运算的统一。
┌───────────────────────────────────────────────────┐
│ ▼
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
│ Node1 │◀───│ Node2 │◀───│ Node3 │◀───│ Node4 │◀───│ Node5 │
└───────┘ └───────┘ └───────┘ LOADING_ICON_REPLACEMENT
- 循环链表是一种特殊的链表,它与普通链表不同的地方在于,尾节点的指针指向头节点而不是NULL。这样一来,整个链表形成一个环状结构。
- 循环链表的好处是可以从任意节点开始遍历整个链表,而不需要遍历到尾节点就停止。另外,循环链表可以很方便地实现循环队列和循环缓冲区等数据结构。
- 循环链表的操作与普通链表类似,可以插入节点、删除节点、查找节点等。唯一需要注意的是,在插入节点和删除节点时需要考虑环状结构的特殊情况。
- 循环链表的实现可以使用链表节点类来表示每一个节点,节点类包含一个数据域和一个指针域,指针域用于指向下一个节点。在循环链表中,尾节点的指针域指向头节点。
线性链表插入
线性链表插入是指在线性链表中插入一个新的节点。
插入操作通常分为两种情况:
- 在链表的头部插入节点:将新的节点插入到链表的头部,即新的节点成为链表的第一个节点,原来的第一个节点成为新的节点的下一个节点。
- 在链表的中间或尾部插入节点:找到待插入位置的前一个节点,将新的节点插入到该位置,新的节点的下一个节点指向原来的位置上的节点。
插入操作需要考虑以下情况:
- 若链表为空,则直接将新的节点设置为链表的头节点。
- 若链表非空,需要遍历链表找到待插入位置的前一个节点。如果找到了该节点,则进行插入操作;如果没有找到,则说明插入位置超出了链表的范围,不执行插入操作。
线性链表删除
线性链表是一种常见的数据结构,它由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。
删除操作是指从链表中删除特定元素的操作。具体步骤如下:
- 遍历链表,找到要删除的节点的前一个节点。可以使用一个临时指针来记录当前节点和前一个节点。
- 修改前一个节点的指针,使其指向要删除节点的下一个节点。
- 释放要删除的节点的内存空间。
需要注意的是,如果要删除的节点是链表的第一个节点,需要特殊处理。可以将头指针指向第二个节点,然后释放要删除的节点的内存空间。
对于双向链表,删除操作的步骤类似,需要同时修改前一个节点和后一个节点的指针。
线性链表查找
线性链表是一种常见的数据结构,它由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。线性链表中的节点按照顺序连接,形成一个有序的链式结构。
在线性链表中进行查找操作时,可以分为两种情况:
- 按照位置查找:根据元素在链表中的位置进行查找,常见的操作包括查找第一个元素、查找最后一个元素、查找指定位置的元素等等。
- 按照值查找:根据元素的值进行查找,需要遍历整个链表,逐个比较每个节点的值,直到找到匹配的节点。
在进行线性链表的查找操作时,需要从链表的头部开始遍历,逐个比较节点的值或位置,直到找到目标元素或者遍历到链表的末尾。如果找到目标元素,则可以返回该节点或节点的位置;如果遍历到链表的末尾仍未找到目标元素,则表示目标元素不存在于链表中,则返回空。
线性链表的查找操作时间复杂度为O(n),其中n为链表的长度。在最坏的情况下,需要遍历整个链表才能完成查找操作。
1.5栈
1.5.1栈的概念
栈是一种操作受限的线性表,限定它只能在表的同一端进行插入和删除操作。允许插入和删除元素的一端称为栈顶,不允许插入和删除元素的一端称为栈底。
由于最先插入的元素总是最后被删除,最后插入的元素总是最先被删除,因此栈也被称为“先进后出”或者是“后进先出”的线性表,也被称为顺序栈。
通常用top指向栈顶元素,反应栈中元素变化情况;bottom表示栈底,在运算当中维持不变。
+----+ +----+ +----+ +----+
| | | | | | | |
+----+ +----+ +----+ +----+
4 3 2 1 (栈顶)
- 栈是一种线性数据结构,它具有特定的操作特性:即只能在一端进行插入和删除操作。这一端被称为栈顶,另一端称为栈底。栈遵循"后进先出"(LIFO)的原则,最后插入的元素最先删除。栈的插入操作称为入栈(push),删除操作称为出栈(pop)。
- 栈的应用场景广泛,包括函数调用、表达式求值、括号匹配、迷宫求解等。在函数调用中,每当一个函数被调用时,其局部变量和返回地址被压入栈中,当函数执行完毕后,这些变量和地址又被弹出,控制权返回到调用者。
- 栈可以使用数组或链表实现。在使用数组实现时,需要设置一个指针来指示栈顶元素的位置;在使用链表实现时,每个节点包含一个元素和一个指向下一个节点的指针。无论使用数组还是链表,栈的操作都具有常数时间复杂度。
1.5.2栈的基本运算
入栈运算:从栈顶位置插入新元素的操作称为入栈。
出栈运算:从栈顶位置取出栈顶元素的操作称为出栈。
读栈顶元素:当栈不为空时,将栈顶元素赋给一个指定变量的操作称为栈顶元素。
入栈运算
插入一个新元素,栈顶指针top+1,指向新插入元素的位置,因此,栈顶指针top的动态变化能反映栈中元素的变化情况。
当栈满时,不能继续做入栈操作,若继续入栈,就会产生“上溢”错误。
出栈运算
当栈不为空时,从栈顶位置取出栈顶元素的操作称为“出栈”。取出一个元素,栈顶指针top-1,指向新的栈顶元素所在位置。
当栈空时,不能继续做出栈操作,若继续出栈,就会产生“下溢”错误。
读栈顶元素
当栈不为空时,将栈顶元素赋给一个指定变量的操作称为“读栈顶元素”。这个操作不删除栈顶元素,因此栈顶指针top不会发生任何变化。
解题技巧:当题目中出现栈的存储空间为S(1:n),初始状态为top=0,top=-1,或者top=n+1,初始状态都按空栈处理。
带链式的栈
栈是一种线性结构,既可以采用顺序存储也可以采用链式存储。采用链式存储的栈称为链栈,也可以称为带链的栈。链栈不要求逻辑上相邻的两个元素在物理位置上也相邻。
在实际应用中,带链的栈可以用来收集计算机存储空间中所有空闲的存储结点,这种带链的栈称为可利用栈。可利用栈总是处于动态变化之中,其中栈顶指针和栈底指针会随着栈的操作而发生动态变化。
1.6队列
1.6.1队列的概念
队列也是一种操作受限的线性表,限定它只能在一端进行插入操作,在另一端进行删除操作。允许删除元素的一端称为队头,允许插入元素的一端称为队尾。由于先插入的元素总是先删除,后插入的元素总是后删除。因此,队列也被称为“先进先出”或者“后进后出”的线性表。
+---+---+---+---+---+---+---+---+
| 1 | 2 | 3 | 4 | 5 | | | |
+---+---+---+---+---+---+---+---+
^ ^
| |
队首指针 队尾指针
1.6.2队列的基本运算
入队和退队操作是队列的基本运算操作。在队列的基本运算中,通常用front表示队头指针(排头指针),用rear表示队尾指针。
当队列不满时,在队尾插入新元素的操作称为入队操作。当队列不为空时,在队头删除元素的操作称为退队操作。退队操作会引起队头指针front的变化,入队操作会引起队尾指针rear的变化。
1.6.3循环队列
在实际应用中,队列的顺序存储结构一般采用的是循环队列的形式。
循环队列是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间。
在循环队列中,用队尾指针rear指向队尾元素,用队头指针front指向队头元素的前一个位置,因此,从排头指针front指向的后一个位置直到队尾指针rear指向的位置之间所有的元素均为队列中的元素。
1.6.4循环队列中计算元素个数
第一种情况:
rear > front
元素个数= rear - front
第二种情况:
rear < front
元素个数= (rear - front) + 循环队列长度
第三种情况:
rear = front
循环队列为空(0个)或者满(Q个)
1.6.5带链的队列
队列也是一种线性结构,也可以采用链式存储结构。
与顺序队列相比,链式存储的队列在插入和删除元素时,操作更简单。且链式队列也是动态分配的,易于扩充,但需要为队列中的逻辑关系增加额外的存储空间。
1.7树
1.7.1树的概念
树是一种典型的非线性结构,它具有明显的层次性,与自然界中的树极其相似,因此而得名。
树是一种非线性数据结构,由节点和节点之间的关系构成。树的结构是层次性的,其中一个节点可以有多个子节点,但每个节点只有一个父节点(除了根节点)。下面是树的示意图:
A
/ \
B C
/ \ \
D E F
/ \
G H
在这个例子中,节点 A 是树的根节点,节点 B 和节点 C 是 A 的子节点。节点 D 和节点 E 是节点 B 的子节点,节点 F 是节点 C 的子节点。节点 G 和节点 H 是节点 E 的子节点。通过这种方式,树可以呈现出层级关系的结构,便于存储和操作具有层次关系的数据。
树是一种非线性的数据结构,由一组节点和连接节点的边组成。树的最顶端节点称为根节点,根节点下面的节点称为子节点,同一个父节点下面的子节点互相称为兄弟节点。树是一种重要的数据结构,常用于表达层级关系。
树的特点包括:
- 每个节点都有零个或多个子节点。
- 除了根节点外,每个节点都有且只有一个父节点。
- 不存在循环节点,即不能通过子节点的连接路径回到自己。
- 每个节点可以有任意多个子节点,但只有一个父节点。
树的应用广泛,例如:
- 文件目录结构可以用树来表示,其中每个文件夹是一个节点,子文件夹和文件是该节点的子节点。
- 分类学中的分类树用于组织和分类不同的生物种类。
- 数据库中的索引结构使用树来加速查询操作。
- 网络路由算法使用树来选择最佳的路由路径。
常见的树结构包括二叉树、二叉搜索树、AVL树、红黑树等。树是计算机科学中一个重要的概念,解决了很多实际问题的数据结构。
父结点:即结点的前件。除根结点外每一个结点只有一个父结点。
子结点:即结点的后件。
根结点:没有父结点的结点,一棵树只有一个根结点。
叶子结点:没有子结点的结点。
度:一个结点的后件个数称为该结点的度。
树的度:一棵树中所有结点中最大的度数称为树的度。
结点的深度:从根结点到该结点的累计层数。
树的深度:树的总层数即为树的深度。
子树:以某结点的一个子结点为根构成的树成为该结点的一棵子树。
1.7.2树的性质
树具有以下性质:
- 树是一个无向图,其中任意两个节点之间有且仅有一条路径。
- 树不包含环路,也不允许有重复的边。
- 树有一个特殊的节点,称为根节点。
- 除根节点外,每个节点都有一个父节点,并且可以有零个或多个子节点。
- 树的高度是从根节点到最远叶子节点的边的数量。
- 树的深度等于树的高度。
- 叶子节点是没有子节点的节点。
- 任意两个节点之间的路径长度是它们之间的边的数量。
- 树的总结点数等于每层结点树之和。即
- 树的总结点数等于所有不同度数的结点数之和。即
- 树的总结点树等于所有结点度数加1。即
1.7.3二叉树的概念
二叉树是一种特殊的树结构。二叉树具有以下两个特点:
非空二叉树只有一个根结点
每个结点至多有两棵子树(即结点的度小于等于2),分别称为该结点的左子树和右子树。
二叉树有五种基本形态,分别是没有结点,只有根结点,只有左子树、有左右子树和只有右子树。
二叉树的基本形态有以下五种:
- 完全二叉树(Complete Binary Tree):除了最后一层外,其他层的节点都是满的,并且最后一层的节点都尽量靠左。
- 注意:满二叉树一定是完全二叉树,但完全二叉树不一定是满二叉树。
1 / \ 2 3 / \ / \ 4 5 6 7 / \ 8 9
- 满二叉树(Full Binary Tree):每个节点都有两个子节点,除了叶子节点外。
A / \ B C / \ / \ D E F G
- 平衡二叉树(Balanced Binary Tree):左右子树的高度差不超过1的二叉树。平衡二叉树的查询和插入操作的时间复杂度是O(logn)。
7 / \ 4 9 / \ \ 2 5 10 \ 6
- 二叉搜索树(Binary Search Tree):左子树的所有节点的值都小于根节点的值,右子树的所有节点的值都大于根节点的值。
8 / \ 3 10 / \ \ 1 6 14 / \ / 4 7 13
- 红黑树(Red-Black Tree):是一种自平衡的二叉搜索树,通过对节点进行染色和旋转操作来保持平衡性,时间复杂度保持在O(logn)。
13 (黑) / \ 8 (红) 17 (红) / \ / \ 1 (黑) 11 (黑) / \ 9 (红) 15 (黑) / \ 14 (红) 25 (红)
二叉树的性质
性质1:非空二叉树的第K层上最多有个结点。
性质2:深度为K的二叉树,最多有个结点。
性质3:具有n个结点的二叉树,深度最少为[]+1,其中[]表示取的整数部分。
性质4:非空二叉树中,叶子结点数(度为0的结点)等于度为2的结点数加1,即。
二叉树的存储结构
一般二叉树采用链式存储;
满二叉树和完全二叉树可以采用顺序存储;
顺序存储对一般二叉树不适合。
1.7.4二叉树的遍历
二叉树的遍历是指按某条搜索路径访问树中的每个结点,使得每个结点均被访问一次而且仅被访问一次。
常见的遍历方法有前序遍历(DLR)、中序遍历(LDR)和后序遍历(LRD)三种。
前序遍历
在遍历二叉树时,先访问根结点,再遍历左子树,最后遍历右子树。
在遍历左子树和右子树时,也是先访问该子树的根结点,在遍历其左子树,最后遍历其右子树。即“根左右”。
前序遍历是指先遍历根节点,然后遍历左子树,最后遍历右子树。下面给出前序遍历的示例样子:
1 / \ 2 3 / \ \ 4 5 6
前序遍历结果为:1 2 4 5 3 6
中序遍历
在遍历二叉树时,先遍历左子树,再访问根结点,最后遍历右子树。
在遍历左子树和右子树时,也是先遍历其左子树,再访问该子树的根结点,最后遍历其右子树。即“左根右”。
中序遍历是一种二叉树的遍历方式,其遍历顺序为:先遍历左子树,然后访问根节点,最后遍历右子树。
下面是一个中序遍历的示例二叉树:
A / \ B C / / \ D E F
中序遍历的结果为:D -> B -> A -> E -> C -> F
后序遍历
在遍历二叉树时,先遍历左子树,再遍历右子树,最后访问根结点。
在遍历左子树和右子树时,也是先遍历其左子树,再遍历其右子树,最后访问该子树的根结点。即“左右根”。
后序遍历是一种遍历二叉树的方式,其中左子树、右子树、根节点的顺序依次被访问。下面是一个二叉树的后序遍历样例:
1 / \ 2 3 / \ / \ 4 5 6 7
后序遍历结果为: 4, 5, 2, 6, 7, 3, 1
注意:前序遍历序列和中序遍历可以唯一确定一棵二叉树;
后序遍历序列和中序遍历可以唯一确定一棵二叉树。