复习内容:
指针、数组、关键字、内存布局、堆和栈的区别、队列、链表。
关键字
1、数据类型关键字
A基本数据类型(5个)
void: 是用来修饰函数的参数或返回值的,代表函数没有参数或没有返回值。
char:用 char 定义的变量是字符型变量,占1个字节。
int :使用 int 定义的变量是整型变量,在32位系统和64位系统下占4个字节,在16位系统下占2个字节。
float :用 float 定义的变量是单浮点型的实数,占4个字节。如果是单浮点数,要记得在数字后面加 f ;否则默认数字为 double 型。
double :用 double 定义的变量是双浮点型的实数,占8个字节。
B 类型修饰关键字(4个)
short :修饰int,短整型数据,可省略被修饰的int。使用 short 定义的变量是短整型变量,占2个字节。
long :修饰int,长整形数据,可省略被修饰的int。使用 long 定义的变量是长整型变量,在32位系统上,long类型通常与int类型相同,占用4个字节;而在64位系统上,long类型通常占用8个字节。它用于表示较大范围的整数,取值范围比int类型更大。
signed :修饰整型数据,有符号数据类型。代表定义的数据是有符号的,可以保存正数,也可以保存负数。默认情况下 signed 可以省略。
unsigned :修饰整型数据,无符号数据类型。 代表定义的数据是无符号的数据,只能保存正数和 0 。
C 复杂类型关键字(5个)
struct :在C语言中,可以使用结构体( Struct )来存放一组不同类型的数据。结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员。
union :union(共同体或联合)就是一个多个变量的结构同时使用一块内存区域,区域的取值大小为该结构中长度最大的变量的值。共用体的初始化与赋值方式与结构体相同。但任何时刻,共用体只有一个成员变量能够存在。
enum :枚举类型用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型。枚举名不能重复,它从第一个成员开始进行编号,从0开始。
typedef :重命名相关的关键字。作用是给一个已有的类型,重新起个类型名,并没有创造一个新的类型。
sizeof :用来测变量、数组的占用存储空间的大小(字节数)。
D 存储级别关键字(6个)
auto :在函数内声明的所有变量默认情况下都被视为具有auto存储类别。当函数退出时,使用auto存储类别的变量也将自动销毁。
static :修饰变量时为静态变量,分配在静态变量区;修饰函数时,为静态函数。全局变量,所有代码都可以访问到;而静态全局变量只有当前文件可以访问。二者生存周期都是程序运行到程序结束。静态局部变量的生存周期也是程序运行到程序结束,但是局部变量的生存周期是函数调用到函数返回。静态局部变量的生存周期比局部变量的长。
register :register修饰变量的作用:尽量将所修饰变量,放入CPU寄存区中,从而达到提高效率的目的。
extern :用关键字 extern 对变量作“外部变量声明”,表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。还有调用的功能。
const :关键字const用来定义常量,如果一个变量被const修饰,那么它的值就不能再被改变。
volatile :用 volatile 定义的变量,是易改变的,即告诉 cpu 每次用 volatile 变量的时候,重新去内存中取,保证用的是最新的值,而不是寄存器中的备份。volatile 关键字现较少使用。
2、流程控制关键字
A 跳转结构(4个)
return :用在函数体中,返回特定值(或者是void值,即不返回值)
continue :结束当前循环,开始下一轮循环
break :跳出当前循环或switch结构
goto :无条件跳转语句
B 分支结构(5个)
if :条件语句
else :条件语句否定分支(与if连用)
switch :开关语句(多重分支语句)
case :开关语句中的分支标记
default :开关语句中的“其他”分治,可选。
C 循环结构(3个)
for :for循环结构
do :do循环结构,do 1 while(2); 的执行顺序是 1->2->1...循环,2为循环条件
while :while循环结构
数组
什么是数组
数组就是一个可以存储相同类型元素的顺序集合。
按维度来划分,数组分为一维数组和多维数组。
按数组的内存大小是否可变来划分,数组可以分为静态数组和动态数组。
数组的特点
①一致性:数组中的元素都是相同类型的。
②有序性:数组是一种线性数据结构,它的元素具有严格的先后顺序。因此,可以通过索引来访问和操作数组元素。
③连续性:在数组中,所有的元素在内存中都是连续存储的。
数组的好处
①保存大量有序数据:数组能够有效地存储和组织大量的信息,这些信息通常是按照一定的顺序排列的。
②减少变量数量:通过使用数组,可以避免创建大量的独立变量,从而简化程序结构和提高代码的可读性。
③节省内存空间:由于数组的每个元素都分配有连续的内存地址,这有助于优化内存管理,减少碎片化,并提高程序的性能。
④清晰的编码风格:在编程时,数组的索引使得数据的访问更加直观,便于分析和思考。
数组的使用
1.数组声明
在C语言中,你可以声明一个数组并指定其大小。例如,声明一个可以存储10个整数的数组:
int myArray[10];
2.数组初始化
可以在声明数组的同时初始化它:
int myArray[5] = {1, 2, 3, 4, 5};
如果省略数组大小,编译器会根据初始化列表中的元素数量自动确定数组大小:
int myArray[] = {1, 2, 3, 4, 5}; // 数组大小为5
3.访问数组元素
通过索引(从0开始)来访问数组中的元素:
int firstElement = myArray[0]; // 访问第一个元素
int lastElement = myArray[4]; // 访问最后一个元素(如果数组有5个元素)
4.多维数组
多维数组最简单的形式是二维数组。
int matrix[3][4]; // 声明一个3x4的整数矩阵
5. 数组作为函数参数(传递数组给函数)
可以将数组作为函数的参数传递。
函数形式参数:1、形式参数是一个指针;2、形式参数是一个确定元素个数的数组;3、形式参数是一个不确定元素个数的数组。
但通常传递的是数组的指针和大小:
void printArray(int arr[], int size) { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n");
} int main() { int myArray[5] = {1, 2, 3, 4, 5}; printArray(myArray, 5); // 调用函数,打印数组 return 0;
}
6.数组与指针
在C语言中,数组名实际上是一个指向数组第一个元素的常量指针,即数组的首地址。
int *p = myArray; // p指向myArray的第一个元素
int secondElement = *(p + 1); // 访问第二个元素
7.从函数返回数组
C 语言不允许返回一个完整的数组作为函数的参数。但是,可以通过指定不带索引的数组名来返回一个指向数组的指针。
另外,C不支持在函数外返回局部变量的地址,除非定义局部变量为static变量。
但是,可以在函数外返回局部变量的值,因为值存放在缓冲区。
8.指向数组的指针
使用数组名作为常量指针是合法的,反之亦然。因此,*(balance+4)是一种访问 balance[4] 数据的合法方式。一旦您把第一个元素的地址存储在p中,您就可以使用 *p、*(p+1)、*(p+2)等来访问数组元素。
9.数组与字符串
在C语言中,字符串通常以字符数组的形式表示,并以空字符\0
结尾:
char str[] = "Hello, World!"; // 字符串被存储为字符数组
10.数组长度
C语言标准库中的sizeof
运算符可以用来获取数组的长度(元素个数):
int length = sizeof(myArray) / sizeof(myArray[0]); // 计算数组长度
静态数组与动态数组
静态数组编译时就需要确定大小,存储在栈中,并且一旦创建,就不能改变大小。而动态数组的大小可以在运行时确定,内存分配发生在运行时,存储在堆内存上,其大小可以在运行时进行修改。另外,动态分配的数组,可以在动态分配内存时保存数组长度,并在需要时使用该长度。
静态数组与动态数组的注意点:使用静态数组时,因为在编译的时候就分配了内存,所以当内存不大时,会导致内存不足。使用动态数组时,需要注意合理地分配和释放内存,以避免内存泄漏和访问无效内存的问题。此外,使用动态数组时还要判断是否生成了内存。
指针
什么是指针
指针是一种数据类型,用于存储内存地址的整数。
指针变量就是使用这种数据类型定义的变量,其存储的值是另一个变量的地址。
指针的特点
①指针的值可以改变,也就是说,指针可以指向不同的存储单元。
②多个指针可以指向同一块内存存储单元,也就是说,多个指针可以具有相同的值。
③指针必须有类型,例如,一个指针可以是指向整数的指针(int类型的指针),也可以是指向字符的指针(char类型的指针)等。
指针的好处
①提高程序的编译效率和执行速度:指针可以直接访问内存地址,这使得程序能够更快速、更高效地执行。
②实现数据共享和双向数据通讯:通过指针,主调函数和被调函数之间可以共享变量或数据结构,这有助于实现数据的双向通讯。
③动态分配内存:指针可以动态地分配和释放内存,这对于处理大型数据集或动态数据结构非常有用。
④表示复杂的数据结构:指针可以有效地表示复杂的数据结构,如链表、树、图等。
指针的使用
1.指针与变量
指针可以用来存储变量的地址,并通过这个地址访问和修改变量的值。
int x = 10;
int *p = &x; // p指向x的地址 // 通过指针访问变量的值
printf("%d\n", *p); // 输出:10 // 通过指针修改变量的值
*p = 20;
printf("%d\n", x); // 输出:20,因为x的值已经被p通过解引用修改
2. 指针与数组
指针可以用来遍历数组元素,因为数组名本质上是一个指向数组第一个元素的常量指针。
int arr[] = {1, 2, 3, 4, 5};
int *p = arr; // p指向数组arr的第一个元素 // 遍历数组
for (int i = 0; i < 5; i++) { printf("%d ", *(p + i)); // 或者 printf("%d ", arr[i]);
}
3.指针的算数运算
可以对指针进行四种算术运算:++、-、+、-。
指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度。比如int就是4个字节,因此,int 类型的指针递增和递减一次,地址变化4个字节。
人们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增。
4.指向指针的指针(二级指针)
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的地址。
当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符。
5.指针与函数
指针可以作为函数的参数,用来传递变量的地址,从而实现数据的共享和修改。
void increment(int *ptr) { (*ptr)++; // 通过解引用修改指针指向的值
} int main() { int x = 1; increment(&x); // 传递x的地址 printf("%d\n", x); // 输出:2 return 0;
}
6.从函数返回指针(指针函数)
C允许函数返回指针到局部变量、静态变量和动态内存分配。
想要从函数返回指针,必须声明一个返回指针的函数。
要注意,C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量。类似于从函数返回数组。
#include <stdio.h> // 声明一个指针函数,返回值为int型指针
int* getPointerToInt() { static int value = 42; // 使用static确保变量在函数外部仍然存在 return &value; // 返回变量的地址
} int main() { int *p = getPointerToInt(); // 调用指针函数,获取地址 if (p != NULL) { printf("The value is: %d\n", *p); // 通过指针访问值 } else { printf("Null pointer received.\n"); } return 0;
}
7.函数指针
函数指针在C/C++中是一个指向函数的指针变量。函数指针变量存储的是函数的内存地址,通过这个地址可以间接调用函数。函数指针通常用于回调函数。
#include <stdio.h> // 声明一个函数类型,该函数接收两个int参数并返回一个int值
typedef int (*FuncPtrType)(int, int); // 定义一个符合FuncPtrType类型的函数
int add(int a, int b) { return a + b;
} // 定义一个符合FuncPtrType类型的函数
int subtract(int a, int b) { return a - b;
} // 一个使用函数指针的函数,它接收一个函数指针作为参数
void performOperation(FuncPtrType operation, int x, int y) { int result = operation(x, y); // 通过函数指针调用函数 printf("Result: %d\n", result);
} int main() { // 声明并初始化函数指针 FuncPtrType ptrAdd = add; FuncPtrType ptrSubtract = subtract; // 使用函数指针调用函数 performOperation(ptrAdd, 5, 3); // 输出:Result: 8 performOperation(ptrSubtract, 5, 3); // 输出:Result: 2 return 0;
}
内存布局
C程序的内存分布如下图
1.文本段(Text Segment)
文本段,也称为代码段,它存储了程序的二进制代码。这部分内存是只读的,防止程序意外地修改其自身的指令。
2.数据段(Data Segment)
.data段:存储了程序中已初始化的全局变量和静态变量。在C语言中,如果全局变量或静态变量被显式地初始化为非零值,那么它们就位于数据段。
.bss(常量区):存储了程序中未初始化的全局变量和静态变量。操作系统通常会在程序开始执行前将这部分内存清零。
.rodata段:存储了 const , #define , char *ptr= "string" 等定义的数据常量。
3.堆(Heap)
堆是用于动态内存分配的区域。当程序使用malloc、calloc或realloc等函数时,就会从堆中分配内存。(由自己管理,用完后,要记得 free ,否则会造成内存泄露)
4.栈(Stack)
临时创建的局部变量和const定义的局部变量存放在栈区。此外,函数调用和返回时,其入口参数和返回值存放在栈区。
栈区由编译器自动分配释放,由操作系统自动管理,无须手动管理。
栈区上的内容只在函数范围内存在,当函数运行结束,这些内容也会自动被销毁。
栈区是先进后出原则,即先进去的被堵在屋里的最里面,后进去的在门口,释放的时候门口的先出去。
5.参数区
参数区存放了命令行中传递到main函数的参数。
6.系统空间
程序运行所需要的空间。
堆和栈的区别
分配方式不同:栈由操作系统自动分配和释放,无需程序员手动控制。而堆的申请和释放工作则由程序员控制,这可能导致内存泄漏的问题。
空间大小不同:栈空间通常较小,而堆空间则相对较大。理论上,程序员可申请的堆大小可以达到虚拟内存的大小。
操作速度不同:栈操作通常非常快速,因为分配和释放栈内存只涉及栈指针的移动。而堆操作可能较慢,因为分配和释放堆内存涉及复杂的内存管理。
存储对象类型不同:栈主要用于存储函数调用时的相关参数(不包括静态变量)以及函数调用语句的下一条可执行语句的地址。而堆的头部通常用一个字节来存储堆的大小,堆中的具体内容则由程序员安排。
数据的生命周期不同:栈上的数据只在包含它的函数的执行期间有效。一旦函数返回,栈上的数据被销毁。而堆上的数据可以在程序的不同部分之间共享,并可以持续存在,直到开发者明确释放它们。
碎片化程度不同:栈通常不会发生碎片问题,因为栈上的分配和释放都是有序的。堆内存管理可能导致碎片问题,因为分配和释放是动态的,可能在堆中留下未分配的小块内存,这些小块内存难以重用。
链表
什么是链表
链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序实现的一种线性存储结构。
链表由一系列节点(链表中每一个元素称为节点)组成,节点在运行时动态生成,每个节点包括两个部分:数据域、指针域。
数据域存储数据元素,而指针域存储下一个节点地址。
链表有多种类型,包括单向链表、双向链表、循环链表等。
单向链表:每个节点只有一个指针,指向下一个节点。
双向链表:每个节点有两个指针,一个指向前一个节点,另一个指向下一个节点。双向链表允许从任意节点向前或向后遍历链表。
循环链表:尾节点的指针指向头节点,形成一个闭环。循环链表允许从尾节点开始继续遍历链表。
链表与数组的区别
数组和链表是两种不同的数据存储方式,它们有着各自的特点和适用场景。
存储形式:
数组是一块连续的空间,声明时就要确定长度,且长度固定,不能动态调整。而链表则是一块可不连续的动态空间,长度可变,每个结点要保存相邻结点指针,通过指针链接起来形成链表。因此,链表在存储上更加灵活,可以动态调整大小,而数组则需要预先分配足够的内存空间。
数据查找:
数组的查找速度快,因为数组元素在内存中是顺序存储的,可以直接通过下标访问,查找操作直接使用偏移地址。而链表需要按顺序检索结点,效率低,因为链表中的元素存储不是连续的,需要通过指针域进行跳转来访问特定元素。所以,对于需要频繁查找数据的场景,数组更为合适。
数据插入或删除:
链表在插入和删除节点时具有优势,因为不需要移动其他元素,只需要修改相邻节点的指针即可。这使得链表在需要频繁进行插入和删除操作的场景中表现良好。而数组在插入和删除元素时可能需要移动大量数据以保持连续性,因此效率较低。因此,如果需要频繁进行数据的插入和删除操作,链表是更好的选择。
越界问题:
链表不存在越界问题,因为链表是由节点和指针构成的,访问链表时只需要遍历节点即可,不需要通过下标访问,因此不存在下标越界的情况。而数组存在越界问题,如果访问超出数组长度的下标,会导致程序崩溃或数据错误。所以,从数据安全性角度来看,链表相对更安全一些。
总的来说,如果需要频繁查找数据且数据规模不大,数组可能更适合;如果需要频繁进行插入和删除操作或数据规模动态变化,链表可能更合适。
链表的好处
动态数据结构:链表是一种动态数据结构,它可以在运行时动态地增加或删除节点,而不需要事先知道数据的大小和数量。这样可以节省空间,避免预分配过多的内存或者数组越界的问题。链表的长度也不受限制,只要有足够的内存空间就可以继续扩展。
内存利用率高:与数组相比,链表更加灵活地利用内存空间。数组需要一块连续的内存空间来存储元素,如果数组长度较大或者频繁扩容,可能会导致内存碎片或者移动大量数据。而链表则可以利用零散的内存空间来存储节点,只要有可用的空间就可以分配给新节点,并通过指针连接起来。这样可以减少内存的浪费和管理难度。
易于插入和删除:由于链表中每个节点都有一个指向下一个节点的指针域(双向链表还有一个指向上一个节点的指针域),所以在已知某个节点位置后,在其前后插入或删除节点非常方便和快速。只需要修改相邻几个节点的指针域即可完成插入或删除操作,并不需要移动其他元素。这样就提高了数据操作的效率。
链表的好处在于其动态性、内存利用率高、实现简单以及易于插入和删除。然而,链表也失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销相对较大。
链表的使用
链表的创建
创建链表首先需要定义链表的数据结构,通常使用一个结构体来表示链表中的节点,该结构体包含节点的数据部分和指向下一个节点的指针部分。然后,可以定义一个头指针来指向链表的第一个节点。创建链表时,可以逐个创建节点并将它们连接起来,形成一个完整的链表。
#include <stdio.h>
#include <stdlib.h>
//定义结点结构体
typedef struct student
{//数据域int num; //学号int score; //分数char name[20]; //姓名//指针域struct student *next;
}STU;void link_creat_head(STU **p_head,STU *p_new)
{STU *p_mov = *p_head;if(*p_head == NULL) //当第一次加入链表为空时,head执行p_new{*p_head = p_new;p_new->next=NULL;}else //第二次及以后加入链表{while(p_mov->next!=NULL){p_mov=p_mov->next; //找到原有链表的最后一个节点}p_mov->next = p_new; //将新申请的节点加入链表p_new->next = NULL;}
}int main()
{STU *head = NULL,*p_new = NULL;int num,i;printf("请输入链表初始个数:\n");scanf("%d",&num);for(i = 0; i < num;i++){p_new = (STU*)malloc(sizeof(STU));//申请一个新节点printf("请输入学号、分数、名字:\n"); //给新节点赋值scanf("%d %d %s",&p_new->num,&p_new->score,p_new->name);link_creat_head(&head,p_new); //将新节点加入链表}
}
遍历链表:
遍历链表是指按照链表的顺序依次访问每个节点,并对节点进行相应的操作。
第一步:输出第一个节点的数据域,输出完毕后,让指针保存后一个节点的地址;
第二步:输出移动地址对应的节点的数据域,输出完毕后,指针继续后移 ;
第三步:以此类推,直到节点的指针域为NULL。
//链表的遍历
void link_print(STU *head)
{STU *p_mov;//定义新的指针保存链表的首地址,防止使用head改变原本链表p_mov = head;//当指针保存最后一个结点的指针域为NULL时,循环结束while(p_mov!=NULL){//先打印当前指针保存结点的指针域printf("num=%d score=%d name:%s\n",p_mov->num,\p_mov->score,p_mov->name);//指针后移,保存下一个结点的地址p_mov = p_mov->next;}
}
插入节点:
①创建新节点:首先,需要创建一个新的节点,这个节点将包含要插入的数据。
②找到插入位置:接下来,需要找到要插入新节点的位置。这可以通过遍历链表来实现,直到找到适当的位置。
③调整指针:一旦找到插入位置,就需要调整相邻节点的指针,以便将新节点插入到链表中。具体来说,需要将新节点的指针指向当前节点的下一个节点,然后将当前节点的指针指向新节点。
④更新链表头或尾(不一定需要):如果新节点被插入到链表的头部或尾部,还需要更新链表的头指针或尾指针。
需要注意的是,插入节点时要考虑链表为空的情况,以及插入位置为链表头部和尾部的情况。此外,还需要注意指针的正确性和空指针的处理,以避免出现程序崩溃或数据错误等问题。
删除节点:
如果链表为空,不需要删除 如果删除的是第一个结点,则需要将保存链表首地址的指针保存第一个结点的下一个结点的 地址 如果删除的是中间结点,则找到中间结点的前一个结点,让前一个结点的指针域保存这个结 点的后一个结点的地址即可
队列
什么是队列
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(FIFO)的线性表。允许插入的一端称为队尾,允许删除的一端称为队头。
队头(Front):允许删除的一端,又称队首。
队尾(Rear):允许插入的一端。
空队列:不包含任何元素的空表。
队列的特点
①先进先出(FIFO)原则:这是队列最基本也是最重要的特点。在队列中,元素按照它们被添加的顺序进行排列,最先被添加的元素将是最先被移除的。这种原则保证了队列操作的有序性。
②只能在队尾添加元素,队头删除元素:队列只允许在一端(称为队尾)添加元素,而在另一端(称为队头)删除元素。
③具有最大容量限制:队列通常都有一个最大容量限制,当队列满时,不能再添加新元素;当队列空时,不能删除元素。
队列的存储结构
队列的顺序存储结构
队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:队头指针 front指向队头元素,队尾指针 rear 指向队尾元素的下一个位置。
在顺序存储结构中,可分为顺序队列和循环队列。
顺序队列:
初始状态(队空条件):Q->front == Q->rear == 0。
进队操作:队不满时,先送值到队尾元素,再将队尾指针加1。
出队操作:队不空时,先取队头元素值,再将队头指针加1。
然而,顺序队列有一个主要的问题,即“假溢出”。当队列满时,即使队列存储空间并未全部被占满,由于队尾指针已经达到队列的最大长度,插入操作也会失败。这是因为顺序队列进行队头出队、队尾入队,造成数组前面会出现空闲单元未被充分利用。
循环队列:
循环队列是为了解决顺序队列的“假溢出”问题而提出的。
它将顺序队列的数组看成一个头尾相接的循环结构,即队尾指针指向队头指针时,认为队列为空;队尾指针指向队头指针的下一个位置时,认为队列为满。
因此,循环队列可以更好地利用存储空间。在循环队列中,头尾指针仍要进行加1操作,但当指针指向向量上界时,其加1操作的结果是指向向量的下界0。这种循环意义下的加1操作可以通过取模运算来实现。
队列的链式存储结构
链队列,也称为链式队列,是用链表来实现队列的存储结构。链队列具有队头指针和队尾指针,分别指示队列元素的头部和尾部。在链队列中,只允许在队尾进行插入操作,在队头进行删除操作,这符合队列的先进先出(FIFO)特性。
链队列入队:
链队列的入队操作是指将一个元素添加到链队列的队尾。
创建一个新的节点,并将待插入的数据元素存储在该节点中。
将新节点的指针部分指向原来的队尾节点的下一个节点,即 NULL。
修改原来的队尾节点的指针部分,使其指向新节点。
将队尾指针移动到新节点,即指向新节点。
链队列出队:
出队操作时,就是头结点的后继结点出队,将头结点的后继改为它后面的结点,若链表除头结点外只剩一个元素时,则需将rear指向头结点。
双端队列
定义
双端队列是指允许两端都可以进行入队和出队操作的队列,如下图所示。其元素的逻辑结构仍是线性结构。将队列的两端分别称为前端和后端,两端都可以入队和出队。
特殊的双端队列
输出受限的双端队列:允许在一端(通常是前端)进行插入和删除, 但在另一端(通常是后端)只允许插入的双端队列称为输出受限的双端队列。
输入受限的双端队列:允许在一端(通常是后端)进行插入和删除,但在另一端(通常是前端)只允许删除的双端队列称为输入受限的双端队列。