Data Structures Algorithms 简明教程
Overview
Data Structure(数据结构)是一种系统的方式,以便有效利用数据。以下术语是数据结构的基本术语。
Data Structure is a systematic way to organize data in order to use it efficiently. Following terms are the foundation terms of a data structure.
-
Interface − Each data structure has an interface. Interface represents the set of operations that a data structure supports. An interface only provides the list of supported operations, type of parameters they can accept and return type of these operations.
-
Implementation − Implementation provides the internal representation of a data structure. Implementation also provides the definition of the algorithms used in the operations of the data structure.
Characteristics of a Data Structure
-
Correctness − Data structure implementation should implement its interface correctly.
-
Time Complexity − Running time or the execution time of operations of data structure must be as small as possible.
-
Space Complexity − Memory usage of a data structure operation should be as little as possible.
Need for Data Structure
随着应用程序变得复杂且数据丰富,如今,应用程序面临着三个常见问题。
As applications are getting complex and data rich, there are three common problems that applications face now-a-days.
-
Data Search − Consider an inventory of 1 million(106) items of a store. If the application is to search an item, it has to search an item in 1 million(106) items every time slowing down the search. As data grows, search will become slower.
-
Processor speed − Processor speed although being very high, falls limited if the data grows to billion records.
-
Multiple requests − As thousands of users can search data simultaneously on a web server, even the fast server fails while searching the data.
为了解决上述问题,数据结构应运而生。数据可以按数据结构组织,按这种方式,可能不需要搜索所有项目,并且可以几乎即时搜索所需数据。
To solve the above-mentioned problems, data structures come to rescue. Data can be organized in a data structure in such a way that all items may not be required to be searched, and the required data can be searched almost instantly.
Execution Time Cases
在三个案例中,通常会以相对方式比较各种数据结构的执行时间。
There are three cases which are usually used to compare various data structure’s execution time in a relative manner.
-
Worst Case − This is the scenario where a particular data structure operation takes maximum time it can take. If an operation’s worst case time is ƒ(n) then this operation will not take more than ƒ(n) time where ƒ(n) represents function of n.
-
Average Case − This is the scenario depicting the average execution time of an operation of a data structure. If an operation takes ƒ(n) time in execution, then m operations will take mƒ(n) time.
-
Best Case − This is the scenario depicting the least possible execution time of an operation of a data structure. If an operation takes ƒ(n) time in execution, then the actual operation may take time as the random number which would be maximum as ƒ(n).
Basic Terminology
-
Data − Data are values or set of values.
-
Data Item − Data item refers to single unit of values.
-
Group Items − Data items that are divided into sub items are called as Group Items.
-
Elementary Items − Data items that cannot be divided are called as Elementary Items.
-
Attribute and Entity − An entity is that which contains certain attributes or properties, which may be assigned values.
-
Entity Set − Entities of similar attributes form an entity set.
-
Field − Field is a single elementary unit of information representing an attribute of an entity.
-
Record − Record is a collection of field values of a given entity.
-
File − File is a collection of records of the entities in a given entity set.
Environment Setup
Local Environment Setup
如果你仍然愿意为 C 编程语言设置环境,则需要在计算机上有以下两个工具:(a)文本编辑器和(b)C 编译器。
If you are still willing to set up your environment for C programming language, you need the following two tools available on your computer, (a) Text Editor and (b) The C Compiler.
Text Editor
这将用于键入您的程序。一些编辑器的示例包括 Windows 记事本、OS 编辑命令、Brief、Epsilon、EMACS 以及 vim 或 vi。
This will be used to type your program. Examples of few editors include Windows Notepad, OS Edit command, Brief, Epsilon, EMACS, and vim or vi.
文本编辑器的名称和版本可能因不同的操作系统而异。例如,记事本将用于 Windows,而且 vim 或 vi 也可用于 Windows 以及 Linux 或 UNIX。
The name and the version of the text editor can vary on different operating systems. For example, Notepad will be used on Windows, and vim or vi can be used on Windows as well as Linux or UNIX.
您使用编辑器创建的文件称为源文件,其中包含程序源代码。C 程序的源文件通常以扩展名 " .c " 命名。
The files you create with your editor are called source files and contain program source code. The source files for C programs are typically named with the extension ".c".
在开始编程之前,请确保到位文本编辑器,并且你具备编写计算机程序、将其保存在文件中、对其进行编译,最后执行它的经验。
Before starting your programming, make sure you have one text editor in place and you have enough experience to write a computer program, save it in a file, compile it, and finally execute it.
The C Compiler
写在源文件中的源代码是程序的人类可读源代码。它需要“编译”才能变为机器语言,以便 CPU 实际上按照给定的说明执行程序。
The source code written in the source file is the human readable source for your program. It needs to be "compiled", to turn into machine language so that your CPU can actually execute the program as per the given instructions.
此 C 编程语言编译器将用于将你的源代码编译为最终的可执行程序。我们假设你对编程语言编译器具备基础知识。
This C programming language compiler will be used to compile your source code into a final executable program. We assume you have the basic knowledge about a programming language compiler.
使用最频繁且免费的编译器是 GNU C/C++ 编译器。否则,如果具有相应的操作系统 (OS),你可以有 Hewlett-Packard (HP) 或 Solaris 的编译器。
Most frequently used and free available compiler is GNU C/C++ compiler. Otherwise, you can have compilers either from HP or Solaris if you have respective Operating Systems (OS).
以下部分指导如何在各种操作系统上安装 GNU C/C 编译器。我们一同提到 C/C,因为 GNU GCC 编译器同时适用于 C 和 C++ 编程语言。
The following section guides you on how to install GNU C/C compiler on various OS. We are mentioning C/C together because GNU GCC compiler works for both C and C++ programming languages.
Installation on UNIX/Linux
如果您使用 Linux or UNIX ,那么通过从命令行输入以下命令来检查您的系统上是否安装了 GCC -
If you are using Linux or UNIX, then check whether GCC is installed on your system by entering the following command from the command line −
$ gcc -v
如果你在自己的机器上安装了 GNU 编译器,则它应当打印如下消息:
If you have GNU compiler installed on your machine, then it should print a message such as the following −
Using built-in specs.
Target: i386-redhat-linux
Configured with: ../configure --prefix = /usr .......
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-46)
如果未安装 GCC,则您将不得不使用 https://gcc.gnu.org/install/ 中提供的详细说明自己安装它。
If GCC is not installed, then you will have to install it yourself using the detailed instructions available at https://gcc.gnu.org/install/
本教程是基于 Linux 编写的,并且所有给定的示例都已在 Linux 系统的 Cent OS 版本中编译。
This tutorial has been written based on Linux and all the given examples have been compiled on Cent OS flavor of Linux system.
Installation on Mac OS
如果你使用 Mac OS X,获取 GCC 的最简单方法是从 Apple 网站下载 Xcode 开发环境,并遵循简单的安装说明。一旦设置了 Xcode,你将能够使用 GNU 编译器针对 C/C++。
If you use Mac OS X, the easiest way to obtain GCC is to download the Xcode development environment from Apple’s website and follow the simple installation instructions. Once you have Xcode setup, you will be able to use GNU compiler for C/C++.
Xcode 当前可从 developer.apple.com/technologies/tools/ 获取。
Xcode is currently available at developer.apple.com/technologies/tools/
Installation on Windows
要在 Windows 上安装 GCC,需要安装 MinGW。要安装 MinGW,请访问 MinGW 主页 www.mingw.org ,然后单击 MinGW 下载页面的链接。下载 MinGW 安装程序的最新版本,它应当命名为 MinGW-<版本>.exe。
To install GCC on Windows, you need to install MinGW. To install MinGW, go to the MinGW homepage, www.mingw.org, and follow the link to the MinGW download page. Download the latest version of the MinGW installation program, which should be named MinGW-<version>.exe.
安装 MinWG 时,您至少必须安装 gcc-core、gcc-g++、binutils 和 MinGW 运行时,但您可能希望安装更多。
While installing MinWG, at a minimum, you must install gcc-core, gcc-g++, binutils, and the MinGW runtime, but you may wish to install more.
将 MinGW 安装的 bin 子目录添加到您的 PATH 环境变量,以便您可以通过其简单名称在命令行中指定这些工具。
Add the bin subdirectory of your MinGW installation to your PATH environment variable, so that you can specify these tools on the command line by their simple names.
安装完成后,你将可以使用 Windows 命令行来运行 gcc、g++、ar、ranlib、dlltool 以及其他一些 GNU 工具。
When the installation is complete, you will be able to run gcc, g++, ar, ranlib, dlltool, and several other GNU tools from the Windows command line.
Data Structure Basics
本章介绍数据结构相关的基本术语。
This chapter explains the basic terms related to data structure.
Data Definition
数据定义通过以下特征定义特定数据。
Data Definition defines a particular data with the following characteristics.
-
Atomic − Definition should define a single concept.
-
Traceable − Definition should be able to be mapped to some data element.
-
Accurate − Definition should be unambiguous.
-
Clear and Concise − Definition should be understandable.
Data Type
数据类型是一种分类各种数据类型(例如整数、字符串等)的方式,它确定哪些值可以与相应的数据类型配合使用,哪些类型的操作可以对相应的数据类型执行。有两种数据类型:
Data type is a way to classify various types of data such as integer, string, etc. which determines the values that can be used with the corresponding type of data, the type of operations that can be performed on the corresponding type of data. There are two data types −
-
Built-in Data Type
-
Derived Data Type
Built-in Data Type
编程语言内置支持的数据类型称为内置数据类型。例如,大多数语言提供以下内置数据类型。
Those data types for which a language has built-in support are known as Built-in Data types. For example, most of the languages provide the following built-in data types.
-
Integers
-
Boolean (true, false)
-
Floating (Decimal numbers)
-
Character and Strings
Derived Data Type
那些实现无关的数据类型称为派生数据类型,因为它们可以用这样或那样的方式实现。这些数据类型通常是通过组合基本数据类型或内置数据类型和对它们的关联操作而构建的。例如 −
Those data types which are implementation independent as they can be implemented in one or the other way are known as derived data types. These data types are normally built by the combination of primary or built-in data types and associated operations on them. For example −
-
List
-
Array
-
Stack
-
Queue
Basic Operations
数据结构中的数据由某些操作处理。所选的具体数据结构很大程度上取决于需要在数据结构上执行的操作的频率。
The data in the data structures are processed by certain operations. The particular data structure chosen largely depends on the frequency of the operation that needs to be performed on the data structure.
-
Traversing
-
Searching
-
Insertion
-
Deletion
-
Sorting
-
Merging
Data Structures and Types
Data structures 在编程语言中引入,目的是存储、组织和操作数据。它们的设计方式让访问和处理数据变得更加容易和简单。这些数据结构不仅限于一种特定的编程语言;它们只是一些在内存中构建数据的代码片段。
Data structures are introduced in order to store, organize and manipulate data in programming languages. They are designed in a way that makes accessing and processing of the data a little easier and simpler. These data structures are not confined to one particular programming language; they are just pieces of code that structure data in the memory.
数据类型经常被误认为数据结构的一种类型,但即使它们被称为抽象数据类型,也不是完全正确的。数据类型表示数据类型,而数据结构只是一组类似或不同的数据类型。
Data types are often confused as a type of data structures, but it is not precisely correct even though they are referred to as Abstract Data Types. Data types represent the nature of the data while data structures are just a collection of similar or different data types in one.
通常只有两种类型的数据结构 −
There are usually just two types of data structures −
-
Linear
-
Non-Linear
Linear Data Structures
数据存储在线性数据结构中按顺序。这些是基本结构,因为元素按顺序存储,而无需应用任何数学运算。
The data is stored in linear data structures sequentially. These are rudimentary structures since the elements are stored one after the other without applying any mathematical operations.
线性数据结构通常很容易实现,但由于内存分配可能变得复杂,时间和空间复杂度会增加。一些线性数据结构的示例包括 −
Linear data structures are usually easy to implement but since the memory allocation might become complicated, time and space complexities increase. Few examples of linear data structures include −
-
Arrays
-
Linked Lists
-
Stacks
-
Queues
基于数据存储方法,这些线性数据结构分为 static 和 dynamic 数据结构。
Based on the data storage methods, these linear data structures are divided into two sub-types. They are − static and dynamic data structures.
Static Linear Data Structures
在静态线性数据结构中,内存分配不可扩展。一旦使用整个内存,就不能再检索更多空间来存储更多数据。因此,需要根据程序的大小来预留内存。这也将成为一个缺点,因为保留比所需更多的内存会导致内存块浪费。
In Static Linear Data Structures, the memory allocation is not scalable. Once the entire memory is used, no more space can be retrieved to store more data. Hence, the memory is required to be reserved based on the size of the program. This will also act as a drawback since reserving more memory than required can cause a wastage of memory blocks.
静态线性数据结构的最佳示例是数组。
The best example for static linear data structures is an array.
Dynamic Linear Data Structures
在动态线性数据结构中,内存分配可以在需要时动态进行。考虑到程序的空间复杂度,这些数据结构是高效的。
In Dynamic linear data structures, the memory allocation can be done dynamically when required. These data structures are efficient considering the space complexity of the program.
一些动态线性数据结构的示例包括:链表、堆栈和队列。
Few examples of dynamic linear data structures include: linked lists, stacks and queues.
Non-Linear Data Structures
非线性数据结构以层次结构的形式存储数据。因此,与线性数据结构相比,数据可以在多个级别中找到,并且难以遍历。
Non-Linear data structures store the data in the form of a hierarchy. Therefore, in contrast to the linear data structures, the data can be found in multiple levels and are difficult to traverse through.
但是,它们旨在克服线性数据结构的问题和限制。例如,线性数据结构的主要缺点是内存分配。由于数据在线性数据结构中按顺序分配,因此这些数据结构中的每个元素都使用一个完整的内存块。然而,如果数据使用的内存少于分配的块所能容纳的,那么块中的额外内存空间就会被浪费。因此,非线性数据结构应运而生。它们降低了空间复杂度,并最优地使用了内存。
However, they are designed to overcome the issues and limitations of linear data structures. For instance, the main disadvantage of linear data structures is the memory allocation. Since the data is allocated sequentially in linear data structures, each element in these data structures uses one whole memory block. However, if the data uses less memory than the assigned block can hold, the extra memory space in the block is wasted. Therefore, non-linear data structures are introduced. They decrease the space complexity and use the memory optimally.
一些非线性数据结构类型 −
Few types of non-linear data structures are −
-
Graphs
-
Trees
-
Tries
-
Maps
Array Data Structure
数组是一种线性数据结构,定义为具有相同或不同数据类型的元素集合。它们以单维和多维形式存在。当需要将多个类似性质的元素一起存储在同一个地方时,这些数据结构才会出现。
Array is a type of linear data structure that is defined as a collection of elements with same or different data types. They exist in both single dimension and multiple dimensions. These data structures come into picture when there is a necessity to store multiple elements of similar nature together at one place.
数组索引和内存地址之间的区别在于,数组索引充当键值来标记数组中的元素。然而,内存地址是可用空闲内存的起始地址。
The difference between an array index and a memory address is that the array index acts like a key value to label the elements in the array. However, a memory address is the starting address of free memory available.
以下是理解数组概念的重要术语。
Following are the important terms to understand the concept of Array.
-
Element − Each item stored in an array is called an element.
-
Index − Each location of an element in an array has a numerical index, which is used to identify the element.
Syntax
在 C 和 C++ 编程语言中创建数组 −
Creating an array in C and C++ programming languages −
data_type array_name[array_size] = {elements separated using commas}
or,
data_type array_name[array_size];
在 Java 编程语言中创建数组 −
Creating an array in Java programming language −
data_type[] array_name = {elements separated by commas}
or,
data_type array_name = new data_type[array_size];
Need for Arrays
数组用作从小型排序问题到更为复杂问题(例如旅行商问题)的许多问题的解决方案。除了数组以外,还有许多能够为这些问题提供有效时间和空间复杂度的数据结构,那么使用数组有什么好处?答案在于随机访问查找时间。
Arrays are used as solutions to many problems from the small sorting problems to more complex problems like travelling salesperson problem. There are many data structures other than arrays that provide efficient time and space complexity for these problems, so what makes using arrays better? The answer lies in the random access lookup time.
数组提供了 O(1) 的随机访问查找时间。这意味着访问数组的第 1 个索引和数组的第 1000 个索引所需的时间都是一样的。这是由于数组带有指针和偏移值。指针指向内存的正确位置,偏移值表明在所述内存中查找距离。
Arrays provide O(1) random access lookup time. That means, accessing the 1st index of the array and the 1000th index of the array will both take the same time. This is due to the fact that array comes with a pointer and an offset value. The pointer points to the right location of the memory and the offset value shows how far to look in the said memory.
array_name[index]
| |
Pointer Offset
因此,在包含 6 个元素的数组中,要访问第 1 个元素,数组指向第 0 个索引。类似地,要访问第 6 个元素,数组指向第 5 个索引。
Therefore, in an array with 6 elements, to access the 1st element, array is pointed towards the 0th index. Similarly, to access the 6th element, array is pointed towards the 5th index.
Array Representation
数组表示为一系列存储桶,其中每个存储桶存储一个元素。这些存储桶的索引从“0”到“n-1”,其中 n 是该特定数组的大小。例如,大小为 10 的数组将具有索引为 0 到 9 的存储桶。
Arrays are represented as a collection of buckets where each bucket stores one element. These buckets are indexed from ‘0’ to ‘n-1’, where n is the size of that particular array. For example, an array with size 10 will have buckets indexed from 0 to 9.
此索引也将类似于多维数组。如果它是一个二维数组,它将在每个存储桶中具有子存储桶。然后,它将被索引为 array_name[m][n],其中 m 和 n 是数组中每一级的尺寸。
This indexing will be similar for the multidimensional arrays as well. If it is a 2-dimensional array, it will have sub-buckets in each bucket. Then it will be indexed as array_name[m][n], where m and n are the sizes of each level in the array.
根据上述说明,以下是要考虑的重要事项。
As per the above illustration, following are the important points to be considered.
-
Index starts with 0.
-
Array length is 9 which means it can store 9 elements.
-
Each element can be accessed via its index. For example, we can fetch an element at index 6 as 23.
Basic Operations in the Arrays
数组中的基本操作是插入、删除、搜索、显示、遍历和更新。通常执行这些操作以修改数组中的数据或报告数组的状态。
The basic operations in the Arrays are insertion, deletion, searching, display, traverse, and update. These operations are usually performed to either modify the data in the array or to report the status of the array.
数组支持的基本操作如下。
Following are the basic operations supported by an array.
-
Traverse − print all the array elements one by one.
-
Insertion − Adds an element at the given index.
-
Deletion − Deletes an element at the given index.
-
Search − Searches an element using the given index or by the value.
-
Update − Updates an element at the given index.
-
Display − Displays the contents of the array.
在 C 中,当一个数组用大小初始化时,它会按以下顺序将其默认值分配给其元素。
In C, when an array is initialized with size, then it assigns defaults values to its elements in following order.
Insertion Operation
在插入操作中,我们将一个或多个元素添加到数组。根据要求,可以在数组的开头、结尾或任何给定索引处添加新元素。这是使用编程语言的输入语句完成的。
In the insertion operation, we are adding one or more elements to the array. Based on the requirement, a new element can be added at the beginning, end, or any given index of array. This is done using input statements of the programming languages.
Algorithm
以下是将元素插入线性阵列中的算法,直到我们到达数组的末尾 −
Following is an algorithm to insert elements into a Linear Array until we reach the end of the array −
1. Start
2. Create an Array of a desired datatype and size.
3. Initialize a variable ‘i’ as 0.
4. Enter the element at ith index of the array.
5. Increment i by 1.
6. Repeat Steps 4 & 5 until the end of the array.
7. Stop
在这里,我们看到了插入操作的实际实现,我们在其中在数组的末尾添加数据 −
Here, we see a practical implementation of insertion operation, where we add data at the end of the array −
Deletion Operation
在此数组操作中,我们将从数组的特定索引处删除一个元素。当我们将后续索引中的值分配给当前索引时,就会发生此删除操作。
In this array operation, we delete an element from the particular index of an array. This deletion operation takes place as we assign the value in the consequent index to the current index.
Algorithm
考虑 LA 是一个包含 N 个元素的线性数组,而 K 是一个正整数,使得 K⇐N。以下是删除 LA 的第 K 个位置的元素的算法。
Consider LA is a linear array with N elements and K is a positive integer such that K⇐N. Following is the algorithm to delete an element available at the Kth position of LA.
1. Start
2. Set J = K
3. Repeat steps 4 and 5 while J < N
4. Set LA[J] = LA[J + 1]
5. Set J = J+1
6. Set N = N-1
7. Stop
Search Operation
使用键在数组中搜索元素;键元素顺序比较数组中的每一个值,以检查键是否存在于数组中。
Searching an element in the array using a key; The key element sequentially compares every value in the array to check if the key is present in the array or not.
Algorithm
根据 LA 是一个包含 N 个元素的线性数组以及 K 是正整数(使得 K⇐N)的条件。以下为使用顺序搜索查找值 ITEM 的元素的算法。
Consider LA is a linear array with N elements and K is a positive integer such that K⇐N. Following is the algorithm to find an element with a value of ITEM using sequential search.
1. Start
2. Set J = 0
3. Repeat steps 4 and 5 while J < N
4. IF LA[J] is equal ITEM THEN GOTO STEP 6
5. Set J = J +1
6. PRINT J, ITEM
7. Stop
Traversal Operation
此操作会遍历数组的所有元素。我们使用循环语句完成此操作。
This operation traverses through all the elements of an array. We use loop statements to carry this out.
Algorithm
以下是遍历线性数组中所有元素的算法−
Following is the algorithm to traverse through all the elements present in a Linear Array −
1 Start
2. Initialize an Array of certain size and datatype.
3. Initialize another variable ‘i’ with 0.
4. Print the ith value in the array and increment i.
5. Repeat Step 4 until the end of the array is reached.
6. End
Update Operation
更新操作是指在给定索引处从数组中更新现有元素。
Update operation refers to updating an existing element from the array at a given index.
Linked List Data Structure
如果数组容纳的数据类型相似,则链表包含数据类型不同的元素,且按顺序排列。
If arrays accommodate similar types of data types, linked lists consist of elements with different data types that are also arranged sequentially.
但如何创建这些链表?
But how are these linked lists created?
链表是通过链接连接在一起的“节点”集合。这些节点包含要存储的数据以及链表中下一个节点地址的指针。在数组中,大小限制在定义中,但在链表中没有定义的大小。任何数量的数据都可以存储在链表中并可从中删除。
A linked list is a collection of “nodes” connected together via links. These nodes consist of the data to be stored and a pointer to the address of the next node within the linked list. In the case of arrays, the size is limited to the definition, but in linked lists, there is no defined size. Any amount of data can be stored in it and can be deleted from it.
有三种类型的链表−
There are three types of linked lists −
-
Singly Linked List − The nodes only point to the address of the next node in the list.
-
Doubly Linked List − The nodes point to the addresses of both previous and next nodes.
-
Circular Linked List − The last node in the list will point to the first node in the list. It can either be singly linked or doubly linked.
Linked List Representation
可以将链表可视化为一个节点链,其中每个节点指向下一个节点。
Linked list can be visualized as a chain of nodes, where every node points to the next node.
根据上述说明,以下是要考虑的重要事项。
As per the above illustration, following are the important points to be considered.
-
Linked List contains a link element called first (head).
-
Each link carries a data field(s) and a link field called next.
-
Each link is linked with its next link using its next link.
-
Last link carries a link as null to mark the end of the list.
Types of Linked List
以下为不同类型的链表。
Following are the various types of linked list.
Singly Linked Lists
单链表在每个节点中包含两个“存储单元”;一个存储单元保存数据,而另一个存储单元保存列表中下一个节点的地址。只能向一个方向遍历,因为同一列表的两个节点之间只存在一个链接。
Singly linked lists contain two “buckets” in one node; one bucket holds the data and the other bucket holds the address of the next node of the list. Traversals can be done in one direction only as there is only a single link between two nodes of the same list.
Doubly Linked Lists
双链表在每个节点中包含三个“存储单元”;一个存储单元保存数据,而其他存储单元保存列表中前一个和下一个节点的地址。列表被遍历两次,因为列表中的节点从两侧相互连接。
Doubly Linked Lists contain three “buckets” in one node; one bucket holds the data and the other buckets hold the addresses of the previous and next nodes in the list. The list is traversed twice as the nodes in the list are connected to each other from both sides.
Circular Linked Lists
循环链表可存在于单向链表和双向链表。
Circular linked lists can exist in both singly linked list and doubly linked list.
由于循环链表中的最后一个节点与第一个节点相连接,因此对该链表中的遍历将一直继续,直到它被中断。
Since the last node and the first node of the circular linked list are connected, the traversal in this linked list will go on forever until it is broken.
Basic Operations in the Linked Lists
链表中的基本操作包括插入、删除、搜索、显示以及删除指定关键处的元素。这些操作在单向链表中执行,如下所示:
The basic operations in the linked lists are insertion, deletion, searching, display, and deleting an element at a given key. These operations are performed on Singly Linked Lists as given below −
-
Insertion − Adds an element at the beginning of the list.
-
Deletion − Deletes an element at the beginning of the list.
-
Display − Displays the complete list.
-
Search − Searches an element using the given key.
-
Delete − Deletes an element using the given key.
Insertion Operation
在链表中添加一个新节点是多步活动。我们将在此处借助图表了解此内容。首先,使用相同结构创建一个节点,并找到要插入该节点的位置。
Adding a new node in linked list is a more than one step activity. We shall learn this with diagrams here. First, create a node using the same structure and find the location where it has to be inserted.
想象一下,我们在 A(LeftNode)和 C(RightNode)之间插入一个节点 B(NewNode)。然后将 B.next 指向 C -
Imagine that we are inserting a node B (NewNode), between A (LeftNode) and C (RightNode). Then point B.next to C −
NewNode.next −> RightNode;
它应该看起来像这样 -
It should look like this −
现在,左侧的下一个节点应该指向新节点。
Now, the next node at the left should point to the new node.
LeftNode.next −> NewNode;
这会将新节点放在两个节点中间。新列表应该看起来像这样 -
This will put the new node in the middle of the two. The new list should look like this −
可以在链表中以三种不同的方式进行插入。解释如下:
Insertion in linked list can be done in three different ways. They are explained as follows −
Insertion at Beginning
在此操作中,我们在列表开头添加了一个元素。
In this operation, we are adding an element at the beginning of the list.
Algorithm
1. START
2. Create a node to store the data
3. Check if the list is empty
4. If the list is empty, add the data to the node and assign the head pointer to it.
5 If the list is not empty, add the data to a node and link to the current head. Assign the head to the newly added node.
6. END
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Insertion at Ending
在此操作中,我们在列表结尾添加了一个元素。
In this operation, we are adding an element at the ending of the list.
Algorithm
1. START
2. Create a new node and assign the data
3. Find the last node
4. Point the last node to new node
5. END
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Insertion at a Given Position
在此操作中,我们在列表中的任意位置添加了一个元素。
In this operation, we are adding an element at any position within the list.
Deletion Operation
删除也是一个多步过程。我们将以图片表示形式进行学习。首先,使用搜索算法找到要删除的目标节点。
Deletion is also a more than one step process. We shall learn with pictorial representation. First, locate the target node to be removed, by using searching algorithms.
现在,目标节点的左侧(前一个)节点应该指向目标节点的下一个节点 -
The left (previous) node of the target node now should point to the next node of the target node −
LeftNode.next −> TargetNode.next;
这将删除指向目标节点的链接。现在,使用以下代码,我们将删除目标节点指向的内容。
This will remove the link that was pointing to the target node. Now, using the following code, we will remove what the target node is pointing at.
TargetNode.next −> NULL;
我们需要使用已删除的节点。我们可以将它保留在内存中,否则我们可以简单地释放内存并完全擦除目标节点。
We need to use the deleted node. We can keep that in memory otherwise we can simply deallocate memory and wipe off the target node completely.
如果节点被插入列表的开头,则应采取类似的步骤。在将其插入末尾时,列表的倒数第二个节点应指向新节点,并且新节点将指向 NULL。
Similar steps should be taken if the node is being inserted at the beginning of the list. While inserting it at the end, the second last node of the list should point to the new node and the new node will point to NULL.
链表中的删除也通过三种不同的方式执行。它们如下 -
Deletion in linked lists is also performed in three different ways. They are as follows −
Deletion at Beginning
在此链表的删除操作中,我们正在从列表的开头删除元素。为此,我们将头部指向第二个节点。
In this deletion operation of the linked, we are deleting an element from the beginning of the list. For this, we point the head to the second node.
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Deletion at Ending
在此链表的删除操作中,我们正在从列表的结尾删除元素。
In this deletion operation of the linked, we are deleting an element from the ending of the list.
Algorithm
1. START
2. Iterate until you find the second last element in the list.
3. Assign NULL to the second last element in the list.
4. END
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Deletion at a Given Position
在此链表的删除操作中,我们正在从列表的任何位置删除元素。
In this deletion operation of the linked, we are deleting an element at any position of the list.
Reverse Operation
此操作是全面的。我们需要使最后一个节点由头节点指向并反转整个链表。
This operation is a thorough one. We need to make the last node to be pointed by the head node and reverse the whole linked list.
首先,我们遍历到列表的末尾。它应指向 NULL。现在,我们将其指向其前一个节点 -
First, we traverse to the end of the list. It should be pointing to NULL. Now, we shall make it point to its previous node −
我们必须确保最后一个节点不是最后一个节点。因此,我们将拥有一个 temp 节点,它看起来像指向最后一个节点的头节点。现在,我们将使所有左侧节点逐一指向其前一个节点。
We have to make sure that the last node is not the last node. So we’ll have some temp node, which looks like the head node pointing to the last node. Now, we shall make all left side nodes point to their previous nodes one by one.
除了由头节点指向的节点(第一个节点),所有节点都应指向其前驱,使其成为其新的后继。第一个节点将指向 NULL。
Except the node (first node) pointed by the head node, all nodes should point to their predecessor, making them their new successor. The first node will point to NULL.
我们将使头节点使用 temp 节点指向新的第一个节点。
We’ll make the head node point to the new first node by using the temp node.
Algorithm
反转链表的分步过程如下 -
Step by step process to reverse a linked list is as follows −
1 START
2. We use three pointers to perform the reversing: prev, next, head.
3. Point the current node to head and assign its next value to the prev node.
4. Iteratively repeat the step 3 for all the nodes in the list.
5. Assign head to the prev node.
Search Operation
使用键元素在列表中搜索元素。此操作与数组搜索相同;比较列表中的每个元素与给定的键元素。
Searching for an element in the list using a key element. This operation is done in the same way as array search; comparing every element in the list with the key element given.
Doubly Linked List Data Structure
双向链表是链表的一个变体,与单向链表相比,双向链表可以轻松地双向导航。以下是理解双向链表概念的一些重要术语。
Doubly Linked List is a variation of Linked list in which navigation is possible in both ways, either forward and backward easily as compared to Single Linked List. Following are the important terms to understand the concept of doubly linked list.
-
Link − Each link of a linked list can store a data called an element.
-
Next − Each link of a linked list contains a link to the next link called Next.
-
Prev − Each link of a linked list contains a link to the previous link called Prev.
-
Linked List − A Linked List contains the connection link to the first link called First and to the last link called Last.
Doubly Linked List Representation
根据上述说明,以下是要考虑的重要事项。
As per the above illustration, following are the important points to be considered.
-
Doubly Linked List contains a link element called first and last.
-
Each link carries a data field(s) and a link field called next.
-
Each link is linked with its next link using its next link.
-
Each link is linked with its previous link using its previous link.
-
The last link carries a link as null to mark the end of the list.
Basic Operations
以下是列表支持的基本操作。
Following are the basic operations supported by a list.
-
Insertion − Adds an element at the beginning of the list.
-
Deletion − Deletes an element at the beginning of the list.
-
Insert Last − Adds an element at the end of the list.
-
Delete Last − Deletes an element from the end of the list.
-
Insert After − Adds an element after an item of the list.
-
Delete − Deletes an element from the list using the key.
-
Display forward − Displays the complete list in a forward manner.
-
Display backward − Displays the complete list in a backward manner.
Insertion at the Beginning
在此操作中,我们创建了一个包含三个区室的新节点,其中一个包含数据,另一个包含列表中其前一个和下一个节点的地址。这个新节点插入列表开头。
In this operation, we create a new node with three compartments, one containing the data, the others containing the address of its previous and next nodes in the list. This new node is inserted at the beginning of the list.
Algorithm
1. START
2. Create a new node with three variables: prev, data, next.
3. Store the new data in the data variable
4. If the list is empty, make the new node as head.
5. Otherwise, link the address of the existing first node to the next variable of the new node, and assign null to the prev variable.
6. Point the head to the new node.
7. END
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Deletion at the Beginning
此删除操作会删除双向链表中现有的第一个节点。头指针移到下一个节点,并移除链接。
This deletion operation deletes the existing first nodes in the doubly linked list. The head is shifted to the next node and the link is removed.
Algorithm
1. START
2. Check the status of the doubly linked list
3. If the list is empty, deletion is not possible
4. If the list is not empty, the head pointer is shifted to the next node.
5. END
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Insertion at the End
在此插入操作中,新输入节点将添加到双向链表的末尾;如果列表不为空。如果列表为空,则头指针将指向新节点。
In this insertion operation, the new input node is added at the end of the doubly linked list; if the list is not empty. The head will be pointed to the new node, if the list is empty.
Circular Linked List Data Structure
Circular Linked List 是链表的一个变体,其中第一个元素指向最后一个元素,而最后一个元素指向第一个元素。单向链表和双向链表都可以变成循环链表。
Circular Linked List is a variation of Linked list in which the first element points to the last element and the last element points to the first element. Both Singly Linked List and Doubly Linked List can be made into a circular linked list.
Singly Linked List as Circular
在单向链表中,最后一个节点的下一个指针指向第一个节点。
In singly linked list, the next pointer of the last node points to the first node.
Doubly Linked List as Circular
在双向链表中,最后一个节点的下一个指针指向第一个节点,而第一个节点的前一个指针指向最后一个节点,使两者都成为循环。
In doubly linked list, the next pointer of the last node points to the first node and the previous pointer of the first node points to the last node making the circular in both directions.
根据上述说明,以下是要考虑的重要事项。
As per the above illustration, following are the important points to be considered.
-
The last link’s next points to the first link of the list in both cases of singly as well as doubly linked list.
-
The first link’s previous points to the last of the list in case of doubly linked list.
Basic Operations
循环列表支持以下重要操作。
Following are the important operations supported by a circular list.
-
insert − Inserts an element at the start of the list.
-
delete − Deletes an element from the start of the list.
-
display − Displays the list.
Insertion Operation
循环链表的插入操作只在列表开头插入元素。这不同于通常的单链表和双向链表,因为此列表中没有特定的开始和结束点。插入在列表的开头或特定节点(或给定位置)之后。
The insertion operation of a circular linked list only inserts the element at the start of the list. This differs from the usual singly and doubly linked lists as there is no particular starting and ending points in this list. The insertion is done either at the start or after a particular node (or a given position) in the list.
Algorithm
1. START
2. Check if the list is empty
3. If the list is empty, add the node and point the head to this node
4. If the list is not empty, link the existing head as the next node to the new node.
5. Make the new node as the new head.
6. END
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Deletion Operation
循环链表的删除操作可以从列表中删除特定节点。这种类型的列表中的删除操作可以在开始处或给定位置或结束处执行。
The Deletion operation in a Circular linked list removes a certain node from the list. The deletion operation in this type of lists can be done at the beginning, or a given position, or at the ending.
Algorithm
1. START
2. If the list is empty, then the program is returned.
3. If the list is not empty, we traverse the list using a current pointer that is set to the head pointer and create another pointer previous that points to the last node.
4. Suppose the list has only one node, the node is deleted by setting the head pointer to NULL.
5. If the list has more than one node and the first node is to be deleted, the head is set to the next node and the previous is linked to the new head.
6. If the node to be deleted is the last node, link the preceding node of the last node to head node.
7. If the node is neither first nor last, remove the node by linking its preceding node to its succeeding node.
8. END
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Stack Data Structure
栈是一种抽象数据类型 (ADT),在大多数编程语言中都很流行。它之所以被称为栈,是因为它具有与现实世界栈类似的操作,例如一叠卡片或一堆盘子等。
A stack is an Abstract Data Type (ADT), that is popularly used in most programming languages. It is named stack because it has the similar operations as the real-world stacks, for example – a pack of cards or a pile of plates, etc.
栈遵循后进先出的 (LIFO) 结构,其中插入的最后一个元素将成为第一个被删除的元素。
The stack follows the LIFO (Last in - First out) structure where the last element inserted would be the first element deleted.
Stack Representation
栈 ADT 只允许在一端进行所有数据操作。在任何给定时间,我们只能访问栈的顶部元素。
A Stack ADT allows all data operations at one end only. At any given time, we can only access the top element of a stack.
下图描绘了一个栈及其操作:
The following diagram depicts a stack and its operations −
可以通过数组、结构、指针和链表来实现栈。栈可以是固定大小的,也可以具有动态调整大小的能力。这里,我们要使用数组来实现栈,这使其成为固定大小的栈实现。
A stack can be implemented by means of Array, Structure, Pointer, and Linked List. Stack can either be a fixed size one or it may have a sense of dynamic resizing. Here, we are going to implement stack using arrays, which makes it a fixed size stack implementation.
Basic Operations on Stacks
栈操作通常用于执行栈 ADT 的初始化、使用和去初始化。
Stack operations usually are performed for initialization, usage and, de-initialization of the stack ADT.
栈 ADT 中最基本的运算包括:push()、pop()、peek()、isFull()、isEmpty()。这些都是用于执行数据操作和检查栈状态的内置运算。
The most fundamental operations in the stack ADT include: push(), pop(), peek(), isFull(), isEmpty(). These are all built-in operations to carry out data manipulation and to check the status of the stack.
栈使用始终指向栈内最顶层元素的指针,因此称为 top 指针。
Stack uses pointers that always point to the topmost element within the stack, hence called as the top pointer.
Insertion: push()
push() 是一种将元素插入栈中的运算。以下是使用更简单的方式描述 push() 运算的算法。
push() is an operation that inserts elements into the stack. The following is an algorithm that describes the push() operation in a simpler way.
Algorithm
1 − Checks if the stack is full.
2 − If the stack is full, produces an error and exit.
3 − If the stack is not full, increments top to point next empty space.
4 − Adds data element to the stack location, where top is pointing.
5 − Returns success.
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Note − 在 Java 中,我们已经使用内置方法 push() 来执行此操作。
Note − In Java we have used to built-in method push() to perform this operation.
Deletion: pop()
pop() 是一种从栈中删除元素的数据操作运算。以下伪代码使用更简单的方式描述了 pop() 运算。
pop() is a data manipulation operation which removes elements from the stack. The following pseudo code describes the pop() operation in a simpler way.
Algorithm
1 − Checks if the stack is empty.
2 − If the stack is empty, produces an error and exit.
3 − If the stack is not empty, accesses the data element at which top is pointing.
4 − Decreases the value of top by 1.
5 − Returns success.
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Note − 在 Java 中,我们使用内置方法 pop()。
Note − In Java we are using the built-in method pop().
peek()
peek() 是一种在不删除最顶层元素的情况下检索栈内最顶层元素的运算。此操作用于借助顶指针检查栈的状态。
The peek() is an operation retrieves the topmost element within the stack, without deleting it. This operation is used to check the status of the stack with the help of the top pointer.
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
isFull()
isFull() 运算检查栈是否已满。此操作用于借助顶指针检查栈的状态。
isFull() operation checks whether the stack is full. This operation is used to check the status of the stack with the help of top pointer.
Algorithm
1. START
2. If the size of the stack is equal to the top position of the stack, the stack is full. Return 1.
3. Otherwise, return 0.
4. END
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
isEmpty()
isEmpty() 操作验证堆栈是否为空。此操作用于在顶部指针的帮助下检查堆栈的状态。
The isEmpty() operation verifies whether the stack is empty. This operation is used to check the status of the stack with the help of top pointer.
Expression Parsing
编写算术表达式的方法称为 notation 。算术表达式可以用三个不同但等效的符号编写,即不改变表达式的本质或输出。这些符号是 −
The way to write arithmetic expression is known as a notation. An arithmetic expression can be written in three different but equivalent notations, i.e., without changing the essence or output of an expression. These notations are −
-
Infix Notation
-
Prefix (Polish) Notation
-
Postfix (Reverse-Polish) Notation
这些符号以它们在表达式中如何使用运算符命名。我们将在本章中学习同样的内容。
These notations are named as how they use operator in expression. We shall learn the same here in this chapter.
Infix Notation
我们在 infix 符号中编写表达式,例如 a - b + c,其中运算符用于在操作数之间 in 。对于我们人类来说,阅读、编写和用中缀表示法说话很容易,但对于计算设备来说却并非如此。处理中缀表示法的算法在时间和空间消耗方面可能既困难又昂贵。
We write expression in infix notation, e.g. a - b + c, where operators are used in-between operands. It is easy for us humans to read, write, and speak in infix notation but the same does not go well with computing devices. An algorithm to process infix notation could be difficult and costly in terms of time and space consumption.
Prefix Notation
在此表示法中,运算符是 prefix*ed to operands, i.e. operator is written ahead of operands. For example, *+ab 。这等同于它的中缀表示法 a + b 。前缀表示法也称为 Polish Notation 。
In this notation, operator is prefix*ed to operands, i.e. operator is written ahead of operands. For example, *+ab. This is equivalent to its infix notation a + b. Prefix notation is also known as Polish Notation.
Postfix Notation
这种符号样式称为 Reversed Polish Notation 。在这种符号样式中,运算符是 postfix*ed to the operands i.e., the operator is written after the operands. For example, *ab+ 。这等同于它的中缀表示法 a + b 。
This notation style is known as Reversed Polish Notation. In this notation style, the operator is postfix*ed to the operands i.e., the operator is written after the operands. For example, *ab+. This is equivalent to its infix notation a + b.
下表简要尝试显示所有三种符号之间的差异 −
The following table briefly tries to show the difference in all three notations −
Parsing Expressions
正如我们所讨论的,设计一个算法或程序来解析中缀符号并不是一种非常有效的方法。相反,这些中缀表示法首先转换为后缀或前缀表示法,然后计算。
As we have discussed, it is not a very efficient way to design an algorithm or program to parse infix notations. Instead, these infix notations are first converted into either postfix or prefix notations and then computed.
为了解析任何算术表达式,我们还需要注意运算符的优先级和结合性。
To parse any arithmetic expression, we need to take care of operator precedence and associativity also.
Precedence
当一个操作数位于两个不同的运算符之间时,首先取得操作数的运算符是由一个运算符对另一个运算符的优先级决定的。例如 −
When an operand is in between two different operators, which operator will take the operand first, is decided by the precedence of an operator over others. For example −
由于乘法运算优先级高于加法,因此 b * c 将首先得到评估。运算符优先级表将在后面提供。
As multiplication operation has precedence over addition, b * c will be evaluated first. A table of operator precedence is provided later.
Associativity
关联性描述了具有相同优先级的运算符在表达式中出现时的规则。例如,在表达式 a + b- c 中,+ 和 - 都具有相同的优先级,那么表达式的哪一部分将首先被求值,将由这些运算符的关联性决定。这里,+ 和 - 都是左关联的,因此表达式将求值为 (a + b) − c 。
Associativity describes the rule where operators with the same precedence appear in an expression. For example, in expression a + b − c, both + and – have the same precedence, then which part of the expression will be evaluated first, is determined by associativity of those operators. Here, both + and − are left associative, so the expression will be evaluated as (a + b) − c.
优先级和结合性决定了表达式求值顺序。以下是运算符优先级和结合性表(从最高到最低)-
Precedence and associativity determines the order of evaluation of an expression. Following is an operator precedence and associativity table (highest to lowest) −
上表显示了运算符的默认行为。在表达式求值的任何时间点,都可以通过使用括号来更改顺序。例如 −
The above table shows the default behavior of operators. At any point of time in expression evaluation, the order can be altered by using parenthesis. For example −
在 a + b*c 中,表达式部分 b * c 将首先得到评估,其中乘法的优先级高于加法。我们在这里使用括号来使 a + b 首先得到评估,例如 (a + b)*c 。
In a + b*c, the expression part b*c will be evaluated first, with multiplication as precedence over addition. We here use parenthesis for a + b to be evaluated first, like (a + b)*c.
Postfix Evaluation Algorithm
现在,我们将研究如何评估后缀表示法的算法 −
We shall now look at the algorithm on how to evaluate postfix notation −
Step 1 − scan the expression from left to right
Step 2 − if it is an operand push it to stack
Step 3 − if it is an operator pull operand from stack and perform operation
Step 4 − store the output of step 3, back to stack
Step 5 − scan the expression until all operands are consumed
Step 6 − pop the stack and perform operation
Queue Data Structure
队列与栈类似,也是一种抽象数据结构。使队列与栈不同的一件事是队列两端都是开放的。因此,它遵循先进先出 (FIFO) 结构,即首先插入的数据项也将首先被访问。数据通过一端插入队列,并从另一端删除 。
Queue, like Stack, is also an abstract data structure. The thing that makes queue different from stack is that a queue is open at both its ends. Hence, it follows FIFO (First-In-First-Out) structure, i.e. the data item inserted first will also be accessed first. The data is inserted into the queue through one end and deleted from it using the other end.
队列的实际示例可以是单车道单行道,其中的车辆先进入,先退出。更多实际示例可以看作是售票窗口和公共汽车站的队列。
A real-world example of queue can be a single-lane one-way road, where the vehicle enters first, exits first. More real-world examples can be seen as queues at the ticket windows and bus-stops.
Representation of Queues
类似于堆栈 ADT,队列 ADT 也可以使用数组、链表或指针实现。作为本教程中的一个小示例,我们使用一维数组实现队列。
Similar to the stack ADT, a queue ADT can also be implemented using arrays, linked lists, or pointers. As a small example in this tutorial, we implement queues using a one-dimensional array.
Basic Operations
队列操作还包括队列的初始化、使用和永久从内存中删除数据。
Queue operations also include initialization of a queue, usage and permanently deleting the data from the memory.
队列 ADT 中最基本的操作包括:enqueue()、dequeue()、peek()、isFull()、isEmpty()。这些都是内置操作,用于执行数据操作和检查队列的状态。
The most fundamental operations in the queue ADT include: enqueue(), dequeue(), peek(), isFull(), isEmpty(). These are all built-in operations to carry out data manipulation and to check the status of the queue.
队列使用两个指针: front 和 rear 。前指针从前端访问数据(帮助入队),而后指针从后端访问数据(帮助出队)。
Queue uses two pointers − front and rear. The front pointer accesses the data from the front end (helping in enqueueing) while the rear pointer accesses data from the rear end (helping in dequeuing).
Insertion operation: enqueue()
enqueue() 是一种数据操作操作,用于将元素插入堆栈。以下算法以更简单的方式描述了 enqueue() 操作。
The enqueue() is a data manipulation operation that is used to insert elements into the stack. The following algorithm describes the enqueue() operation in a simpler way.
Algorithm
1 − START
2 – Check if the queue is full.
3 − If the queue is full, produce overflow error and exit.
4 − If the queue is not full, increment rear pointer to point the next empty space.
5 − Add data element to the queue location, where the rear is pointing.
6 − return success.
7 – END
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Deletion Operation: dequeue()
dequeue() 是一种数据处理操作,用于从堆栈中移除元素。以下算法以一种更简单的方式描述了 dequeue() 操作。
The dequeue() is a data manipulation operation that is used to remove elements from the stack. The following algorithm describes the dequeue() operation in a simpler way.
Algorithm
1 – START
2 − Check if the queue is empty.
3 − If the queue is empty, produce underflow error and exit.
4 − If the queue is not empty, access the data where front is pointing.
5 − Increment front pointer to point to the next available data element.
6 − Return success.
7 – END
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
The peek() Operation
peek() 是一种操作,用于检索队列中最前面的元素,而不会删除它。此操作用于在指针的帮助下检查队列的状态。
The peek() is an operation which is used to retrieve the frontmost element in the queue, without deleting it. This operation is used to check the status of the queue with the help of the pointer.
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
The isFull() Operation
isFull() 操作验证堆栈是否已满。
The isFull() operation verifies whether the stack is full.
Algorithm
1 – START
2 – If the count of queue elements equals the queue size, return true
3 – Otherwise, return false
4 – END
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
The isEmpty() operation
isEmpty() 操作验证堆栈是否为空。此操作用于在顶部指针的帮助下检查堆栈的状态。
The isEmpty() operation verifies whether the stack is empty. This operation is used to check the status of the stack with the help of top pointer.
Graph Data Structure
图是一种抽象数据类型 (ADT),它由一组通过链接相互连接的对象组成。这些对象称为 vertices ,而链接称为 edges 。
A graph is an abstract data type (ADT) that consists of a set of objects that are connected to each other via links. These objects are called vertices and the links are called edges.
通常,一个图表示为 G = {V,E},其中 G 是图空间,V 是顶点集合,E 是边集合。如果 E 为空,则该图被称为 forest 。
Usually, a graph is represented as G = {V, E}, where G is the graph space, V is the set of vertices and E is the set of edges. If E is empty, the graph is known as a forest.
在我们继续之前,让我们熟悉一些重要的术语 −
Before we proceed further, let’s familiarize ourselves with some important terms −
-
Vertex − Each node of the graph is represented as a vertex. In the following example, the labelled circle represents vertices. Thus, A to G are vertices. We can represent them using an array as shown in the following image. Here A can be identified by index 0. B can be identified using index 1 and so on.
-
Edge − Edge represents a path between two vertices or a line between two vertices. In the following example, the lines from A to B, B to C, and so on represents edges. We can use a two-dimensional array to represent an array as shown in the following image. Here AB can be represented as 1 at row 0, column 1, BC as 1 at row 1, column 2 and so on, keeping other combinations as 0.
-
Adjacency − Two node or vertices are adjacent if they are connected to each other through an edge. In the following example, B is adjacent to A, C is adjacent to B, and so on.
-
Path − Path represents a sequence of edges between the two vertices. In the following example, ABCD represents a path from A to D.
Operations of Graphs
图的主要操作包括使用顶点和边创建图,并显示所述图。但是,使用图执行的最常见和最流行的操作之一是遍历,即按特定顺序访问图的每个顶点。
The primary operations of a graph include creating a graph with vertices and edges, and displaying the said graph. However, one of the most common and popular operation performed using graphs are Traversal, i.e. visiting every vertex of the graph in a specific order.
图中有两种类型的遍历 −
There are two types of traversals in Graphs −
-
Depth First Search Traversal
-
Breadth First Search Traversal
Depth First Search Traversal
深度优先搜索是一种遍历算法,它以递减的深度顺序访问图的所有顶点。在此算法中,选择一个任意节点作为起点,并通过标记未访问的相邻节点来遍历该图,直到标记所有顶点为止。
Depth First Search is a traversal algorithm that visits all the vertices of a graph in the decreasing order of its depth. In this algorithm, an arbitrary node is chosen as the starting point and the graph is traversed back and forth by marking unvisited adjacent nodes until all the vertices are marked.
DFS 遍历使用堆栈数据结构来跟踪未访问的节点。
The DFS traversal uses the stack data structure to keep track of the unvisited nodes.
Breadth First Search Traversal
广度优先搜索是一种遍历算法,它在移动到下一个深度级别之前,先访问深度一个级别的图的所有顶点。在此算法中,选择一个任意节点作为起点,并通过访问同一深度级别的相邻顶点并标记它们来遍历图,直到没有剩余顶点为止。
Breadth First Search is a traversal algorithm that visits all the vertices of a graph present at one level of the depth before moving to the next level of depth. In this algorithm, an arbitrary node is chosen as the starting point and the graph is traversed by visiting the adjacent vertices on the same depth level and marking them until there is no vertex left.
DFS 遍历使用队列数据结构来跟踪未访问的节点。
The DFS traversal uses the queue data structure to keep track of the unvisited nodes.
Representation of Graphs
在表示图表时,我们必须仔细描绘图表中存在的元素(顶点和边)以及它们之间的关系。在图片上,用一组有限的节点和它们之间的连接链接来表示一个图表。但是,我们也可以用其他最常用的方式来表示图表,例如 −
While representing graphs, we must carefully depict the elements (vertices and edges) present in the graph and the relationship between them. Pictorially, a graph is represented with a finite set of nodes and connecting links between them. However, we can also represent the graph in other most commonly used ways, like −
-
Adjacency Matrix
-
Adjacency List
Adjacency Matrix
邻接矩阵是一个 V×V 矩阵,其中值填充为 0 或 1。如果 Vi 和 Vj 之间存在链接,则记录为 1;否则记录为 0。
The Adjacency Matrix is a V×V matrix where the values are filled with either 0 or 1. If the link exists between Vi and Vj, it is recorded 1; otherwise, 0.
对于下面给出的图表,让我们构建一个邻接矩阵 −
For the given graph below, let us construct an adjacency matrix −
邻接矩阵为 −
The adjacency matrix is −
Types of graph
有两种基本类型的图——
There are two basic types of graph −
-
Directed Graph
-
Undirected Graph
有向图,顾名思义,由具有从顶点指向外部或指向顶点的方向的边线组成。无向图有完全无方向的边线。
Directed graph, as the name suggests, consists of edges that possess a direction that goes either away from a vertex or towards the vertex. Undirected graphs have edges that are not directed at all.
Directed Graph
Directed Graph
Undirected Graph
Undirected Graph
Spanning Tree
spanning tree 是无向图的一个子集,它包含图中所有用图中最少数量的边线连接的顶点。精确地说,生成树的边线是原始图中的边线的一个子集。
A spanning tree is a subset of an undirected graph that contains all the vertices of the graph connected with the minimum number of edges in the graph. Precisely, the edges of the spanning tree is a subset of the edges in the original graph.
如果图中的所有顶点是连接的,那么至少存在一棵生成树。在一个图中,可能存在多棵生成树。
If all the vertices are connected in a graph, then there exists at least one spanning tree. In a graph, there may exist more than one spanning tree.
Minimum Spanning Tree
Minimum Spanning Tree (MST) 是连通的加权无向图的边线的子集,它用最小的总边线权重将所有顶点连接在一起。为了推导 MST,可以使用 Prim 算法或 Kruskal 算法。因此,我们将在本章讨论 Prim 算法。
A Minimum Spanning Tree (MST) is a subset of edges of a connected weighted undirected graph that connects all the vertices together with the minimum possible total edge weight. To derive an MST, Prim’s algorithm or Kruskal’s algorithm can be used. Hence, we will discuss Prim’s algorithm in this chapter.
正如我们所讨论的,一个图可能有多棵生成树。如果有 n 个顶点,生成树应有 n−1 个边线。在此上下文中,如果图的每个边线都与一个权重相关联,并且存在多棵生成树,我们需要找到图的最小生成树。
As we have discussed, one graph may have more than one spanning tree. If there are n number of vertices, the spanning tree should have 𝒏−𝟏 number of edges. In this context, if each edge of the graph is associated with a weight and there exists more than one spanning tree, we need to find the minimum spanning tree of the graph.
此外,如果存在任何重复的加权边线,图可能有多个最小生成树。
Moreover, if there exist any duplicate weighted edges, the graph may have multiple minimum spanning tree.
在上述图中,我们展示了一棵生成树,尽管它不是最小生成树。这棵生成树的成本是 (5+7+3+3+5+8+3+4)=38 。
In the above graph, we have shown a spanning tree though it’s not the minimum spanning tree. The cost of this spanning tree is (5+7+3+3+5+8+3+4)=38.
Shortest Path
图中最短的路径被定义为从一个顶点到另一个顶点的最小成本路线。这在加权有向图中最常见,但也可以应用于无向图。
The shortest path in a graph is defined as the minimum cost route from one vertex to another. This is most commonly seen in weighted directed graphs but are also applicable to undirected graphs.
查找图中最短路径的一个流行实际应用是地图。在各种最短路径算法的帮助下,导航变得更加容易和简单,这些算法将目的地视为图的顶点,将路线视为边线。两种常见的最短路径算法是——
A popular real-world application of finding the shortest path in a graph is a map. Navigation is made easier and simpler with the various shortest path algorithms where destinations are considered vertices of the graph and routes are the edges. The two common shortest path algorithms are −
-
Dijkstra’s Shortest Path Algorithm
-
Bellman Ford’s Shortest Path Algorithm
Depth First Traversal
深度优先搜索 (DFS) 算法以深度方向遍历图,并使用栈来记住以在任何迭代中发生死锁时获取要开始搜索的下一个顶点。
Depth First Search (DFS) algorithm traverses a graph in a depthward motion and uses a stack to remember to get the next vertex to start a search, when a dead end occurs in any iteration.
正如上面给出的示例中,DFS 算法首先从 S 到 A 到 D 到 G 到 E 到 B,然后到 F,最后到 C。它采用以下规则。
As in the example given above, DFS algorithm traverses from S to A to D to G to E to B first, then to F and lastly to C. It employs the following rules.
-
Rule 1 − Visit the adjacent unvisited vertex. Mark it as visited. Display it. Push it in a stack.
-
Rule 2 − If no adjacent vertex is found, pop up a vertex from the stack. (It will pop up all the vertices from the stack, which do not have adjacent vertices.)
-
Rule 3 − Repeat Rule 1 and Rule 2 until the stack is empty.
由于 C 没有任何未访问的邻接节点,因此我们将继续弹出堆栈,直到找到具有未访问邻接节点的节点。在这种情况下,没有,并且我们一直弹出,直到堆栈为空。
As C does not have any unvisited adjacent node so we keep popping the stack until we find a node that has an unvisited adjacent node. In this case, there’s none and we keep popping until the stack is empty.
Breadth First Traversal
广度优先搜索 (BFS) 算法以广度方向遍历图,并使用队列来记住以在任何迭代中发生死锁时获取要开始搜索的下一个顶点。
Breadth First Search (BFS) algorithm traverses a graph in a breadthward motion and uses a queue to remember to get the next vertex to start a search, when a dead end occurs in any iteration.
正如以上给出的示例,BFS 算法首先从 A 到 B 到 E 到 F,然后到 C 和 G,最后到 D。它采用以下规则。
As in the example given above, BFS algorithm traverses from A to B to E to F first then to C and G lastly to D. It employs the following rules.
-
Rule 1 − Visit the adjacent unvisited vertex. Mark it as visited. Display it. Insert it in a queue.
-
Rule 2 − If no adjacent vertex is found, remove the first vertex from the queue.
-
Rule 3 − Repeat Rule 1 and Rule 2 until the queue is empty.
在此阶段,我们只剩下未标记(未访问)的节点。但根据算法,我们会不断出列以获取所有未访问的节点。当队列清空后,程序结束。
At this stage, we are left with no unmarked (unvisited) nodes. But as per the algorithm we keep on dequeuing in order to get all unvisited nodes. When the queue gets emptied, the program is over.
Spanning Tree
生成树是图 G 的一个子集,它以尽可能少数量的边覆盖所有顶点。因此,生成树没有回路并且不能断开连接。
A spanning tree is a subset of Graph G, which has all the vertices covered with minimum possible number of edges. Hence, a spanning tree does not have cycles and it cannot be disconnected..
根据这个定义,我们可以得出结论:每个相连且无向的图 G 至少有一个生成树。断开连接的图没有任何生成树,因为它无法跨越到其所有顶点。
By this definition, we can draw a conclusion that every connected and undirected Graph G has at least one spanning tree. A disconnected graph does not have any spanning tree, as it cannot be spanned to all its vertices.
我们从一个完全图中找到了三个生成树。一个完整的无向图可以有 nn-2 个生成树,其中 n 是节点数。在上述示例中, n is 3, 因此 33−2 = 3 生成树是可能的。
We found three spanning trees off one complete graph. A complete undirected graph can have maximum nn-2 number of spanning trees, where n is the number of nodes. In the above addressed example, n is 3, hence 33−2 = 3 spanning trees are possible.
General Properties of Spanning Tree
我们现在明白一个图可以有多个生成树。以下是与图 G 相连的生成树的一些性质 −
We now understand that one graph can have more than one spanning tree. Following are a few properties of the spanning tree connected to graph G −
-
A connected graph G can have more than one spanning tree.
-
All possible spanning trees of graph G, have the same number of edges and vertices.
-
The spanning tree does not have any cycle (loops).
-
Removing one edge from the spanning tree will make the graph disconnected, i.e. the spanning tree is minimally connected.
-
Adding one edge to the spanning tree will create a circuit or loop, i.e. the spanning tree is maximally acyclic.
Mathematical Properties of Spanning Tree
-
Spanning tree has n-1 edges, where n is the number of nodes (vertices).
-
From a complete graph, by removing maximum e - n + 1 edges, we can construct a spanning tree.
-
A complete graph can have maximum nn-2 number of spanning trees.
因此,我们可以得出结论:生成树是连通图 G 的一个子集,断开连接的图没有生成树。
Thus, we can conclude that spanning trees are a subset of connected Graph G and disconnected graphs do not have spanning tree.
Application of Spanning Tree
生成树本质上用于查找一条最短路径以连接图中的所有节点。生成树的常见应用包括 −
Spanning tree is basically used to find a minimum path to connect all nodes in a graph. Common application of spanning trees are −
-
Civil Network Planning
-
Computer Network Routing Protocol
-
Cluster Analysis
让我们通过一个小例子来理解一下。考虑城市网络是一个巨大的图,现在计划以一种方式布设电话线路,即,我们可以用最少的线路连接到所有城市节点。这就是生成树派上用场的地方。
Let us understand this through a small example. Consider, city network as a huge graph and now plans to deploy telephone lines in such a way that in minimum lines we can connect to all city nodes. This is where the spanning tree comes into picture.
Minimum Spanning Tree (MST)
在一个带权重的图中,极小生成树是生成树,其权重比相同图形的所有其他生成树的权重都小。在现实世界中,此权重可以衡量为距离、拥堵、交通负荷或赋予边的任何任意值。
In a weighted graph, a minimum spanning tree is a spanning tree that has minimum weight than all other spanning trees of the same graph. In real-world situations, this weight can be measured as distance, congestion, traffic load or any arbitrary value denoted to the edges.
Tree Data Structure
树是一个具有基于层次结构的非线性抽象数据类型。它包括通过链接连接的存储数据的节点。树数据结构源自一个称为根节点的单个节点,并具有连接到根节点的子树。
A tree is a non-linear abstract data type with a hierarchy-based structure. It consists of nodes (where the data is stored) that are connected via links. The tree data structure stems from a single node called a root node and has subtrees connected to the root.
Important Terms
以下是关于树的一些重要术语。
Following are the important terms with respect to tree.
-
Path − Path refers to the sequence of nodes along the edges of a tree.
-
Root − The node at the top of the tree is called root. There is only one root per tree and one path from the root node to any node.
-
Parent − Any node except the root node has one edge upward to a node called parent.
-
Child − The node below a given node connected by its edge downward is called its child node.
-
Leaf − The node which does not have any child node is called the leaf node.
-
Subtree − Subtree represents the descendants of a node.
-
Visiting − Visiting refers to checking the value of a node when control is on the node.
-
Traversing − Traversing means passing through nodes in a specific order.
-
Levels − Level of a node represents the generation of a node. If the root node is at level 0, then its next child node is at level 1, its grandchild is at level 2, and so on.
-
Keys − Key represents a value of a node based on which a search operation is to be carried out for a node.
Types of Trees
有三种类型的树:
There are three types of trees −
-
General Trees
-
Binary Trees
-
Binary Search Trees
General Trees
通用树是无序树数据结构,其中根节点具有最小 0 或最大“n”个子树。
General trees are unordered tree data structures where the root node has minimum 0 or maximum ‘n’ subtrees.
通用树对其层次结构没有限制。因此,根节点充当所有其他子树的超集。
The General trees have no constraint placed on their hierarchy. The root node thus acts like the superset of all the other subtrees.
Binary Trees
二叉树是在其中根节点最多只能容纳 2 个子树(左子树和右子树)的通用树。根据子节点的数量,二叉树分为三类。
Binary Trees are general trees in which the root node can only hold up to maximum 2 subtrees: left subtree and right subtree. Based on the number of children, binary trees are divided into three types.
Full Binary Tree
Full Binary Tree
-
A full binary tree is a binary tree type where every node has either 0 or 2 child nodes.
Complete Binary Tree
Complete Binary Tree
-
A complete binary tree is a binary tree type where all the leaf nodes must be on the same level. However, root and internal nodes in a complete binary tree can either have 0, 1 or 2 child nodes.
Perfect Binary Tree
Perfect Binary Tree
-
A perfect binary tree is a binary tree type where all the leaf nodes are on the same level and every node except leaf nodes have 2 children.
Binary Search Trees
二叉查找树具有二叉树的所有属性,还包括基于一些约束的额外属性,使其比二叉树更高效。
Binary Search Trees possess all the properties of Binary Trees including some extra properties of their own, based on some constraints, making them more efficient than binary trees.
二叉查找树 (BST) 中的数据总是以这样的方式存储:左子树中的值始终小于根节点中的值,右子树中的值始终大于根节点中的值,即左子树 < 根节点 ≤ 右子树。
The data in the Binary Search Trees (BST) is always stored in such a way that the values in the left subtree are always less than the values in the root node and the values in the right subtree are always greater than the values in the root node, i.e. left subtree < root node ≤ right subtree.
Advantages of BST
Advantages of BST
-
Binary Search Trees are more efficient than Binary Trees since time complexity for performing various operations reduces.
-
Since the order of keys is based on just the parent node, searching operation becomes simpler.
-
The alignment of BST also favors Range Queries, which are executed to find values existing between two keys. This helps in the Database Management System.
Disadvantages of BST
Disadvantages of BST
二叉查找树的主要缺点是,如果节点中的所有元素都大于或小于根节点,* 则树会变得不平衡 *。简而言之,树会完全向一侧倾斜。
The main disadvantage of Binary Search Trees is that if all elements in nodes are either greater than or lesser than the root node,* the tree becomes skewed*. Simply put, the tree becomes slanted to one side completely.
此 skewness 会使树成为链表而不是 BST,因为搜索操作的最坏情况时间复杂度变为 O(n)。
This skewness will make the tree a linked list rather than a BST, since the worst case time complexity for searching operation becomes O(n).
为了克服二叉查找树中的这种不平衡问题,引入了 Balanced Binary Search Trees 的概念。
To overcome this issue of skewness in the Binary Search Trees, the concept of Balanced Binary Search Trees was introduced.
Balanced Binary Search Trees
考虑一棵二叉查找树,左子树的高度为“m”,右子树的高度为“n”。如果 (m-n) 的值等于 0、1 或 -1,则该树被称为 Balanced Binary Search Tree 。
Consider a Binary Search Tree with ‘m’ as the height of the left subtree and ‘n’ as the height of the right subtree. If the value of (m-n) is equal to 0,1 or -1, the tree is said to be a Balanced Binary Search Tree.
这些树的设计方式是,一旦高度差超过 1,它们就会进行自我平衡。二叉查找树使用旋转作为自我平衡算法。有四种不同类型的旋转:左左旋转、右右旋转、左右旋转和右左旋转。
The trees are designed in a way that they self-balance once the height difference exceeds 1. Binary Search Trees use rotations as self-balancing algorithms. There are four different types of rotations: Left Left, Right Right, Left Right, Right Left.
有各种类型的自平衡二叉查找树 -
There are various types of self-balancing binary search trees −
-
AVL Trees
-
Red Black Trees
-
B Trees
-
B+ Trees
-
Splay Trees
-
Priority Search Trees
Tree Traversal
遍历是一个访问树的所有节点并可能打印其值的过程。因为,所有节点都通过边(链接)连接,所以我们总是从根(头)节点开始。也就是说,我们无法随机访问树中的节点。有三种方法可以遍历树 -
Traversal is a process to visit all the nodes of a tree and may print their values too. Because, all nodes are connected via edges (links) we always start from the root (head) node. That is, we cannot randomly access a node in a tree. There are three ways which we use to traverse a tree −
-
In-order Traversal
-
Pre-order Traversal
-
Post-order Traversal
通常,我们遍历一棵树以搜索或定位树中给定的项目或键,或打印它包含的所有值。
Generally, we traverse a tree to search or locate a given item or key in the tree or to print all the values it contains.
In-order Traversal
在这种遍历方法中,首先访问左子树,然后访问根,最后访问右子树。我们应该始终记住,每个节点本身都可能表示一个子树。
In this traversal method, the left subtree is visited first, then the root and later the right sub-tree. We should always remember that every node may represent a subtree itself.
如果遍历一棵二叉树 in-order ,输出将按升序生成排序的键值。
If a binary tree is traversed in-order, the output will produce sorted key values in an ascending order.
从 A 开始,按照中序遍历,我们将移动到它的左子树 B 。 B 也以中序遍历。此过程一直持续到所有节点都已访问。这棵树的中序遍历的输出将是−
We start from A, and following in-order traversal, we move to its left subtree B.B is also traversed in-order. The process goes on until all the nodes are visited. The output of in-order traversal of this tree will be −
D → B → E → A → F → C → G
D → B → E → A → F → C → G
Pre-order Traversal
在这种遍历方法中,首先访问根节点,然后访问左子树,最后访问右子树。
In this traversal method, the root node is visited first, then the left subtree and finally the right subtree.
从 A 开始,并按照先序遍历,我们首先访问 A 本身,然后移动到它的左子树 B 。 B 也以先序遍历。此过程一直持续到所有节点都已访问。这棵树的先序遍历的输出将是−
We start from A, and following pre-order traversal, we first visit A itself and then move to its left subtree B. B is also traversed pre-order. The process goes on until all the nodes are visited. The output of pre-order traversal of this tree will be −
A → B → D → E → C → F → G
A → B → D → E → C → F → G
Post-order Traversal
在这种遍历方法中,根节点最后被访问,因此得名。我们首先遍历左子树,然后遍历右子树,最后遍历根节点。
In this traversal method, the root node is visited last, hence the name. First we traverse the left subtree, then the right subtree and finally the root node.
从 A 开始,并按照后序遍历,我们首先访问左子树 B 。 B 也以后序遍历。此过程一直持续到所有节点都已访问。这棵树的后序遍历的输出将是−
We start from A, and following pre-order traversal, we first visit the left subtree B. B is also traversed post-order. The process goes on until all the nodes are visited. The output of post-order traversal of this tree will be −
D → E → B → F → G → C → A
D → E → B → F → G → C → A
Algorithm
到所有节点都已遍历−
Until all nodes are traversed −
Step 1 − 递归遍历左子树。
Step 1 − Recursively traverse left subtree.
Step 2 − 递归遍历右子树。
Step 2 − Recursively traverse right subtree.
Step 3 − 访问根节点。
Step 3 − Visit root node.
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
要检查树遍历的 C 实现,请 click here
To check the C implementation of tree traversing, please click here
Implementation
遍历是一个访问树的所有节点并可能打印其值的过程。因为,所有节点都通过边(链接)连接,所以我们总是从根(头)节点开始。也就是说,我们无法随机访问树中的节点。有三种方法可以遍历树 -
Traversal is a process to visit all the nodes of a tree and may print their values too. Because, all nodes are connected via edges (links) we always start from the root (head) node. That is, we cannot randomly access a node in a tree. There are three ways which we use to traverse a tree −
-
In-order Traversal
-
Pre-order Traversal
-
Post-order Traversal
我们现在将在这里使用以下二叉树,看到树遍历在 C 编程语言中的实现−
We shall now see the implementation of tree traversal in C programming language here using the following binary tree −
Binary Search Tree
二叉查找树(BST)是其中所有节点都符合以下所述属性的树——
A Binary Search Tree (BST) is a tree in which all the nodes follow the below-mentioned properties −
-
The left sub-tree of a node has a key less than or equal to its parent node’s key.
-
The right sub-tree of a node has a key greater than or equal to its parent node’s key.
因此,BST 将其所有子树划分为两个部分;左子树和右子树,并且可以定义为——
Thus, BST divides all its sub-trees into two segments; the left sub-tree and the right sub-tree and can be defined as −
left_subtree (keys) ≤ node (key) ≤ right_subtree (keys)
Representation
BST 是按照可以维持 BST 属性的方式排列的一组节点。每个节点都有一个键和一个相关值。在搜索时,将所需的键与 BST 中的键比较,如果找到,则检索相关值。
BST is a collection of nodes arranged in a way where they maintain BST properties. Each node has a key and an associated value. While searching, the desired key is compared to the keys in BST and if found, the associated value is retrieved.
以下是 BST 的图像表示——
Following is a pictorial representation of BST −
我们观察到,根节点键(27)在左子树上具有所有值较小的键,在右子树上具有值较大的键。
We observe that the root node key (27) has all less-valued keys on the left sub-tree and the higher valued keys on the right sub-tree.
Basic Operations
以下是树的基本操作——
Following are the basic operations of a tree −
-
Search − Searches an element in a tree.
-
Insert − Inserts an element in a tree.
-
Pre-order Traversal − Traverses a tree in a pre-order manner.
-
In-order Traversal − Traverses a tree in an in-order manner.
-
Post-order Traversal − Traverses a tree in a post-order manner.
Defining a Node
定义一个节点,该节点存储一些数据以及对其左子节点和右子节点的引用。
Define a node that stores some data, and references to its left and right child nodes.
struct node {
int data;
struct node *leftChild;
struct node *rightChild;
};
Search Operation
每当要搜索一个元素时,从根节点开始搜索。然后,如果数据小于键值,则在左子树中搜索元素。否则,在右子树中搜索该元素。对每个节点遵循相同的算法。
Whenever an element is to be searched, start searching from the root node. Then if the data is less than the key value, search for the element in the left subtree. Otherwise, search for the element in the right subtree. Follow the same algorithm for each node.
Algorithm
1. START
2. Check whether the tree is empty or not
3. If the tree is empty, search is not possible
4. Otherwise, first search the root of the tree.
5. If the key does not match with the value in the root, search its subtrees.
6. If the value of the key is less than the root value, search the left subtree
7. If the value of the key is greater than the root value, search the right subtree.
8. If the key is not found in the tree, return unsuccessful search.
9. END
Insert Operation
每当要插入一个元素时,首先找到它的适当位置。从根节点开始搜索,然后,如果数据小于键值,则在左子树中搜索空位置并插入该数据。否则,在右子树中搜索空位置并插入该数据。
Whenever an element is to be inserted, first locate its proper location. Start searching from the root node, then if the data is less than the key value, search for the empty location in the left subtree and insert the data. Otherwise, search for the empty location in the right subtree and insert the data.
Algorithm
1 – START
2 – If the tree is empty, insert the first element as the root node of the tree. The following elements are added as the leaf nodes.
3 – If an element is less than the root value, it is added into the left subtree as a leaf node.
4 – If an element is greater than the root value, it is added into the right subtree as a leaf node.
5 – The final leaf nodes of the tree point to NULL values as their child nodes.
6 – END
Inorder Traversal
二叉查找树中的中序遍历操作按照以下顺序访问其所有节点——
The inorder traversal operation in a Binary Search Tree visits all its nodes in the following order −
-
Firstly, we traverse the left child of the root node/current node, if any.
-
Next, traverse the current node.
-
Lastly, traverse the right child of the current node, if any.
Preorder Traversal
二叉搜索树的先序遍历操作将访问其所有节点。但是,首先打印根节点,然后是其左子树,最后是其右子树。
The preorder traversal operation in a Binary Search Tree visits all its nodes. However, the root node in it is first printed, followed by its left subtree and then its right subtree.
Postorder Traversal
与其他遍历类似,后序遍历也会访问二叉搜索树中的所有节点并显示它们。但是,首先打印左子树,然后是右子树,最后是根节点。
Like the other traversals, postorder traversal also visits all the nodes in a Binary Search Tree and displays them. However, the left subtree is printed first, followed by the right subtree and lastly, the root node.
Algorithm
1. START
2. Traverse the left subtree, recursively
3. Traverse the right subtree, recursively.
4. Then, traverse the root node
5. END
AVL Trees
要发明的第一种自平衡二叉搜索树是 AVL 树。AVL 树的名称是由其发明者的名字命名的 − Adelson-Velsky 和 Landis。
The first type of self-balancing binary search tree to be invented is the AVL tree. The name AVL tree is coined after its inventor’s names − Adelson-Velsky and Landis.
在 AVL 树中,左右子树的高度差,称为 Balance Factor ,最多必须为 1。一旦差值超过 1,该树会自动执行平衡算法,直至差值再次变为 1。
In AVL trees, the difference between the heights of left and right subtrees, known as the Balance Factor, must be at most one. Once the difference exceeds one, the tree automatically executes the balancing algorithm until the difference becomes one again.
BALANCE FACTOR = HEIGHT(LEFT SUBTREE) – HEIGHT(RIGHT SUBTREE)
在 AVL 树的平衡算法中通常有四种旋转情况:LL、RR、LR、RL。
There are usually four cases of rotation in the balancing algorithm of AVL trees: LL, RR, LR, RL.
LL Rotations
当将节点插入导致不平衡树的右子树时,执行 LL 旋转。这是一个单一的左旋转,再次使树平衡 −
LL rotation is performed when the node is inserted into the right subtree leading to an unbalanced tree. This is a single left rotation to make the tree balanced again −
Fig : LL Rotation
Fig : LL Rotation
发生不平衡的节点变为左子节点,新添加的节点变为右子节点,中间节点变为父节点。
The node where the unbalance occurs becomes the left child and the newly added node becomes the right child with the middle node as the parent node.
RR Rotations
当将节点插入导致不平衡树的左子树时,执行 RR 旋转。这是一个单一的右旋转,再次使树平衡 −
RR rotation is performed when the node is inserted into the left subtree leading to an unbalanced tree. This is a single right rotation to make the tree balanced again −
Fig : RR Rotation
Fig : RR Rotation
发生不平衡的节点变为右子节点,新添加的节点变为左子节点,中间节点变为父节点。
The node where the unbalance occurs becomes the right child and the newly added node becomes the left child with the middle node as the parent node.
LR Rotations
LR 旋转是之前单一旋转的扩展版本,也称为双旋转。当将节点插入左子树的右子树中时,执行此操作。LR 旋转将左旋转与右旋转相结合。有几个需要遵循的步骤来执行此操作。
LR rotation is the extended version of the previous single rotations, also called a double rotation. It is performed when a node is inserted into the right subtree of the left subtree. The LR rotation is a combination of the left rotation followed by the right rotation. There are multiple steps to be followed to carry this out.
-
Consider an example with “A” as the root node, “B” as the left child of “A” and “C” as the right child of “B”.
-
Since the unbalance occurs at A, a left rotation is applied on the child nodes of A, i.e. B and C.
-
After the rotation, the C node becomes the left child of A and B becomes the left child of C.
-
The unbalance still persists, therefore a right rotation is applied at the root node A and the left child C.
-
After the final right rotation, C becomes the root node, A becomes the right child and B is the left child.
Fig : LR Rotation
Fig : LR Rotation
RL Rotations
RL 旋转也是先前单旋转的扩展版本,因此它被称为双旋转,并且当将一个节点插入右子树的左子树中时执行该旋转。RL 旋转是右旋转和左旋转的组合。需要遵循多个步骤来执行此操作。
RL rotation is also the extended version of the previous single rotations, hence it is called a double rotation and it is performed if a node is inserted into the left subtree of the right subtree. The RL rotation is a combination of the right rotation followed by the left rotation. There are multiple steps to be followed to carry this out.
-
Consider an example with “A” as the root node, “B” as the right child of “A” and “C” as the left child of “B”.
-
Since the unbalance occurs at A, a right rotation is applied on the child nodes of A, i.e. B and C.
-
After the rotation, the C node becomes the right child of A and B becomes the right child of C.
-
The unbalance still persists, therefore a left rotation is applied at the root node A and the right child C.
-
After the final left rotation, C becomes the root node, A becomes the left child and B is the right child.
Fig : RL Rotation
Fig : RL Rotation
Basic Operations of AVL Trees
对 AVL 树结构执行的基本操作包括对二叉查找树执行的所有操作,因为 AVL 树的核心实际上只是一个保存其所有属性的二叉查找树。因此,对 AVL 树执行的基本操作为 Insertion 和 Deletion 。
The basic operations performed on the AVL Tree structures include all the operations performed on a binary search tree, since the AVL Tree at its core is actually just a binary search tree holding all its properties. Therefore, basic operations performed on an AVL Tree are − Insertion and Deletion.
Insertion
通过遵循插入的二叉查找树属性将数据插入 AVL 树,即左子树必须包含小于根值的值,右子树必须包含所有更大的值。然而,在 AVL 树中,在插入每个元素后,都会检查树的平衡因子;如果它不超过 1,则树保持原样。但是,如果平衡因子超过 1,则应用平衡算法来调整树,使得平衡因子再次小于或等于 1。
The data is inserted into the AVL Tree by following the Binary Search Tree property of insertion, i.e. the left subtree must contain elements less than the root value and right subtree must contain all the greater elements. However, in AVL Trees, after the insertion of each element, the balance factor of the tree is checked; if it does not exceed 1, the tree is left as it is. But if the balance factor exceeds 1, a balancing algorithm is applied to readjust the tree such that balance factor becomes less than or equal to 1 again.
Algorithm
AVL 树的插入操作涉及以下步骤 -
The following steps are involved in performing the insertion operation of an AVL Tree −
Step 1 − 创建一个节点
Step 1 − Create a node
Step 2 − 检查树是否为空
Step 2 − Check if the tree is empty
Step 3 − 如果树为空,则创建的新节点将成为 AVL 树的根节点。
Step 3 − If the tree is empty, the new node created will become the root node of the AVL Tree.
Step 4 − 如果树不为空,则执行二叉查找树插入操作,并检查树中节点的平衡因子。
Step 4 − If the tree is not empty, we perform the Binary Search Tree insertion operation and check the balancing factor of the node in the tree.
Step 5 − 假设平衡因子超过 ±1,我们对所述节点应用合适的旋转,并从第 4 步继续插入。
Step 5 − Suppose the balancing factor exceeds ±1, we apply suitable rotations on the said node and resume the insertion from Step 4.
START
if node == null then:
return new node
if key < node.key then:
node.left = insert (node.left, key)
else if (key > node.key) then:
node.right = insert (node.right, key)
else
return node
node.height = 1 + max (height (node.left), height (node.right))
balance = getBalance (node)
if balance > 1 and key < node.left.key then:
rightRotate
if balance < -1 and key > node.right.key then:
leftRotate
if balance > 1 and key > node.left.key then:
node.left = leftRotate (node.left)
rightRotate
if balance < -1 and key < node.right.key then:
node.right = rightRotate (node.right)
leftRotate (node)
return node
END
Insertion Example
让我们通过构建一个包含 1 到 7 个整数的示例 AVL 树来理解插入操作。
Let us understand the insertion operation by constructing an example AVL tree with 1 to 7 integers.
从第一个元素 1 开始,我们创建一个节点并测量平衡,即 0。
Starting with the first element 1, we create a node and measure the balance, i.e., 0.
由于二叉查找属性和平衡因子都得到满足,我们再向树中插入另一个元素。
Since both the binary search property and the balance factor are satisfied, we insert another element into the tree.
计算两个节点的平衡因子,发现为 -1(左子树的高度为 0,右子树的高度为 1)。由于它没有超过 1,我们向树中添加另一个元素。
The balance factor for the two nodes are calculated and is found to be -1 (Height of left subtree is 0 and height of the right subtree is 1). Since it does not exceed 1, we add another element to the tree.
现在,添加第三个元素后,平衡因子超过 1 并变成 2。因此,应用旋转。由于不平衡发生在两个右侧节点上,所以在这种情况下应用 RR 旋转。
Now, after adding the third element, the balance factor exceeds 1 and becomes 2. Therefore, rotations are applied. In this case, the RR rotation is applied since the imbalance occurs at two right nodes.
树被重新排列为 −
The tree is rearranged as −
类似地,使用这些旋转插入并重新排列下一个元素。重新排列后,我们实现树为 −
Similarly, the next elements are inserted and rearranged using these rotations. After rearrangement, we achieve the tree as −
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Deletion
AVL 树中的删除发生在三种不同的情况下 −
Deletion in the AVL Trees take place in three different scenarios −
-
Scenario 1 (Deletion of a leaf node) − If the node to be deleted is a leaf node, then it is deleted without any replacement as it does not disturb the binary search tree property. However, the balance factor may get disturbed, so rotations are applied to restore it.
-
Scenario 2 (Deletion of a node with one child) − If the node to be deleted has one child, replace the value in that node with the value in its child node. Then delete the child node. If the balance factor is disturbed, rotations are applied.
-
Scenario 3 (Deletion of a node with two child nodes) − If the node to be deleted has two child nodes, find the inorder successor of that node and replace its value with the inorder successor value. Then try to delete the inorder successor node. If the balance factor exceeds 1 after deletion, apply balance algorithms.
START
if root == null: return root
if key < root.key:
root.left = delete Node
else if key > root.key:
root.right = delete Node
else:
if root.left == null or root.right == null then:
Node temp = null
if (temp == root.left)
temp = root.right
else
temp = root.left
if temp == null then:
temp = root
root = null
else
root = temp
else:
temp = minimum valued node
root.key = temp.key
root.right = delete Node
if (root == null) then:
return root
root.height = max (height (root.left), height (root.right)) + 1
balance = getBalance
if balance > 1 and getBalance (root.left) >= 0:
rightRotate
if balance > 1 and getBalance (root.left) < 0:
root.left = leftRotate (root.left);
rightRotate
if balance < -1 and getBalance (root.right) <= 0:
leftRotate
if balance < -1 and getBalance (root.right) > 0:
root.right = rightRotate (root.right);
leftRotate
return root
END
Deletion Example
使用上面给出的同一棵树,让我们在三种情况下执行删除 −
Using the same tree given above, let us perform deletion in three scenarios −
-
Deleting element 7 from the tree above −
由于元素 7 是一个叶节点,我们通常在不干扰树中的任何其他节点的情况下删除该元素
Since the element 7 is a leaf, we normally remove the element without disturbing any other node in the tree
-
Deleting element 6 from the output tree achieved −
然而,元素 6 不是一个叶节点,并且有一个子节点与之相连。在这种情况下,我们用其子节点替换节点 6:节点 5。
However, element 6 is not a leaf node and has one child node attached to it. In this case, we replace node 6 with its child node: node 5.
树的平衡变为 1,并且因为它没有超过 1,所以树保持原样。如果我们进一步删除元素 5,我们不得不应用左旋转;由于不平衡发生在 1-2-4 和 3-2-4,所以可能是 LL 或 LR。
The balance of the tree becomes 1, and since it does not exceed 1 the tree is left as it is. If we delete the element 5 further, we would have to apply the left rotations; either LL or LR since the imbalance occurs at both 1-2-4 and 3-2-4.
删除元素 5 后平衡因子受到干扰,因此我们应用 LL 旋转(这里我们也可以应用 LR 旋转)。
The balance factor is disturbed after deleting the element 5, therefore we apply LL rotation (we can also apply the LR rotation here).
一旦在路径 1-2-4 上应用 LL 旋转,节点 3 保持为节点 2 的右子节点(现在由节点 4 占据)的样子。因此,该节点被添加到节点 2 的右子树中,并作为节点 4 的左子节点。
Once the LL rotation is applied on path 1-2-4, the node 3 remains as it was supposed to be the right child of node 2 (which is now occupied by node 4). Hence, the node is added to the right subtree of the node 2 and as the left child of the node 4.
-
Deleting element 2 from the remaining tree −
如场景 3 中所提到的那样,该节点有两个子节点。因此,我们找到它是一个叶节点的中序后继(例如,3)并用中序后继替换其值。
As mentioned in scenario 3, this node has two children. Therefore, we find its inorder successor that is a leaf node (say, 3) and replace its value with the inorder successor.
树的平衡仍然保持为 1,因此我们在不进行任何旋转的情况下将树保留原样。
The balance of the tree still remains 1, therefore we leave the tree as it is without performing any rotations.
Red Black Trees
Red-Black 树是另一种类型的平衡二叉查找树,带有两种颜色的节点:红色和黑色。这是一棵自平衡二叉查找树,它利用这些颜色来在插入和删除操作期间维持平衡因子。因此,在 Red-Black 树操作期间,内存会使用 1 位存储来容纳每个节点的颜色信息。
Red-Black Trees are another type of the Balanced Binary Search Trees with two coloured nodes: Red and Black. It is a self-balancing binary search tree that makes use of these colours to maintain the balance factor during the insertion and deletion operations. Hence, during the Red-Black Tree operations, the memory uses 1 bit of storage to accommodate the colour information of each node
在 Red-Black 树(也称为 RB 树)中,在为节点分配颜色时有一些不同的条件需要遵循。
In Red-Black trees, also known as RB trees, there are different conditions to follow while assigning the colours to the nodes.
-
The root node is always black in colour.
-
No two adjacent nodes must be red in colour.
-
Every path in the tree (from the root node to the leaf node) must have the same amount of black coloured nodes.
尽管 AVL 树比 RB 树更平衡(AVL 树中的平衡算法比 RB 树中的严格),但通过 RB 树可以更有效地进行多次且快速的插入和删除操作。
Even though AVL trees are more balanced than RB trees, with the balancing algorithm in AVL trees being stricter than that of RB trees, multiple and faster insertion and deletion operations are made more efficient through RB trees.
Fig: RB trees
Fig: RB trees
Basic Operations of Red-Black Trees
对 Red-Black 树的操作包括通常在二叉查找树上执行的所有基本操作。RB 树的一些基本操作包括:
The operations on Red-Black Trees include all the basic operations usually performed on a Binary Search Tree. Some of the basic operations of an RB Tree include −
-
Insertion
-
Deletion
-
Search
Red-Black 树的插入操作遵循二叉查找树的相同插入算法。该元素按照二叉查找属性插入,另外,对节点进行红黑颜色编码以根据红黑树属性对树进行平衡。
Insertion operation of a Red-Black tree follows the same insertion algorithm of a binary search tree. The elements are inserted following the binary search property and as an addition, the nodes are color coded as red and black to balance the tree according to the red-black tree properties.
按照以下给定的程序将元素插入红黑树,同时维护二叉查找树和红黑树属性。
Follow the procedure given below to insert an element into a red-black tree by maintaining both binary search tree and red black tree properties.
Case 1 - 检查树是否为空;如果树为空,则将当前节点作为根,并将该节点着色为黑色。
Case 1 − Check whether the tree is empty; make the current node as the root and color the node black if it is empty.
Case 2 - 但如果树不为空,则创建一个新节点并将其着色为红色。此处我们面临两种不同的情况:
Case 2 − But if the tree is not empty, we create a new node and color it red. Here we face two different cases −
-
If the parent of the new node is a black colored node, we exit the operation and tree is left as it is.
-
If the parent of this new node is red and the color of the parent’s sibling is either black or if it does not exist, we apply a suitable rotation and recolor accordingly.
-
If the parent of this new node is red and color of the parent’s sibling is red, recolor the parent, the sibling and grandparent nodes to black. The grandparent is recolored only if it is not the root node; if it is the root node recolor only the parent and the sibling.
Insertion Example
让我们构造一棵 RB 树以详细了解插入操作的前 7 个整数:
Let us construct an RB Tree for the first 7 integer numbers to understand the insertion operation in detail −
检查树是否为空,因此添加的第一个节点是根,并将其着色为黑色。
The tree is checked to be empty so the first node added is a root and is colored black.
现在,树不为空,因此我们创建一个新节点,并用红色添加下一个整数,
Now, the tree is not empty so we create a new node and add the next integer with color red,
节点不违反二叉查找树和 RB 树属性,因此我们继续添加另一个节点。
The nodes do not violate the binary search tree and RB tree properties, hence we move ahead to add another node.
树不为空;我们用下一个整数创建一个新的红色节点。但新节点的父节点不是黑色节点,
The tree is not empty; we create a new red node with the next integer to it. But the parent of the new node is not a black colored node,
现在树违反了二叉搜索树和 RB 树的属性;因为父节点的兄弟节点是 NULL,我们应用了合适的旋转并重新给节点着色。
The tree right now violates both the binary search tree and RB tree properties; since parent’s sibling is NULL, we apply a suitable rotation and recolor the nodes.
现在 RB 树属性已恢复,我们向树中添加另一个节点 −
Now that the RB Tree property is restored, we add another node to the tree −
树再次违背 RB 树的平衡属性,因此我们检查父节点的兄弟节点颜色,在本例中为红色,因此我们只需重新给父节点和兄弟节点着色。
The tree once again violates the RB Tree balance property, so we check for the parent’s sibling node color, red in this case, so we just recolor the parent and the sibling.
接下来,我们插入元素 5,这使树再次违反 RB 树的平衡属性。
We next insert the element 5, which makes the tree violate the RB Tree balance property once again.
并且由于兄弟节点是 NULL,我们应用了合适的旋转并重新给颜色。
And since the sibling is NULL, we apply suitable rotation and recolor.
现在,我们插入元素 6,但 RB 树属性被违反,需要应用插入案例之一 −
Now, we insert element 6, but the RB Tree property is violated and one of the insertion cases need to be applied −
父节点的兄弟节点是红色的,因此我们重新给父节点、父节点的兄弟节点和祖父节点着色,因为祖父节点不是根节点。
The parent’s sibling is red, so we recolor the parent, parent’s sibling and the grandparent nodes since the grandparent is not the root node.
现在,我们添加最后一个元素 7,但这个新节点的父节点是红色的。
Now, we add the last element, 7, but the parent node of this new node is red.
因为父节点的兄弟节点是 NULL,我们应用合适的旋转(RR 旋转)
Since the parent’s sibling is NULL, we apply suitable rotations (RR rotation)
最终的 RB 树已完成。
The final RB Tree is achieved.
Deletion
红黑树上的删除操作必须以一种方式执行,这种方式必须恢复二叉搜索树和红黑树的所有属性。按照以下步骤在红黑树上执行删除操作 −
The deletion operation on red black tree must be performed in such a way that it must restore all the properties of a binary search tree and a red black tree. Follow the steps below to perform the deletion operation on the red black tree −
首先,我们基于二叉搜索树属性执行删除。
Firstly, we perform deletion based on the binary search tree properties.
Case 1 - 如果要删除的节点或节点的父节点为红色,则直接删除它。
Case 1 − If either the node to be deleted or the node’s parent is red, just delete it.
Case 2 - 如果节点是双黑色,则只需删除双黑色(双黑色发生在待删除节点为黑色叶节点时,因为它添加了也被视为黑色节点的 NULL 节点)
Case 2 − If the node is a double black, just remove the double black (double black occurs when the node to be deleted is a black colored leaf node, as it adds up the NULL nodes which are considered black colored nodes too)
Case 3 - 如果双黑节点的兄弟节点也是黑色节点并且其子节点也是黑色,请按照以下步骤操作 −
Case 3 − If the double black’s sibling node is also a black node and its child nodes are also black in color, follow the steps below −
-
Remove double black
-
Recolor its parent to black (if the parent is a red node, it becomes black; if the parent is already a black node, it becomes double black)
-
Recolor the parent’s sibling with red
-
If double black node still exists, we apply other cases.
Case 4 − 如果双黑节点的兄弟节点为红色,则执行以下操作 −
Case 4 − If the double black node’s sibling is red, we perform the following steps −
-
Swap the colors of the parent node and the parent’s sibling node.
-
Rotate parent node in the double black’s direction
-
Reapply other cases that are suitable.
Case 5 − 如果双黑节点的兄弟节点是黑节点,但兄弟节点的一个最近的子节点为红色,则按照以下步骤进行 −
Case 5 − If the double black’s sibling is a black node but the sibling’s child node that is closest to the double black is red, follows the steps below −
-
Swap the colors of double black’s sibling and the sibling’s child in question
-
Rotate the sibling node is the opposite direction of double black (i.e. if the double black is a right child apply left rotations and vice versa)
-
Apply case 6.
Case 6 − 如果双黑节点的兄弟节点是黑节点,但兄弟节点的一个更远的子节点为红色,则按照以下步骤进行 −
Case 6 − If the double black’s sibling is a black node but the sibling’s child node that is farther to the double black is red, follows the steps below −
-
Swap the colors of double black’s parent and sibling nodes
-
Rotate the parent in double black’s direction (i.e. if the double black is a right child apply right rotations and vice versa)
-
Remove double black
-
Change the color of red child node to black.
Deletion Example
Deletion Example
考虑以上构建的红黑树,让我们从树中删除一些元素。
Considering the same constructed Red-Black Tree above, let us delete few elements from the tree.
从树中删除元素 4、5、3。
Delete elements 4, 5, 3 from the tree.
要删除元素 4,让我们首先执行二元查找删除。
To delete the element 4, let us perform the binary search deletion first.
执行二元查找删除后,红黑树属性不受干扰,因此树保持原样。
After performing the binary search deletion, the RB Tree property is not disturbed, therefore the tree is left as it is.
然后,我们使用二元查找删除来删除元素 5
Then, we delete the element 5 using the binary search deletion
但是在执行二元查找删除后,红黑属性被破坏,即树中的所有路径未保存相同数量的黑节点;因此,我们交换颜色来平衡树。
But the RB property is violated after performing the binary search deletion, i.e., all the paths in the tree do not hold same number of black nodes; so we swap the colors to balance the tree.
然后,我们从获得的树中删除节点 3 −
Then, we delete the node 3 from the tree obtained −
应用二进制查找删除,我们正常删除节点 3,因为它是一个叶节点。由于 3 是一个黑色节点,我们得到了一个双重节点。
Applying binary search deletion, we delete node 3 normally as it is a leaf node. And we get a double node as 3 is a black colored node.
我们应用情况 3 删除,因为双重黑色的兄弟节点是黑色,其子节点也是黑色。在这里,我们删除双重黑色,为双重黑色的父级和兄弟节点重新着色。
We apply case 3 deletion as double black’s sibling node is black and its child nodes are also black. Here, we remove the double black, recolor the double black’s parent and sibling.
所有所需的节点都被删除,并且 RB 树属性得到了保持。
All the desired nodes are deleted and the RB Tree property is maintained.
Search
红黑树中的搜索操作遵循与二叉查找树相同的算法。遍历树,并比较每个节点与要搜索的关键元素;如果找到,则返回成功的搜索。否则,返回不成功的搜索。
The search operation in red-black tree follows the same algorithm as that of a binary search tree. The tree is traversed and each node is compared with the key element to be searched; if found it returns a successful search. Otherwise, it returns an unsuccessful search.
B Trees
B 树是扩展的二叉查找树,专门用于 m 路搜索,因为 B 树的阶为“m”。树的阶定义为一个节点可容纳的最大子节点数。因此,B 树的高度比 AVL 树和 RB 树的高度要低得多。
B trees are extended binary search trees that are specialized in m-way searching, since the order of B trees is ‘m’. Order of a tree is defined as the maximum number of children a node can accommodate. Therefore, the height of a b tree is relatively smaller than the height of AVL tree and RB tree.
它们是二分搜索树的一般形式,因为它包含多个键和两个子项。
They are general form of a Binary Search Tree as it holds more than one key and two children.
B 树的各种属性包括 -
The various properties of B trees include −
-
Every node in a B Tree will hold a maximum of m children and (m-1) keys, since the order of the tree is m.
-
Every node in a B tree, except root and leaf, can hold at least m/2 children
-
The root node must have no less than two children.
-
All the paths in a B tree must end at the same level, i.e. the leaf nodes must be at the same level.
-
A B tree always maintains sorted data.
B 树还广泛用于磁盘访问,因为它高度较低,减少了磁盘访问时间。
B trees are also widely used in disk access, minimizing the disk access time since the height of a b tree is low.
Note - 磁盘访问是计算机磁盘的内存访问,信息存储在其中,磁盘访问时间是系统访问磁盘内存所花费的时间。
Note − A disk access is the memory access to the computer disk where the information is stored and disk access time is the time taken by the system to access the disk memory.
Basic Operations of B Trees
B 树中支持的操作为插入、删除和搜索,每个操作的时间复杂度为 O(log n) 。
The operations supported in B trees are Insertion, deletion and searching with the time complexity of O(log n) for every operation.
Insertion
B 树的插入操作类似于二叉搜索树,但将元素插入相同节点中,直至达到最大键。使用以下过程执行插入:
The insertion operation for a B Tree is done similar to the Binary Search Tree but the elements are inserted into the same node until the maximum keys are reached. The insertion is done using the following procedure −
Step 1 − 计算一个节点可以容纳的最大 $\mathrm{\left ( m-1 \right )}$ 和最小 $\mathrm{\left ( \left \lceil \frac{m}{2}\right \rceil-1 \right )}$ 数目的键,其中 m 表示 B 树的阶。
Step 1 − Calculate the maximum $\mathrm{\left ( m-1 \right )}$ and minimum $\mathrm{\left ( \left \lceil \frac{m}{2}\right \rceil-1 \right )}$ number of keys a node can hold, where m is denoted by the order of the B Tree.
Step 2 - 使用二进制搜索插入将数据插入到树中,一旦键达到最大数量,节点将分成两半,中键成为内部节点,而左右键成为其子节点。
Step 2 − The data is inserted into the tree using the binary search insertion and once the keys reach the maximum number, the node is split into half and the median key becomes the internal node while the left and right keys become its children.
Step 3 - 所有叶子节点必须在同一层。
Step 3 − All the leaf nodes must be on the same level.
键 5、3、21、9、13 根据二进制搜索属性全部添加到节点中,但如果我们添加键 22,则它将违反最大键属性。因此,节点分成两半,中键移动到父节点,然后继续插入。
The keys, 5, 3, 21, 9, 13 are all added into the node according to the binary search property but if we add the key 22, it will violate the maximum key property. Hence, the node is split in half, the median key is shifted to the parent node and the insertion is then continued.
在插入 11 时也会出现另一次小故障,因此节点被分成两半,中键移动到父级。
Another hiccup occurs during the insertion of 11, so the node is split and median is shifted to the parent.
在插入 16 时,即使将节点分成两部分,父节点也溢出,因为其达到最大键数。因此,先拆分父节点,中键成为根。然后,将叶节点分成两半,叶节点的中键移动到其父级。
While inserting 16, even if the node is split in two parts, the parent node also overflows as it reached the maximum keys. Hence, the parent node is split first and the median key becomes the root. Then, the leaf node is split in half the median of leaf node is shifted to its parent.
插入所有元素后,将实现最终的 B 树。
The final B tree after inserting all the elements is achieved.
Example
// Insert the value
void insertion(int item) {
int flag, i;
struct btreeNode *child;
flag = setNodeValue(item, &i, root, &child);
if (flag)
root = createNode(i, child);
}
Deletion
B 树中的删除操作与二叉搜索树的删除操作略有不同。从 B 树中删除节点的过程如下:
The deletion operation in a B tree is slightly different from the deletion operation of a Binary Search Tree. The procedure to delete a node from a B tree is as follows −
Case 1 - 如果要删除的键位于叶节点中并且删除不违反最小键属性,则只需删除该节点。
Case 1 − If the key to be deleted is in a leaf node and the deletion does not violate the minimum key property, just delete the node.
Case 2 - 如果要删除的键位于叶节点中,但删除违反了最小键属性,则向其左兄弟节点或右兄弟节点借一个键。如果两个兄弟节点都具有相同数量的最小键,则将节点合并到其中任何一个中。
Case 2 − If the key to be deleted is in a leaf node but the deletion violates the minimum key property, borrow a key from either its left sibling or right sibling. In case if both siblings have exact minimum number of keys, merge the node in either of them.
Case 3 - 如果要删除的键位于内部节点中,则用其左子节点或右子节点中的键替换该键,具体取决于哪个子节点具有更多的键。但是,如果两个子节点都具有最小数量的键,则它们会被合并在一起。
Case 3 − If the key to be deleted is in an internal node, it is replaced by a key in either left child or right child based on which child has more keys. But if both child nodes have minimum number of keys, they’re merged together.
Case 4 - 如果要删除的键位于内部节点中,违反了最小键属性,并且其子节点和兄弟节点都具有最小数量的键,则合并子节点。然后,将其兄弟节点与父节点合并。
Case 4 − If the key to be deleted is in an internal node violating the minimum keys property, and both its children and sibling have minimum number of keys, merge the children. Then merge its sibling with its parent.
下面是 B 树中删除操作的 C++ 功能代码片段 −
Following is functional C++ code snippet of the deletion operation in B Trees −
// Deletion operation
void deletion(int key){
int index = searchkey(key);
if (index < n && keys[index] == key) {
if (leaf)
deletion_at_leaf(index);
else
deletion_at_nonleaf(index);
} else {
if (leaf) {
cout << "key " << key << " does not exist in the tree\n";
return;
}
bool flag = ((index == n) ? true : false);
if (C[index]->n < t)
fill(index);
if (flag && index > n)
C[index - 1]->deletion(key);
else
C[index]->deletion(key);
}
return;
}
// Deletion at the leaf nodes
void deletion_at_leaf(int index){
for (int i = index + 1; i < n; ++i)
keys[i - 1] = keys[i];
n--;
return;
}
// Deletion at the non leaf node
void deletion_at_nonleaf(int index){
int key = keys[index];
if (C[index]->n >= t) {
int pred = get_Predecessor(index);
keys[index] = pred;
C[index]->deletion(pred);
} else if (C[index + 1]->n >= t) {
int successor = copysuccessoressor(index);
keys[index] = successor;
C[index + 1]->deletion(successor);
} else {
merge(index);
C[index]->deletion(key);
}
return;
}
B+ Trees
B+ 树是 B 树的扩展,旨在使插入、删除和搜索操作更有效率。
The B+ trees are extensions of B trees designed to make the insertion, deletion and searching operations more efficient.
B+ 树的性质与 B 树的性质相似,不同之处在于 B 树可以在所有内部节点和叶子节点中存储键和记录,而 B+ 树则在叶子节点中存储记录,在内部节点中存储键。B+ 树的一个显著特性是,所有叶子节点都以单链表格式彼此连接,并且提供了一个数据指针来指向磁盘文件中存在的数据。这有助于以相等数量的磁盘访问来获取记录。
The properties of B+ trees are similar to the properties of B trees, except that the B trees can store keys and records in all internal nodes and leaf nodes while B+ trees store records in leaf nodes and keys in internal nodes. One profound property of the B+ tree is that all the leaf nodes are connected to each other in a single linked list format and a data pointer is available to point to the data present in disk file. This helps fetch the records in equal numbers of disk access.
由于主内存的大小有限,因此 B+ 树充当了存储在主内存中无法存储的记录的数据存储。为此,内部节点存储在主内存中,而叶子节点存储在辅助内存存储中。
Since the size of main memory is limited, B+ trees act as the data storage for the records that couldn’t be stored in the main memory. For this, the internal nodes are stored in the main memory and the leaf nodes are stored in the secondary memory storage.
Properties of B+ trees
-
Every node in a B+ Tree, except root, will hold a maximum of m children and (m-1) keys, and a minimum of $\mathrm{\left \lceil \frac{m}{2}\right \rceil}$ children and $\mathrm{\left \lceil \frac{m-1}{2}\right \rceil}$ keys, since the order of the tree is m.
-
The root node must have no less than two children and at least one search key.
-
All the paths in a B tree must end at the same level, i.e. the leaf nodes must be at the same level.
-
A B+ tree always maintains sorted data.
Basic Operations of B+ Trees
B+ 树支持的操作是插入、删除和搜索,所有操作的时间复杂度为 O(log n) 。
The operations supported in B+ trees are Insertion, deletion and searching with the time complexity of O(log n) for every operation.
它们与 B 树操作类似,因为将数据存储在此两种数据结构中的基本思想是相同的。然而,两者的不同之处在于,与 B 树不同,数据仅存储在 B+ 树的叶结点中。
They are almost similar to the B tree operations as the base idea to store data in both data structures is same. However, the difference occurs as the data is stored only in the leaf nodes of a B+ trees, unlike B trees.
Insertion
B+ 树的插入从叶结点开始。
The insertion to a B+ tree starts at a leaf node.
Step 1 − 计算要添加到 B+ 树结点上的最大和最小键数。
Step 1 − Calculate the maximum and minimum number of keys to be added onto the B+ tree node.
Step 2 − 将元素一个接一个地插入叶结点,直到超过最大键数。
Step 2 − Insert the elements one by one accordingly into a leaf node until it exceeds the maximum key number.
Step 3 − 将结点分成两半,其中左子结点包含最小键数,而剩余键存储在右子结点中。
Step 3 − The node is split into half where the left child consists of minimum number of keys and the remaining keys are stored in the right child.
Step 4 − 但是,如果内部结点也超过了最大键属性,则将结点分成两半,其中左子结点包含最小键,而剩余键存储在右子结点中。但是,将右子结点中最小的数字设为父结点。
Step 4 − But if the internal node also exceeds the maximum key property, the node is split in half where the left child consists of the minimum keys and remaining keys are stored in the right child. However, the smallest number in the right child is made the parent.
Step 5 − 如果叶结点和内部结点都具有最大键,则它们都会以类似的方式分割,并且右子结点中的最小键添加到父结点中。
Step 5 − If both the leaf node and internal node have the maximum keys, both of them are split in the similar manner and the smallest key in the right child is added to the parent node.
Deletion
在 B+ 树中的删除操作中,我们需要考虑数据结构中的冗余。
The deletion operation in a B+ tree, we need to consider the redundancy in the data structure.
Case 1 − 对于存在于超过最小键数目的叶节点且副本不存在于内部节点中的键,简单地删除它。
Case 1 − If the key is present in a leaf node which has more than minimal number of keys, without its copy present in the internal nodes, simple delete it.
Case 2 − 对于存在于仅包含最小键数目的叶节点且副本不存在于内部节点中的键,从其兄弟节点借用一个键并删除所需的键。
Case 2 − If the key is present in a leaf node with exactly minimal number of keys and a copy of it is not present in the internal nodes, borrow a key from its sibling node and delete the desired key.
Case 3 − 对于存在于叶节点且副本存在于内部节点中的键,有多种可能的情况 −
Case 3 − If the key present in the leaf node has its copy in the internal nodes, there are multiple scenarios possible −
-
More than minimal keys present in both leaf node and internal node: simply delete the key and add the inorder successor to the internal node only.
-
Exact minimum number of keys present in the leaf node: delete the node and borrow a node from its immediate sibling and replace its value in internal node as well.
-
If the copy of the leaf node to be delete is in its grandparent, delete the node and remove the empty space. The grandparent is filled with the inorder successor of the deleted node.
Case 4 − 如果要删除的键存在于最小键属性中的一个违反规则的节点中,其父级和兄弟节点都包含最小键数目,则删除该键,并且将其兄弟节点与其父级合并。
Case 4 − If the key to be deleted is in a node violating the minimum keys property, both its parent and sibling have minimum number of keys, delete the key and merge its sibling with its parent.
Splay Trees
Splay 树是二叉搜索树的变体,因为它包含所有 BST 操作,如插入、删除和搜索,然后是另一个称为 splaying 的扩展操作。
Splay trees are the altered versions of the Binary Search Trees, since it contains all the operations of BSTs, like insertion, deletion and searching, followed by another extended operation called splaying.
例如,应该将一个值为 “A” 的值插入到树中。如果树是空,则将 “A” 添加到树的根节点并退出;但是,如果树不为空,则使用二进制查找插入操作来插入元素,然后在新节点上执行伸展操作。
For instance, a value “A” is supposed to be inserted into the tree. If the tree is empty, add “A” to the root of the tree and exit; but if the tree is not empty, use binary search insertion operation to insert the element and then perform splaying on the new node.
类似地,在 splay 树中搜索一个元素之后,包含该元素的节点也必须被 splaying。
Similarly, after searching an element in the splay tree, the node consisting of the element must be splayed as well.
但是我们如何执行 splaying?简单来说,splaying 就是将一个操作节点移到根处的过程。它有六种类型的旋转。
But how do we perform splaying? Splaying, in simpler terms, is just a process to bring an operational node to the root. There are six types of rotations for it.
-
Zig rotation
-
Zag rotation
-
Zig-Zig rotation
-
Zag-Zag rotation
-
Zig-Zag rotation
-
Zag-Zig rotation
Zig rotation
当操作节点是根节点或根节点的左子节点时,执行 zig 旋转。该节点向右旋转。
The zig rotations are performed when the operational node is either the root node or the left child node of the root node. The node is rotated towards its right.
经过移动后,树看起来如下:
After the shift, the tree will look like −
Zag rotation
当操作节点是根节点或根节点的右子节点时,也执行 zag 旋转。该节点向左旋转。
The zag rotations are also performed when the operational node is either the root node or the right child nod of the root node. The node is rotated towards its left.
经过移动后,操作节点变为根节点:
The operational node becomes the root node after the shift −
Zig-Zig rotation
当操作节点同时具有父节点和祖先节点时,执行 zig-zig 旋转。该节点向右移动两个位置。
The zig-zig rotations are performed when the operational node has both parent and a grandparent. The node is rotated two places towards its right.
第一次旋转将树向右移动一个位置:
The first rotation will shift the tree to one position right −
第二次向右旋转将再次将节点移动一个位置。移动后的最终树看起来如下:
The second right rotation will once again shift the node for one position. The final tree after the shift will look like this −
Zag-Zag rotation
当操作节点同时具有父节点和祖先节点时,也执行 zag-zag 旋转。该节点向左移动两个位置。
The zag-zag rotations are also performed when the operational node has both parent and a grandparent. The node is rotated two places towards its left.
经过第一次旋转后,树看起来如下:
After the first rotation, the tree will look like −
然后,第二次旋转后的最终树如下所示。然而,操作节点仍然不是根,因此 splaying 被认为不完整。因此,在这种情况下再次应用其他合适的旋转,直到该节点成为根节点。
Then the final tree after the second rotation is given as follows. However, the operational node is still not the root so the splaying is considered incomplete. Hence, other suitable rotations are again applied in this case until the node becomes the root.
Zig-Zag rotation
当操作节点同时具有父节点和祖先节点时,执行 zig-zag 旋转。但不同之处在于,祖先节点父节点和子节点采用 LRL 格式。该节点先向右旋转,然后向左旋转。
The zig-zag rotations are performed when the operational node has both a parent and a grandparent. But the difference is the grandparent, parent and child are in LRL format. The node is rotated first towards its right followed by left.
经过第一次旋转后,树如下:
After the first rotation, the tree is −
经过第二次旋转后的最终树如下:
The final tree after the second rotation −
Zag-Zig rotation
当操作节点同时具有父节点和祖先节点时,也执行 zag-zig 旋转。但不同之处在于,祖先节点、父节点和子节点采用 RLR 格式。该节点先向左旋转,然后向右旋转。
The zag-zig rotations are also performed when the operational node has both parent and grandparent. But the difference is the grandparent, parent and child are in RLR format. The node is rotated first towards its left followed by right.
进行了第一次旋转,得到如下树:
First rotation is performed, the tree is obtained as −
经过第二次旋转,最终得到如下树。然而,操作节点尚未成为根节点,因此需要执行另一次旋转,将该节点设为根。
After second rotation, the final tree is given as below. However, the operational node is not the root node yet so one more rotation needs to be performed to make the said node as the root.
Basic Operations of Splay Trees
伸展树包含二叉搜索树提供的所有基本操作:插入、删除和搜索。然而,在每个操作之后,都会有一个附加操作将其与二叉搜索树操作区分开来:伸展。我们已经学过伸展,因此,让我们理解其他操作的过程。
A splay contains the same basic operations that a Binary Search Tree provides with: Insertion, Deletion, and Search. However, after every operation there is an additional operation that differs them from Binary Search tree operations: Splaying. We have learned about Splaying already so let us understand the procedures of the other operations.
Insertion
伸展树中的插入操作与在二叉搜索树中执行插入操作的方式完全相同。在伸展树中执行插入操作的过程如下:
The insertion operation in a Splay tree is performed in the exact same way insertion in a binary search tree is performed. The procedure to perform the insertion in a splay tree is given as follows −
-
Check whether the tree is empty; if yes, add the new node and exit
-
If the tree is not empty, add the new node to the existing tree using the binary search insertion.
-
Then, suitable splaying is chosen and applied on the newly added node.
Zag (Left) Rotation is applied on the new node
Zag (Left) Rotation is applied on the new node
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Deletion
伸展树中的删除操作执行如下:
The deletion operation in a splay tree is performed as following −
-
Apply splaying operation on the node to be deleted.
-
Once, the node is made the root, delete the node.
-
Now, the tree is split into two trees, the left subtree and the right subtree; with their respective first nodes as the root nodes: say root_left and root_right.
-
If root_left is a NULL value, then the root_right will become the root of the tree. And vice versa.
-
But if both root_left and root_right are not NULL values, then select the maximum value from the left subtree and make it the new root by connecting the subtrees.
Example
以下是该操作在各种编程语言中的实现 −
Following are the implementations of this operation in various programming languages −
Search
伸展树中的搜索操作遵循二叉搜索树操作的相同过程。然而,在进行搜索并找到元素之后,对搜索的节点应用伸展。如果找不到元素,则提示搜索不成功。
The search operation in a Splay tree follows the same procedure of the Binary Search Tree operation. However, after the searching is done and the element is found, splaying is applied on the node searched. If the element is not found, then unsuccessful search is prompted.
Tries Data Structure
trie 是一种多路搜索树,它根本用于从字符串或一组字符串中检索特定的键。它以一种有效的方式存储数据,因为它对字母表中的每个字母都使用指针。
A trie is a type of a multi-way search tree, which is fundamentally used to retrieve specific keys from a string or a set of strings. It stores the data in an ordered efficient way since it uses pointers to every letter within the alphabet.
trie 数据结构基于字符串的公共前缀。考虑到集中字符串的数量,根节点可以具有任意数目的节点。trie 的根节点不包含任何值,只包含其子节点的指针。
The trie data structure works based on the common prefixes of strings. The root node can have any number of nodes considering the amount of strings present in the set. The root of a trie does not contain any value except the pointers to its child nodes.
有三种类型的 trie 数据结构 −
There are three types of trie data structures −
-
Standard Tries
-
Compressed Tries
-
Suffix Tries
trie 的实际应用包括:自动更正、文本预测、情感分析和数据科学。
The real-world applications of trie include − autocorrect, text prediction, sentiment analysis and data sciences.
Basic Operations in Tries
trie 数据结构还执行树数据结构执行的相同操作。它们是:
The trie data structures also perform the same operations that tree data structures perform. They are −
-
Insertion
-
Deletion
-
Search
Insertion
trie 中的插入操作是一种简单的方法。trie 中的根不包含任何值,并且插入操作从根的直接子节点开始,这些子节点充当其子节点的键。然而,我们观察到trie中的每个节点都代表输入字符串中的一个字符。因此,字符一个接一个地添加到trie中,而trie中的链接充当指向下一级节点的指针。
The insertion operation in a trie is a simple approach. The root in a trie does not hold any value and the insertion starts from the immediate child nodes of the root, which act like a key to their child nodes. However, we observe that each node in a trie represents a singlecharacter in the input string. Hence the characters are added into the tries one by one while the links in the trie act as pointers to the next level nodes.
Deletion
trie 中的删除操作使用自下而上的方法执行。在trie中搜索元素并进行删除(如果找到)。然而,在执行删除操作时需要记住一些特殊的情况。
The deletion operation in a trie is performed using the bottom-up approach. The element is searched for in a trie and deleted, if found. However, there are some special scenarios that need to be kept in mind while performing the deletion operation.
Case 1 - 键是唯一的 - 在这种情况下,整个键路径从节点中删除。(唯一键表示没有其他路径从一条路径分支出来)。
Case 1 − The key is unique − in this case, the entire key path is deleted from the node. (Unique key suggests that there is no other path that branches out from one path).
Case 2 - 键不是唯一的 - 更新叶节点。例如,如果要删除的键是 see ,但它是另一个键 seethe 的前缀;我们删除 see 并将 t、h 和 e 的布尔值更改为 false。
Case 2 − The key is not unique − the leaf nodes are updated. For example, if the key to be deleted is see but it is a prefix of another key seethe; we delete the see and change the Boolean values of t, h and e as false.
Case 3 - 要删除的键已经有一个前缀 - 直到前缀的值被删除,并且前缀保留在树中。例如,如果要删除的键是 heart ,但存在另一个键 he ;因此,我们删除 a、r 和 t,直到只剩下 he。
Case 3 − The key to be deleted already has a prefix − the values until the prefix are deleted and the prefix remains in the tree. For example, if the key to be deleted is heart but there is another key present he; so we delete a, r, and t until only he remains.
Search
在trie中搜索是一种相当直接的方法。我们只能基于键节点(插入操作开始的节点)向下移动trie的级别。搜索直到路径的末尾。如果找到该元素,则搜索成功;否则,搜索提示不成功。
Searching in a trie is a rather straightforward approach. We can only move down the levels of trie based on the key node (the nodes where insertion operation starts at). Searching is done until the end of the path is reached. If the element is found, search is successful; otherwise, search is prompted unsuccessful.
Heap Data Structure
堆是平衡二叉树数据结构的一个特例,其中将根节点键与其子节点进行比较并相应排列。如果 α 有子节点 β ,则:
Heap is a special case of balanced binary tree data structure where the root-node key is compared with its children and arranged accordingly. If α has child node β then −
key(α) ≥ key(β)
由于父级的值大于子级的值,因此此属性生成 Max Heap 。基于此标准,堆可以分为两种类型:
As the value of parent is greater than that of child, this property generates Max Heap. Based on this criteria, a heap can be of two types −
For Input → 35 33 42 10 14 19 27 44 26 31
Min-Heap - 其中根节点的值小于或等于其任一子节点。
Min-Heap − Where the value of the root node is less than or equal to either of its children.
Max-Heap - 其中根节点的值大于或等于其任一子节点。
Max-Heap − Where the value of the root node is greater than or equal to either of its children.
两棵树都是使用相同的输入和到达顺序构建的。
Both trees are constructed using the same input and order of arrival.
Max Heap Construction Algorithm
我们将使用相同的示例来说明如何创建最大堆。创建最小堆的过程类似,但我们追求最小值而不是最大值。
We shall use the same example to demonstrate how a Max Heap is created. The procedure to create Min Heap is similar but we go for min values instead of max values.
我们将通过一次插入一个元素推导出最大堆的算法。在任何时候,堆都必须保持其属性。在插入时,我们还假设我们将一个节点插入到已经堆化的树中。
We are going to derive an algorithm for max heap by inserting one element at a time. At any point of time, heap must maintain its property. While insertion, we also assume that we are inserting a node in an already heapified tree.
Step 1 − Create a new node at the end of heap.
Step 2 − Assign new value to the node.
Step 3 − Compare the value of this child node with its parent.
Step 4 − If value of parent is less than child, then swap them.
Step 5 − Repeat step 3 & 4 until Heap property holds.
Note - 在最小堆构造算法中,我们希望父节点的值小于子节点的值。
Note − In Min Heap construction algorithm, we expect the value of the parent node to be less than that of the child node.
让我们通过动画图解来了解最大堆的构造。我们考虑前面使用过的相同输入样本。
Let’s understand Max Heap construction by an animated illustration. We consider the same input sample that we used earlier.
Max Heap Deletion Algorithm
让我们推导出一个从最大堆中删除的算法。最大(或最小)堆中的删除始终发生在根处以删除最大(或最小)值。
Let us derive an algorithm to delete from max heap. Deletion in Max (or Min) Heap always happens at the root to remove the Maximum (or minimum) value.
Step 1 − Remove root node.
Step 2 − Move the last element of last level to root.
Step 3 − Compare the value of this child node with its parent.
Step 4 − If value of parent is less than child, then swap them.
Step 5 − Repeat step 3 & 4 until Heap property holds.
Recursion Algorithms
一些计算机编程语言允许模块或函数调用自身。这种技术称为递归。在递归中,一个函数 α 要么直接调用自身,要么调用一个函数 β 而该函数反过来又调用了原来的函数 α 。函数 α 称为递归函数。
Some computer programming languages allow a module or function to call itself. This technique is known as recursion. In recursion, a function α either calls itself directly or calls a function β that in turn calls the original function α. The function α is called recursive function.
Example − 一个调用自身的函数。
Example − a function calling itself.
int function(int value) {
if(value < 1)
return;
function(value - 1);
printf("%d ",value);
}
Example − 一个调用另一个函数(而该函数反过来又调用自身)的函数。
Example − a function that calls another function which in turn calls it again.
int function1(int value1) {
if(value1 < 1)
return;
function2(value1 - 1);
printf("%d ",value1);
}
int function2(int value2) {
function1(value2);
}
Properties
一个递归函数可以像循环一样无限运行。为了避免递归函数无限运行,递归函数必须具有以下两个特性:
A recursive function can go infinite like a loop. To avoid infinite running of recursive function, there are two properties that a recursive function must have −
-
Base criteria − There must be at least one base criteria or condition, such that, when this condition is met the function stops calling itself recursively.
-
Progressive approach − The recursive calls should progress in such a way that each time a recursive call is made it comes closer to the base criteria.
Implementation
许多编程语言通过 stacks 实现递归。通常,每当一个函数 ( caller ) 调用另一个函数 ( callee ) 或自身作为被调用方时,调用方函数会将执行控制权转移到被调用方。这个转移过程还可能涉及将一些数据从调用方传递到被调用方。
Many programming languages implement recursion by means of stacks. Generally, whenever a function (caller) calls another function (callee) or itself as callee, the caller function transfers execution control to the callee. This transfer process may also involve some data to be passed from the caller to the callee.
这意味着,调用方函数必须暂时挂起其执行,并在执行控制权从被调用方函数返回时继续执行。在这里,调用方函数需要从它暂停执行时的执行点准确地开始。它还需要它当时正在处理的确切相同的数据值。为此,将会为调用方函数创建一个激活记录(或堆栈帧)。
This implies, the caller function has to suspend its execution temporarily and resume later when the execution control returns from the callee function. Here, the caller function needs to start exactly from the point of execution where it puts itself on hold. It also needs the exact same data values it was working on. For this purpose, an activation record (or stack frame) is created for the caller function.
这个激活记录保存有关局部变量、形式参数、返回地址和传递给调用方函数的所有信息。
This activation record keeps the information about local variables, formal parameters, return address and all information passed to the caller function.
Analysis of Recursion
有人可能会争论为何使用递归,因为相同的任务可以用迭代来完成。第一个原因是,递归使程序更具有可读性,并且由于采用了最新的增强型 CPU 系统,因此递归比迭代更有效。
One may argue why to use recursion, as the same task can be done with iteration. The first reason is, recursion makes a program more readable and because of latest enhanced CPU systems, recursion is more efficient than iterations.
Time Complexity
对于迭代,我们采用迭代次数来计算时间复杂度。同样,对于递归,假设一切都是常数,我们尝试找出进行递归调用的次数。对函数进行一次调用为 Ο(1),因此递归调用 (n) 次将使递归函数 Ο(n)。
In case of iterations, we take number of iterations to count the time complexity. Likewise, in case of recursion, assuming everything is constant, we try to figure out the number of times a recursive call is being made. A call made to a function is Ο(1), hence the (n) number of times a recursive call is made makes the recursive function Ο(n).
Space Complexity
空间复杂度是指模块执行所需的额外空间量。对于迭代,编译器几乎不需要任何额外空间。编译器不断更新在迭代中使用的变量值。但是在递归的情况下,系统需要在进行每次递归调用时存储激活记录。因此,人们认为递归函数的空间复杂度可能高于具有迭代的函数。
Space complexity is counted as what amount of extra space is required for a module to execute. In case of iterations, the compiler hardly requires any extra space. The compiler keeps updating the values of variables used in the iterations. But in case of recursion, the system needs to store activation record each time a recursive call is made. Hence, it is considered that space complexity of recursive function may go higher than that of a function with iteration.
Tower of Hanoi Using Recursion
河内塔是一个数学谜题,由三根塔(桩)和多个圆环组成,如下图所示 -
Tower of Hanoi, is a mathematical puzzle which consists of three towers (pegs) and more than one rings is as depicted −
这些圆环大小不同,并且按升序堆叠,即较小的圆环放在较大的圆环上面。还有其他变种的谜题,其中磁盘的数量不断增加,但塔数保持不变。
These rings are of different sizes and stacked upon in an ascending order, i.e. the smaller one sits over the larger one. There are other variations of the puzzle where the number of disks increase, but the tower count remains the same.
Rules
任务是将所有磁盘移动到另一个塔,而不违反排列顺序。解决河内塔的几个规则如下 -
The mission is to move all the disks to some another tower without violating the sequence of arrangement. A few rules to be followed for Tower of Hanoi are −
-
Only one disk can be moved among the towers at any given time.
-
Only the "top" disk can be removed.
-
No large disk can sit over a small disk.
以下是解决带有三个圆盘的河内塔谜题的动画表示。
Following is an animated representation of solving a Tower of Hanoi puzzle with three disks.
带有 n 个磁盘的河内塔谜题可以在最少 2n−1 步中解决。此演示显示带有 3 个磁盘的谜题已经进行了 23 - 1 = 7 步。
Tower of Hanoi puzzle with n disks can be solved in minimum 2n−1 steps. This presentation shows that a puzzle with 3 disks has taken 23 - 1 = 7 steps.
Algorithm
要编写河内塔算法,首先我们需要学习如何解决圆盘更少的问题,例如 → 1 或 2。我们用 source 、 destination 和 aux 标记三个塔(仅帮助移动磁盘)。如果我们只有一个磁盘,则可以轻松地从源点移动到目标桩。
To write an algorithm for Tower of Hanoi, first we need to learn how to solve this problem with lesser amount of disks, say → 1 or 2. We mark three towers with name, source, destination and aux (only to help moving the disks). If we have only one disk, then it can easily be moved from source to destination peg.
如果我们有 2 个磁盘 -
If we have 2 disks −
-
First, we move the smaller (top) disk to aux peg.
-
Then, we move the larger (bottom) disk to destination peg.
-
And finally, we move the smaller disk from aux to destination peg.
所以现在,我们有能力设计一个用于超过两个磁盘的河内塔算法。我们将磁盘堆栈分成两部分。最大的磁盘(第 n 个磁盘)在一部份中,所有其他 (n-1) 个磁盘在第二部分中。
So now, we are in a position to design an algorithm for Tower of Hanoi with more than two disks. We divide the stack of disks in two parts. The largest disk (nth disk) is in one part and all other (n-1) disks are in the second part.
我们的最终目的是将磁盘 n 从源点移动到目标点,然后将所有其他 (n1) 个磁盘放在它上面。我们可以想象以递归方式对所有给定的磁盘集应用相同的操作。
Our ultimate aim is to move disk n from source to destination and then put all other (n1) disks onto it. We can imagine to apply the same in a recursive way for all given set of disks.
要遵循的步骤如下 -
The steps to follow are −
Step 1 − Move n-1 disks from source to aux
Step 2 − Move nth disk from source to dest
Step 3 − Move n-1 disks from aux to dest
河内塔的递归算法可以驱动如下 -
A recursive algorithm for Tower of Hanoi can be driven as follows −
START
Procedure Hanoi(disk, source, dest, aux)
IF disk == 1, THEN
move disk from source to dest
ELSE
Hanoi(disk - 1, source, aux, dest) // Step 1
move disk from source to dest // Step 2
Hanoi(disk - 1, aux, dest, source) // Step 3
END IF
END Procedure
STOP
Fibonacci Series Using Recursion
斐波那契数列通过将两个前一个数字相加来生成后续数字。斐波那契数列从两个数字开始 − F0 & F1 。F0 & F1 的初始值可以分别取 0, 1 或 1, 1。
Fibonacci series generates the subsequent number by adding two previous numbers. Fibonacci series starts from two numbers − F0 & F1. The initial values of F0 & F1 can be taken 0, 1 or 1, 1 respectively.
斐波那契数列满足以下条件:
Fibonacci series satisfies the following conditions −
Fn = Fn-1 + Fn-2
因此,一个斐波那契数列看起来可能是这样 −
Hence, a Fibonacci series can look like this −
F8 = 0 1 1 2 3 5 8 13
或者,这样 −
or, this −
F8 = 1 1 2 3 5 8 13 21
为了说明,F8 的斐波那契显示为 −
For illustration purpose, Fibonacci of F8 is displayed as −
Fibonacci Iterative Algorithm
首先,我们尝试起草斐波那契数列的迭代算法。
First we try to draft the iterative algorithm for Fibonacci series.
Procedure Fibonacci(n)
declare f0, f1, fib, loop
set f0 to 0
set f1 to 1
<b>display f0, f1</b>
for loop ← 1 to n
fib ← f0 + f1
f0 ← f1
f1 ← fib
<b>display fib</b>
end for
end procedure
Fibonacci Recursive Algorithm
让我们学习如何创建斐波那契数列的递归算法。递归的基本准则。
Let us learn how to create a recursive algorithm Fibonacci series. The base criteria of recursion.
START
Procedure Fibonacci(n)
declare f0, f1, fib, loop
set f0 to 0
set f1 to 1
display f0, f1
for loop ← 1 to n
fib ← f0 + f1
f0 ← f1
f1 ← fib
display fib
end for
END