目录
先说明要达到的层次要求:
-
层次1(无代码)
- 熟悉各种不同的数据线性结构(一对一):顺序表(一维数组)、链表、栈、队列;
- 熟悉各种不同的数据结构(一对多):二叉树、树、深林;
- 熟悉各种不同的数据结构(多对多):图等;
- 了解上面不同的数据结构的特点、如何存储、优缺点等;
-
层次2(开始有代码)
- 如何编写相关的代码,实现不同数据结构的操作(不同数据结构对应的增、删、改、查、长度、遍历等);
-
层次3(靠自己后面积累)
- 算法层面的训练,根据实际选择合适的数据结构和代码
- 可以在leetcode网站刷题:力扣 (LeetCode) 全球极客挚爱的技术成长平台,或者牛客网等网站,300道+打底
///
数据结构的介绍
数据结构是什么?
- 定义1(宏观):数据结构是为了高效访问数据而设计出的一种数据的组织和存储方式。
- 定义2(微观):数据结构是相互之间存在一种或多种特定关系的数据元素的集合。
- 简言之,数据+结构=变量+关系,数据结构是带结构的数据元素的集合,“结构”就是指数据元素之间存在的关系。
- 所以数据结构就是研究多个数据之间的关系!
数据结构的目的
为了高效的访问或对数据进行操作。关注的是对这些数据元素的操作和数据元素之间的关系,不关心具体的数据项内容。
数据结构和编程语言的关系
- 数据结构本身和编程语言是无关的。
- 二者关系类似于数学定理和描述它的自然语言一样,你可以用中文表达勾股定理,也可以用英文,法文或者世界上任何一种语言表达它,但是世上只有一个勾股定理。但如果任何一种语言你都不会,那就无法学好数据结构。还类似于房屋设计图纸和实现它所需要的水泥或木材一样。
内存存储的理解
内存的存储:一般而言,数据结构针对的是内存中的数据。所以在学习数据结构之前,需要先对内存有一个简单的了解。
- 内存由许多存储单元组成,每个存储单元可以存储一个固定大小的数据块,通常以字节( Byte,一个Byte=8bit位)为单位。每个存储单元都有一个唯一的地址,操作系统正是根据这一地址去访问内存储结构存中的数据的。
- 我们讨论的数据结构中的数据元素就保存在这些一个个的内存单元中,这些数据元素或存储在物理层面上连续的内存单元中,或存储在物理层面上分散的内存单元中。
- 内存中只有连续、分散这两种存储形式,其他形式都是衍生出去的
数据结构的内容(方向):
- 数据的逻辑结构
- 数据的存储结构
- 数据的运算结构
数据的逻辑结构
分类角度1:逻辑结构有四种基本类型:线性结构、集合结构、树形结构、图形结构(或网状结构)
- 集合结构:
- 数据结构中的元素之间除了“同属一个集合”的相互关系外,别无其他关系。
- 这是数据之间最弱的一种关系。
- 线性关系:
- 结构中的元素存在一对一的相互关系。
- 所以数据元素按照顺序排序,强调数据元素的前后顺序。
- 结构中必须存在唯一的首元素和唯一的尾元素。
- 比如排队。
- 树形结构:
- 结构中的元素存在一对多的相互关系
- 所以数据元素按照层次结构进行组织,强调元素之间的父子关系
- 比如:家谱、文件系统、思维导图、组织架构、
- 图形结构:
-
- 结构中的元素存在多对多的相互关系
- 由顶点和边组成,顶点对应数据元素,边连接两个顶点,表示两个数据元素之间的关系。
- 比如:全国铁路、地铁图、社交网络、
分类角度2:因为线性结构比较突出,所以,我们又习惯上将逻辑结构分为:线性结构和非线性结构
- 线性结构:包括线性表(顺序表、链表)、栈(先进后出)、队列(先进先出)、字符串、数组、广义表等
- 非线性结构:各个数据元素不再保持在一个线性序列中。比如集合结构、树结构、图结构等。
小结:
- 数据的逻辑结构指反映数据元素之间的逻辑关系﹐是独立于计算机的,与数据的存储无关(即与数据元素本身的形式、内容无关,与数据元素的相对位置无关,与所含结点个数无关)。(因为还没涉及到代码,涉及到代码进行分配内存时,才与数据的存储有关)比如,栈属于线性结构,是一种逻辑结构,可以采用顺序存储,也可以采用链式存储。
数据的存储结构
- 数据的存储结构又称为物理结构,是数据的逻辑结构在计算机中的表示(又称映像)。
- 它包括数据元素的表示和关系的表示(即逻辑关系)。
- 数据的存储结构是逻辑结构用计算机语言的实现,它依赖于计算机语言。
两种基本的存储方式:
-
顺序存储结构:
- 把逻辑相邻的数据元素存储在物理位置也相邻的存储单元中,即所有的元素依次存放在一片连续的存储空间中。
- 简单就是:逻辑上连续,物理存储位置也连续
- 优点:数据元素存放的地址是连续的,支持下标访问,方便存或取表中元素
- 在c语言中,用一维数组表示顺序存储结构:
-
链式存储结构:
- 把逻辑相邻的数据元素存储在物理位置不相邻的存储单元中
- 为每个数据元素构造一个结点(用结构体类型表示),结点中除了存放数据本身以外,还存放指向下一个结点的指针
- 简单就是:逻辑上连续,物理存储位置不连续
- 优点:
- 克服顺序存储结构中预知元素个数的缺点(比如数组的元素个数就是定好的,元素个数是已预知了的)
- 插入﹑删除灵活(不必移动节点,只要改变节点中的指针指向位置,而数组需要一个个往前或往后移)
- 缺点:占用内存空间大。需要额外的空间来表达数据之间的逻辑关系(每个节点都由数据域和指针域组成,所以相同内存,空间内全存满的话,顺序比链式存储更多数据)。
- 在c语言中,用指针表示链式存储结构(图中next为指针):
由上,可以引申两类存储结构(都具备线性、链式结构):
- 索引存储结构
- 把索引想象成书的目录,有了目录,我们找书的相关内容就十分简单。索引存储除了存储数据元素之外,还同时建立数据元素的目录,便于快速检索
- 优点:用节点的索引号来确定节点存储地址,检索速度快
- 缺点:增加了附加的索引表,会占用较多的存储空间,在增加和删除数据时要修改索引表,因而会花费较多的时间
-
- 比如MySQL数据库的索引的设计方案:
- 散列存储结构
-
- 根据数据元素的关键字直接计算出该元素的关键码,由关键码决定其存储地址,又称为Hash存储。
- 在初中,我们就学过一种能够将一个x值通过一个函数获得对应的一个y值的操作,叫做映射。散列表的实现原理正是映射的原理。
- 在映射的过程中,事先设定的函数称作散列函数或者哈希函数。
- 优点:检索、增加或删除结点的操作都很快。
- 缺点:不支持排序,一般比用线性表存储需要更多的空间,并且记录的关键字不能重复。
- 在设计算法的过程中,尽量不要出现链式结构
复习与启下
要达到的层次要求:
- 层次1(无代码)
- 熟悉各种不同的数据线性结构(一对一):顺序表(一维数组)、链表、栈、队列;
- 熟悉各种不同的数据结构(一对多):二叉树、树、深林;
- 熟悉各种不同的数据结构(多对多):图等;
- 了解上面不同的数据结构的特点、如何存储、优缺点等;
- 层次2(开始有代码)
- 如何编写相关的代码,实现不同数据结构的操作(不同数据结构对应的增、删、改、查、长度、遍历等);
- 层次3(靠自己后面积累)
- 算法层面的训练,根据实际选择合适的数据结构和代码
- 可以在leetcode网站刷题:力扣 (LeetCode) 全球极客挚爱的技术成长平台,或者牛客网等网站,300道+打底
总结:
针对于层次1
1.什么是数据结构? datastructure(D->s)
- 数据+结构
- 数据:多个相同类型的数据或变量
- 结构:即关系
- 目的:为了更高效的访问数据
2.数据结构中有哪些内容?即问研究方向?
- 研究方向1:数据之间的逻辑关系
-
- 线性关系:(一对一的关系)。比如:顺序表、链表、栈、队列、数组、字符串、广义表等〉
- 非线性关系:集合关系、树形关系(一对多的关系)、图形关系(多对多的关系)
- 研究方向2:数据的存储结构(或物理结构)
-
- 基本的两种:顺序存储结构、链式存储结构
- 拓展的两种:索引存储结构、哈希存储结构(散列存储结构)研究方向3:数据之间的运算:增、删、改、查(CRUD)
三、具体的不同的数据结构的实现(对应着层次2)|
- 顺序表(数组)的实现和相关算法的封装
- 链表的实现和相关算法的封装
两者优缺点对比:彼此优缺点相反
类型 | 优点 | 缺点 |
数组 |
|
|
链表 |
|
|
线性结构-线性表
介绍
顺序表(数组)
- 数组是具有“相同类型”的数据元素的集合。在所有的数据结构之中,数组是最常见、最简单的
- 数组需要先声明才能使用,数组的长度一旦确定就不可在变。比如: int array[5];
- 数组的下标是从0开始的,比如上面数组的第一个元素为array[0],最后一个元素为array[4]
数组优缺点:
- 优点
- 查找容易(通过下标),时间复杂度为O(1),不需要额外申请或删除空间
- 使用下标位置索引十分高效的访问任意元素、修改快、
- 缺点
- 插入、删除元素难、效率低(需要移动大量元素以使元素空间连续)
- 拓展相对繁琐,一方面要确保能提供更大区域的连续内存空间,另一方面需要将原有数据复制到新的顺序表中。
- 插入元素平均需要移动n/2个元素
- 删除元素平均需要移动(n-1)/2个元素
- n取数组元素索引最大值
顺序表(数组)代码功能实现:
- 实现一个增强版数组:可变长度的动态数组
//先总结:一共有以下函数:
//初始化 动态数组
void initDynamicArray(DynamicArray *array,size_t initialCapacity)
//释放 动态数组 内存
void destroyDynamicArray(DynamicArray *array)
//调整 动态数组 内存大小
void resizeDynamicArray(DynamicArray *array,size_t newCapacity)
//获取 动态数组 长度(元素个数)
size_t getLength(const DynamicArray *array)
//在指定位置插入新元素
void insertAt(DynamicArray *array,size_t index,int element)
//在末尾位置插入新元素
void insertEnd(DynamicArray *array,int element)
//删除指定位置的元素并返回被删除的元素
int deleteAt(DynamicArray *array,size_t index)
//删除末尾的元素并返回被删除的元素
int deleteEnd(DynamicArray *array)
//遍历所有元素
void print(DynamicArray *array)
实现原理
- “可变长度”的动态数组是一种数据结构,它允许在运行时根据需要,动态地调整数组的大小,而不需要提前指定固定的大小。这种动态数组通常被称为动态分配数组、动态增长数组、动态内存数组、
- C语言中是通过使用指针和内存分配函数来实现动态数组的,用到的内存分配函数为:malloc、realloc、free、
- malloc(分配内存函数)
- 在C语言中,可以使用malloc函数来分配一块指定大小的内存。
- 例如,int *arr = (int *)malloc(n * sizeof(int));将分配能够存储n个整数的内存空间。
- malloc(分配内存函数)
-
- realloc(重新分配内存函数)
- 如果需要改变动态数组的大小,可以使用realloc函数来重新分配内存。
- 这允许你在保留原有数据的情况下扩展或缩小数组的大小。
- free(释放内存函数)
- 当不再需要动态数组时,应使用free函数释放之前分配的内存,以避免内存泄露。
- realloc(重新分配内存函数)
代码实现
#include <stdio.h>
#include <stdlib.h>
/*
声明动态数组,并提供相关函数操作
*/
//声明动态数组结构体
typedef struct {
int* data; //声明存储数据的数组
size_t capacity;//记录数组的容量
size_t size; //记录数组中存储的数据的长度
}DynamicArray;
//初始化 动态数组
void initDynamicArray(DynamicArray* array, size_t initialCapacity)
{
//动态创建数组的方式:
array->data = (int *)malloc(initialCapacity * sizeof(int));
//初始化capacity成员:
array->capacity = initialCapacity;
//初始化size成员:
array->size = 0;
printf("初始化数组成功\n");
}
/* 释放 动态数组 内存 */
void destroyDynamicArray(DynamicArray* array)
{
//释放数组空间
free(array->data);
//修改capacity、size为默认值
array->capacity = 0;
array->size = 0;
printf("释放内存成功\n");
}
//调整 动态数组 内存大小
void resizeDynamicArray(DynamicArray* array, size_t newCapacity)
{
//修改data数组,进行扩容
array->data = (int *)realloc(array->data, newCapacity * sizeof(int));
//修改capacity
array->capacity = newCapacity;
}
//获取 动态数组 长度(元素个数)
size_t getLength(const DynamicArray* array)
{
return array->size;
}
//在指定位置插入新元素
void insertAt(DynamicArray* array, size_t index, int element)
{
//不合法的情况:不在索引范围内
if (index < 0 || index > array->size)
{
printf("index范围不合法,插入失败\n");
return;
}
//不合法的情况:内存不足
if (array->size == array->capacity)
{
resizeDynamicArray(array,array->capacity << 1 ); //capacity<<1或 *2,扩大2倍
printf("内存不足,扩容成功\n");
}
//将index后续的元素依次后移
for( int i = array->size ; i > index ; i-- )//索引从0开始,所以size -1为最后一个索引
{
array->data[i] = array->data[i - 1];
}
//修改index位置的元素数据值
array->data[index] = element;
//修改长度
array->size++;
}
//在末尾位置插入新元素
void insertEnd(DynamicArray* array, int element)
{
insertAt(array, array->size, element);
}
//删除指定位置的元素并返回被删除的元素
int deleteAt(DynamicArray* array, size_t index)
{
//不合法的情况:不在索引范围内
if (index < 0 || index >= array->size)
{
printf("index范围不合法,删除失败\n");
return -1;
}
//获取要删除位置的元素的数据值
int deleteData = array->data[index];
//将index后续的元素依次前移
for( int i = index; i< array->size-1 ; i++ )
{
array->data[i] = array->data[i + 1];
}
//修改长度
array->size--;
//返回被删除的元素
return deleteData;
}
//删除末尾的元素并返回被删除的元素
int deleteEnd(DynamicArray* array)
{
return deleteAt(array,array->size-1);
}
//遍历所有元素
void print(DynamicArray* array)
{
for (int i=0; i < array->size ; i++ )
{
printf("%d\n",array->data[i]);
}
}
//
int main() //测试以上函数的效果
{
DynamicArray array;
initDynamicArray(&array,2);//测试初始化
insertAt(&array,0,10);//测试指定位置插入元素
insertAt(&array, 0, 20);//在0位置插入20,上面的10索引变1
insertAt(&array, 0, 30); //在0位置插入30,上面的20索引变1,上面的10索引变2
deleteAt(&array, 0);//测试删除指定元素。删除0位置的元素:30的元素被删,20的索引变0,10的索引变1
print(&array);//测试遍历数组
return 0;
}
线性结构-链表
链表介绍
- 逻辑结构
- 链表是一个线性结构,由一系列“结点”组成,每个结点包含一个数据元素和一个指向下一个结点的指针。
- 所有结点通过指针相连,形成一个链式结构。通常我们将链表中的第一个结点称为“头结点”
-
- data:数据域,存放结点的值
- next:指针域,存放结点的直接后继的地址
- 物理结构
- 与数组不同,链表中的结点需要自行组织,向系统申请很多分散在内存各处的结点。
- 每个结点都保存了当前节点 的数据和下一个结点的地址(指针),通过指针将结点串成一串。
- 链表又可分为:单向链表、双向链表、循环单链表、循环双链表、静态链表(少见)
- 头结点:
- 可跟后面的结点数据类型一样,也可以不一样。
- 作用:让对每一个元素位置的操作都可以通用起来。比如插入元素,如果没有头结点,那么就无法在第一个结点前插入元素。有了头结点,就可以插入了
代码实现
//先总结,链表的函数:
//初始化链表
void initLinkedList(LinkedList *list)
//返回链表的长度
size_t getLength(const LinkedList *list)
//在指定位置插入元素
void insertAt(LinkedList *list,size_t index,int element)
//在末尾插入元素
void insertEnd(LinkedList *list,int element)
//删除指定位置的元素并返回被删除的元素
int deleteAt(LinkedList *list,size_t index)
//删除末尾元素
int deleteEnd(LinkedList *list)
//获取指定位置的元素
int getElementAt(const LinkedList *list,size_t index)
//修改指定位置的元素
void modifyAt(LinkedList *list,size_t index,int newValue)
//释放链表内存
void destroyLinkedList(LinkedList *list)
完整代码:
#include<stdio.h>
#include<stdlib.h>
/*
自定义链表函数,并提供操作函数
*/
//定义结点 //node翻译:结点
typedef struct Node{
int data; //存放数据
struct Node* next; //指向下个元素指针
}Node;
//定义虚拟头结点
typedef struct{
int size; //记录单链表中存储的数据的个数
Node* next; //指向保存数据的首元素
}LinkedList;
//明确:在包含虚拟头结点的情况下,首个保存数据的结点的索引为0 !
//初始化链表
void initLinkedList(LinkedList* list)
{
//初始化LinkedList结构体内部成员
list->size = 0;
list->next = NULL;
}
//返回链表的长度
size_t getLength(const LinkedList* list)
{
return list->size;
}
//在指定位置插入元素
void insertAt(LinkedList* list, size_t index, int element)
{
//非法情况
if (index < 0 || index > list->size)
{
printf("输入的index不在范围内,非法!\n");
}
//插入数据
//将数据封装到Node结构体的变量中
Node* node = (Node *)malloc(1 * sizeof(Node));
node->data = element;
//找到index的位置进行插入操作
if (index == 0)//如果是首个元素
{
node->next = list->next;
list->next = node;
list->size++;
return;
}
else if (index < list->size)//如果是首尾之间的元素
{
Node* currentNode = list->next; //指向有数据的首元素
for ( int i = 0 ;i<index-1;i++)
{
currentNode = currentNode->next;
}
node->next = currentNode->next;
currentNode->next = node;
list->size++;
return;
}
}
//在末尾插入元素
void insertEnd(LinkedList* list, int element)
{
insertAt(list,list->size,element);
}
//删除指定位置的元素并返回被删除的元素
int deleteAt(LinkedList* list, size_t index)
{
//非法情况
if (index < 0 || index > list->size)
{
printf("要删除的index不在范围内,非法!\n");
return -1;
}
if (index == 0)
{
Node* deleteNode = list->next;
list->next = deleteNode->next;
//获取要删除的index的数据
int deleteElement = deleteNode->data;
free(deleteNode);//释放Node的内存
list->size--;
return deleteElement;
}
else
{
Node* currentNode = list->next;//指向有数据的首元素
for (int i = 0; i < index -1; i++)
{
currentNode = currentNode->next;
}
Node* deleteNode = currentNode->next;
currentNode->next = deleteNode->next;
//获取要删除的index的数据
int deleteElement = deleteNode->data;
free(deleteNode);//释放Node的内存
list->size--;
return deleteElement;
}
}
//删除末尾元素
int deleteEnd(LinkedList* list)
{
return deleteAt(list, list->size - 1);
}
//获取指定位置的元素
int getElementAt(const LinkedList* list, size_t index)
{
if (index < 0 || index >= list->size)
{
printf("输入的index不合法,不在范围内\n");
return -1;
}
Node* currentNode = list->next;
for (int i = 0; i < index; i++)
{
currentNode = currentNode->next;
}
return currentNode->data;
}
//修改指定位置的元素
void modifyAt(LinkedList* list, size_t index, int newValue)
{
if (index < 0 || index >= list->size)
{
printf("输入的index不合法,不在范围内\n");
}
Node* currentNode = list->next;
for (int i = 0; i < index; i++)
{
currentNode = currentNode->next;
}
currentNode->data = newValue;
}
//释放链表内存
void destroyLinkedList(LinkedList* list)
{
Node* currentNode = list->next;
for (int i = 0; i < list->size; i++)
{
Node* tempNode = currentNode;
currentNode = currentNode->next;
free(currentNode);
}
list->next = NULL;
list->size = 0;
}
int main() //测试
{
LinkedList list;
initLinkedList(&list);
insertAt(&list,0,10);
insertAt(&list, 0, 20);
insertAt(&list, 0, 30);
insertAt(&list, 0, 40);
size_t count = getLength(&list);
printf("%d \n", count);
return 0;
}
线性结构-栈
栈的介绍
栈的定义
- 栈(stack),是限制在只能在表的一端进行插入和删除操作的线性表。
- 应用范围广泛,生活中的例子,比如堆叠的盘子、报纸、电梯中的人们、邮局的邮筒、
- 特点:后进先出(LIFO)、或先进后出(FILO)的线性表。
相关概念
- 栈顶(Top)
- 允许进行插入、删除操作的一端,又称为“表尾”。
- 栈顶由一个称为栈顶指针的位置指示器(其实就是一个变量)来指示,它是动态变化的
- 栈底(Botton)
- 是固定不变的,不允许进行插入和删除的一端,又称为“表头”。
- 空栈
- 不含任何元素的空表
- 设栈S=(a1,a2,a3,...an),则a1称为栈底元素,an称为栈顶元素,
- 栈中元素按a1,a2,a3,....an的次序进栈(或叫压栈、push)
- 出栈(弹栈、pop)的第一个元素被应为栈顶元素,出栈顺序为:an,an-1,...a3,a2,a1
栈顶的两种位置情况(2种都正确)
- 情况1:初始情况top=-1
-
- 进栈:先top++,再赋值
- 空栈时,top=-1,bottom为0,top在栈底下面
- 当第一个元素A要进栈时,先top++,为top=0,再把元素A进栈,进到0位置。此时这个top指针指向元素A的位置空间
- 当第二个元素B要进栈时,先top++,top=1,再让元素B进栈,进到1位置。此时top指针指向元素B的位置空间
- 当后面C,D依次进栈后,top自加2次,最终为top=3,这个top指针指向元素D
- 即最后一个元素进栈后,top指的位置,永远为最后一个元素的当前位置
- 出栈:先删除元素,再top--
- 假设尾元素D要出站,元素D先出栈,然后top--,以让top指向下一个将成为最后一个元素的位置
- 进栈:先top++,再赋值
- 情况2:初始情况top = 0
-
- 进栈:先赋值,再top++
- 空栈时,top=0,bottom=0
- 当第一个元素A要进栈时,直接把元素A的值赋到0位置,之后再top++
- 即最后一个元素进栈后,top指的位置,永远为最后一个元素的下一位(空位)
- 出栈:先top--,再删除元素
- 假设尾元素D要出站,先top--,以让top位置指向元素D的位置,然后再让元素D出栈,
- 进栈:先赋值,再top++
栈的使用举例
- 代码中的函数调用:不管何种语言,最先被调用的函数一定最后返回
栈的存储结构:可用顺序表和链表来存储栈,栈可以依照存储结构分为两种:顺序栈、链式栈
- 顺序栈:与顺序表的函数区别,就是无法指定位置增、删某元素,只能对尾部操作
- 链式栈
- 上指下:头差法
- 下指上:尾差法
- 下边代码用的是顺序栈
代码实现
#include<stdio.h>
#include<stdlib.h>
/*
自定义栈函数,使用顺序(数组)存储结构实现:顺序栈
*/
typedef struct {
int* data; //存储数据的指针
size_t capacity;//指明存储容器的大小
size_t size; //指明存储容器中实际存储的数据量
}Stack;
//初始化栈
void initStack(Stack* stack, size_t capacity)
{
stack->data = (int *)malloc(capacity * sizeof(int));
stack->capacity = capacity;
stack->size = 0;
}
//返回栈内元素个数
size_t getSize(const Stack *stack)
{
return stack->size;
}
//扩容
void resizeCapacity(Stack* stack, int newCapacity)
{
stack->data = (int *)realloc(stack->data, newCapacity * sizeof(int));
stack->capacity = newCapacity;
}
//添加新元素
void push(Stack *stack,int element)
{
//判断是否容量已满
if (stack->size == stack->capacity)
{
//扩容
resizeCapacity(stack,stack->capacity + stack->capacity >> 1); //扩容为原来的1.5倍
printf("容量已满,现已扩容成功\n");
}
stack->data[stack->size] = element;
stack->size++;
}
//栈顶元素出栈并返回
int pop(Stack *stack)
{
//判断是否为空栈
if (stack->size == 0)
{
printf("出栈失败,当前栈为空栈");
return -1;
}
第一种写法
//int popElement = stack->data[stack->size-1];
//stack->size--;
//return popElement;
//第二种写法
return stack->data[--stack->size];
}
//释放栈内存
void destroyStack(Stack *stack)
{
free(stack->data);
stack->data = NULL;
stack->capacity = 0;
stack->size = 0;
printf("释放栈内存成功\n");
}
//遍历栈中的元素
void print(Stack* stack)
{
for (int i = 0; i < stack->size; i++)
{
printf("%d \n", stack->data[i]);
}
}
int main() //测试
{
Stack myStack;
initStack(&myStack,3);
push(&myStack, 1);
push(&myStack, 2);
push(&myStack, 3);
push(&myStack, 4);
printf("栈中的元素个数为:%d\n",getSize(&myStack));
print(&myStack);
printf("出栈,弹出的数据为:%d\n", pop(&myStack));
return 0;
}
线性结构-队列
队列的介绍
队列(Queue):
- 和栈一样,也是操作被受限的线性表
- 限制为仅允许在表的一端进行插入(入队、进队),而在表的另一端进行删除(出队、离队)操作
- 队首(front):允许进行删除的一端
- 队尾(rear):允许进行插入的一端
- 队列,是一种先进先出(FIFO)的线性结构。
- 比如生活中的排队
- 在空队列中依次加入元素a1,a2,a3,...an之后,a1是队首元素,an是队尾元素。显然退出队列的次序也只能是a1,a2,a3,...an
- 空队列:队列中没有元素
队列的存储结构:可用顺序表和链表来存储队列,队列可以依照存储结构分为两种:顺序队列、链式队列
- 顺序(数组)队列:
- 链式队列:
- 首、尾两个指针分别指向头结点、尾结点、
- 不会出现内存满的情况
- 下面的代码使用顺序(数组)表构造成的循环队列来实现
顺序队列:
用顺序队列会出现的问题:
- 顺序队列存在“假溢出”现象:尽管队列中实际元素个数小于数组大小,但可能由于尾指针rear已超出向量空间的上界而不能做 入队 操作
解决“假溢出”办法
- 方法1:使用数组实现,入队列时添加到队列的最后,出队列时移除第一个元素同时将右侧的元素全部依次左移
- 方法2:为队列分配的向量空间看成是一个首尾相接的圆环(实际上是上图那样的),这种队列称为循环队列,如下图示
代码实现
#include<stdio.h>
#include<stdlib.h>
/*
自定义函数实现队列,使用顺序队列的循环队列
*/
typedef struct {
int* data; //存储数据的指针
int capacity;//记录存储容器的最大容量
int size; //记录存储容器中实际存储的元素个数
int front; //记录要出队的索引位置
int rear; //记录入队后的索引位置
}Queue;
//初始化队列
void initQueue(Queue* queue,size_t capacity)
{
queue->data = (int *)malloc(capacity * sizeof(int));
queue->capacity = capacity;
queue->size = 0;
queue->front = 0;
queue->rear = 0;
}
//返回队列内的元素个数
size_t getSize(const Queue* queue)
{
return queue->size;
}
//添加新元素
void enqueue(Queue* queue, int element)
{
//容量满的情况
if (queue->size == queue->capacity)
{
printf("队列容量已满,入队失败\n");
return;
}
queue->data[queue->rear] = element;
queue->size++;
queue->rear = (queue->rear + 1) % queue->capacity; //rear加一,忘右移。取模,大于最大容量时,rear将回到首位置
}
//元素出列队
int dequeue(Queue* queue)
{
//没有元素的情况
//if(queue->front == queue->rear ){} //该语句满足的情况有:1.队列为空、2.队列已满。故不能用该语句
if (queue->size == 0)
{
printf("队列为空,出队失败\n");
return -1;
}
int dequeueData = queue->data[queue->front];
queue->size--;
queue->front = (queue->front + 1) % queue->capacity; //front加一,忘右移。取模,大于最大容量时,front将回到首位置
return dequeueData;
}
//释放队列内存
void destroyQueue(Queue *queue)
{
free(queue->data);
queue->data = NULL;
queue->capacity = 0;
queue->size = 0;
queue->front = 0;
queue->rear = 0;
printf("释放内存成功\n");
}
//遍历队列
void printQueue(Queue *queue)
{
for (int i = queue->front,j = 0 ; j < queue->size; i++,j++)
{
printf(" 遍历的队列元素: %d \n",queue->data[i % queue->capacity]);
}
}
int main() //测试
{
Queue myQueue;
initQueue(&myQueue,3);
enqueue(&myQueue, 1);
enqueue(&myQueue, 2);
enqueue(&myQueue, 3);
enqueue(&myQueue, 4);
printf(" 出队的元素是:%d \n",dequeue(&myQueue));
printQueue(&myQueue);
return 0;
}