数据结构之栈,队列,树

ops/2024/12/26 14:58:35/

目录

一.栈

1.栈的概念及结构

2.栈的实现

3.实现讲解

1.初始化栈

2.销毁栈

3.压栈

4.出栈

5.返回栈顶元素

6.返回栈内元素个数

7.判断栈内是否为空

二.队列

1.队列的概念及结构

2.队列的实现

3.实现讲解

1.初始化队列

2.销毁队列

3.单个成员入队列

4.单个成员出队列

5.判断队列是否为空

6.返回队列内元素个数

7.返回队列首个元素

8.返回队列尾部元素

三.树

1.树的概念概念及结构

2.树的相关概念

3.树的实现

4.实现讲解

1.初始化树

2.销毁树

3.单个成员插入树

4.单个成员移除树

5.判断树是否为空

6.返回树中元素个数

7.返回树的顶部元素


———————————————————本文开始——————————————————

一.栈

1.栈的概念及结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。
栈中的数据元素遵守后进先出 LIFO(Last In First Out) 的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
出栈:栈的删除操作叫做出栈。出数据也在栈顶
下面是栈的概念图:

2.栈的实现

对于栈的实现我们可以使用数组或者是链表来完成,这里我们用数组来模拟实现一个栈:

3.实现讲解

对于栈,我们可以采用顺序表的形式来编写,首先是来对Stack结构体的定义,我们定义容量,栈顶数,以及数据指针

typedef int StackDataType;
typedef struct Stack
{StackDataType* data;int capacity;int top;
}Stack;

其次,我们想让栈具有以下几个功能,分别是:

1.初始化栈

2.销毁栈

3.压栈

4.出栈

5.返回栈顶元素

6.返回栈内元素个数

7.判断栈是否为空

下面我们分批次来完成这些函数功能:

1.初始化栈

我们先对栈来申请四个数据类型的空间,这里我们可以使用malloc函数,判断一下是否开辟成功,然后我们把开辟出的空间的地址赋值给栈内的data,将栈内的容量capacity赋值为开辟的数量4,将栈内的栈顶数置为0

void StackInit(Stack* q)
{assert(q);StackDataType* tmp = (StackDataType*)malloc(sizeof(StackDataType) * 4);if (tmp == NULL){perror("malloc failed");return;}q->data = tmp;q->capacity = 4;q->top = 0;
}

2.销毁栈

比较简单!我们直接释放掉栈内的data指针所指向的空间,将data置为空即可:

void StackDestroy(Stack* q)
{free(q->data);q->data = NULL;
}

3.压栈

将数据压入栈内,这里我们首先先来对栈内的容量和栈顶数做一个判断:

如果栈顶数等于栈的容量数,那么就说明此时栈已经满了,我们需要对栈进行扩容,

这里我们使用realloc函数,对栈内的data指针重新调整一块空间,这里我们同样简单判断一下空间是否开辟成功,并将栈内的容量数+4,代表我们扩容成功,

随后我们将要进行压栈的数据,放入data中,这里top就发挥作用了,可以让我们直接访问栈顶对应的空间,赋值即可,随后把top++代表放入数据成功:

void StackPush(Stack* q, StackDataType x)
{if (q->capacity == q->top){StackDataType* tmp = (StackDataType*)realloc(q->data, sizeof(StackDataType) * (q->capacity + 4));if (tmp == NULL){perror("realloc failed");return;}printf("extend successfully");q->data = tmp;q->capacity += 4;}q->data[q->top++] = x;
}

4.出栈

这里我们要使用另一个函数,判断栈内是否为空,如果为空,那么就没有数据可供pop出去了,我们结束这个函数,在自己撰写时,请记住提前声明函数哦,这样之后我们直接对栈内的top--即可:

void StackPop(Stack* q)
{assert(q);assert(!StackEmpty(q));q->top--;
}

5.返回栈顶元素

操作之前先来判断一下栈内是否为空,如果为空那么取栈顶数top-1就是错误访问数组了,

如果不为空,我们才返回data中的第top-1的元素:

STDataType StackTop(Stack* ps)
{assert(ps);assert(!StackEmpty(ps));return ps->a[ps->top-1];
}

6.返回栈内元素个数

这里不需要进别的操作,直接返回栈顶数top即可:

int StackSize(Stack* ps)
{return ps->top;
}

7.判断栈内是否为空

返回一个判断栈顶数top是否为0的表达式即可:

bool StackEmpty(Stack* ps)
{return ps->top == 0;
}

二.队列

1.队列的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out) 的性质
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头

2.队列的实现

如果要实现一个队列,如果我们模仿栈使用一个顺序表,那么出队列的时候就很麻烦了!,因此我们在这里使用更为方便的链表实现队列的功能:

先来定义队列中每个成员节点的内容以及队列结构体中的首尾指针,哦对了!千万别忘记了添加一个int size成员,这关联到一个队列的重要功能!:

typedef int QDatatype;typedef struct  QueueNode
{struct QueueNode* next;QDatatype data;
}QNode;typedef struct Queue
{QNode* head;QNode* tail;int size;
}Queue;

一个完整的队列应该具备以下几个功能:

1.初始化队列

2.销毁队列

3.单个成员入队列

4.单个成员出队列

5.判断队列是否为空

6.返回队列内元素个数

7.返回队列首个元素

8.返回队列尾部元素

3.实现讲解

1.初始化队列

这里我们传入一个Queue类型的指针,默认已经创建好了一个Queue结构体,我们只需使用指针把Queue内的tail和head头尾指针置为空,代表元素个数的size置为0,就完成了队列的初始化:

void QueueInit(Queue* pq)
{assert(pq);pq->head = NULL;pq->tail = NULL;pq->size = 0;
}

2.销毁队列

销毁队列难度不大,我们定义一个QNode*类型的指针cur,让cur从头指针head开始遍历整个队列,再定义另一个指针prev用来备份cur,判断条件就设置为cur!=NULL,然后让cur依次指向下一个节点,每次cur指向下一个节点之前都赋值给prev,再释放prev指向的空间,然后我们再把Queue结构体中的head,tail头尾指针置为空,size赋值为0即可:

void QueueDestroy(Queue* pq)
{assert(pq);QNode* cur = pq->head;QNode* prev = NULL;while (cur){prev = cur;cur = cur->next;free(prev);}pq->head = NULL;pq->tail = NULL;pq->size = 0;
}

3.单个成员入队列

入队列,PushQueue,这里我们传入Queue*指针,以及要入队列的数据x,我们使用malloc先开辟一块QNode的空间来存放我们的数据,完成一个节点的创建,将newnode的next置为NULL,data赋值x,其次,将这一节点连接到队列当中去,这里其实类似于链表中的尾插,最后,别忘了让Queue中的size自加!

void QueuePush(Queue* pq, QDatatype x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc failed");return;}newnode->next = NULL;newnode->data = x;if (pq->tail == NULL){pq->head = newnode;pq->tail = newnode;}else{pq->tail->next = newnode;pq->tail = newnode;}pq->size++;
}

4.单个成员出队列

单个成员出队列,我们只需将head指针备份一下,将head重新赋值为下一个节点的地址,再释放备份的地址的空间,完成了出队列的功能,这里需要注意的是,当队列中只有一个成员时,出队列后head指向的是空,同时我们还需将tail改为空,否则在访问队列尾部成员的时候会对空指针进行非法解引用操作,并且要将size自减一个,代码如下:

void QueuePop(Queue* pq)
{assert(pq);assert(pq->head);QNode* del = pq->head;pq->head = pq->head->next;free(del);del = NULL;if (pq->head == NULL){pq->tail = NULL;}pq->size--;
}

5.判断队列是否为空

这里比较简单,我们直接返回判断size是否为0的表达式即可,值得一提的是,在前面出队列的时候,我们可以先用这一函数来判断队列是否为空,代替断言:

bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;
}

6.返回队列内元素个数

这里我们返回size就可以了,同样比较简单:

int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}

7.返回队列首个元素

请注意,这里如果直接对head指针进行解引用,那么head可能为空,引发非法解引用的错误,因此,我们需要用到上面的函数,来判断队列是否为空,其次再返回头部成员:

QDatatype QueueFront(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->head->data;
}

8.返回队列尾部元素

同样的道理,这里如果直接对tail指针进行解引用,那么tail可能为空,引发非法解引用的错误,因此,我们需要用到上面的函数,来判断队列是否为空,其次再返回尾部成员:

QDatatype QueueBack(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->tail->data;
}

三.树

1.树的概念概念及结构

树型结构是一类重要的非线性数据结构。其中以树和二叉树最为常用,直观看来,树是以分支关系定义的层次结构。把它叫做“树”是因为它常看起来像一棵倒挂的树,也就是说它常是根朝上,而叶朝下的。

2.树的相关概念

节点的度 一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
叶节点或终端节点 度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点
双亲节点或父节点 若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点 一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的度:一棵树中, 最大的节点的度称为树的度; 如上图:树的度为6
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙 以某节点为根的子树中任一节点都称为该节点的子孙 。如上图:所有节点都是A的子孙
森林:由m(m>0)棵互不相交的树的集合称为森林;

3.树的实现

树的实现可以采用链表实现,也可以采用顺序表也就是数组实现,这里我们只介绍数组实现树的方法,首先是对树这个结构体的定义问题,跟我们的顺序表相同,我们定义储存数据类型的指针,用来记录数据存放个数的size,以及后面用来放入数据判断是否需要扩容的capacity

typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;

一个完整的树应该具备以下几个功能:

1.初始化树

2.销毁树

3.单个成员插入树

4.单个成员移除树

5.判断树是否为空

6.返回树中元素个数

7.返回树的顶部元素

4.实现讲解

1.初始化树

初始化一个树,我们只需先使用malloc开辟一个初始设定值的空间来存放数据,并且,将结构体内部的capacity设定为初始值,size置为0即可,下面是代码实现:

void HeapInit(HP* php)
{assert(php);php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);if (php->a == NULL){perror("malloc failed");return;}php->size = 0;php->capacity = 4;
}

2.销毁树

比较简单,我们直接释放掉结构体内存放数据的指针a所指向空间即可,如果有必要的话可以将a置为NULL,防止野指针的产生,然后我们将capacity和size都置为0即可:

void HeapDestroy1(HP* php)//这里函数名后面加1是因为可能会与系统的函数冲突!
{free(php->a);php->a = NULL;free(php);	php = NULL;
}

3.单个成员插入树

这里首先要来判断树的容量是否足够大,也就是树中的capacity是否与size相等,如果相等那么就需要扩容,这里我们使用realloc重新申请一块capacity+增长值的空间,再重新赋给a,

同时capacity加上增长的值就完成了扩容,我们将要插入的数据放在最后,size++,到这里还没有结束,这也是树不同于前面链表与顺序表的地方,我们还需要对树进行调整

这里我选择的是建大堆,那么就是说父亲节点比孩子节点要大,我们插入一个AdjustUp调整树函数:

void HeapPush(HP* php, HPDataType x)
{if (php->capacity == php->size){HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * (php->capacity + 4));if (tmp == NULL){perror("realloc failed");return;}php->a = tmp;php->capacity += 4;}php->a[php->size++] = x;AdjustUp(php->a, php->size - 1);
}

下面是AdjustUp的实现:

对于这个函数,我们传入孩子节点以及要调整的数组的地址,定义父亲节点

这里需要用到一条规律:父亲节点=(孩子节点-1)/2;具体的推导过程可以参考别的讲解文章,不在此做过多介绍,将孩子节点的值与父亲节点比较,

如果前者大于后者,那么就进行交换,将父亲节点作为孩子节点继续判断,直到前者小于等于后者,就结束比较,外层我们使用一个while循环,条件设置为child>0,

为什么不是parent<0呢?经过计算发现,parent不可能小于0,最小就是0,因此最佳的解决方案是判断孩子节点的下坐标:

void swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (child>0){if (a[child] > a[parent]){swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

4.单个成员移除树

首先判断树是否为空,如果不为空,就将数组的第一个值与最后一个值进行交换,因为这里Pop的是树的顶部元素,大堆顶部元素是最大的,我们进行交换后将size--,这样就访问间接删除了顶部元素,然后这里我们还需要向下建堆,AdjustDown函数:

void HeapPop(HP* php)
{assert(php);assert(!HeapEmpty(php));swap(&php->a[0], &php->a[php->size - 1]);php->size--;	AdjustDown(php->a, php->size, 0);
}

跟AdjustUp函数同理,对于这个函数,我们传入父亲节点以及要调整的数组的地址,还需要数组元素个数,定义孩子节点:

这里需要用到一条规律:孩子节点=父亲节点*2+1,这是公式父亲节点=(孩子节点-1)/2的逆推

这里我们需要选出孩子节点中的较大者来与父亲节点进行比较,如果前者大于后者,那么就进行交换,将孩子节点作为父亲节点继续判断,直到前者小于等于后者,就结束比较,外层我们使用一个while循环,条件设置为child<元素个数n:

void AdjustDown(HPDataType* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){//选出最大的一个孩子if (child + 1 < n && a[child] < a[child + 1]){child = child + 1;}if (a[parent] < a[child]){swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}else{break;}}
}

5.判断树是否为空

返回结构体中size是否为0的判断表达式即可:

bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}

6.返回树中元素个数

直接返回size的值即可:

int HeapSize1(HP* php)
{assert(php);return php->size;
}

7.返回树的顶部元素

这里需要判断树是否为空,在返回数组的第一个元素,也就是树的顶部元素:

HPDataType HeapTop(HP* php)
{assert(php);assert(!HeapEmpty(php));return php->a[0];
}

数据结构就更新到这里啦!觉得对你有帮助的话可以多多支持作者哦~

——————————————————本文结束——————————————————


http://www.ppmy.cn/ops/145145.html

相关文章

08 Django - Django媒体文件静态文件文件上传

九、Django媒体文件&静态文件&文件上传 1.静态文件和媒体文件 媒体文件: 用户上传的文件, 叫做media静态文件: 存放在服务器的 css, js, image等,叫做static 在Django中使用静态文件 {% static img/example.jpg %} > static模板关键字就是在settings.py中指定的…

前端Pako.js 压缩解压库 与 Java 的 zlib 压缩与解压 的互通实现

工具介绍&#xff1a; pako.js 前端压缩解压的库&#xff08;包含 zlib 和gzip 两种实现&#xff0c;这里只介绍 zlib&#xff09; pako 2.0.4 API documentation Java8 原生支持 zlib 和 gzip 业务场景 因为数据太大&#xff0c;网络环境不可控。故前端需要将数据 A 先压缩…

PCL点云库入门——PCL库点云滤波算法之直通滤波(PassThrough)和条件滤波(ConditionalRemoval)

0、滤波算法概述 PCL点云库中的滤波算法是处理点云数据不可或缺的一部分&#xff0c;它们能够有效地去除噪声、提取特征或进行数据降维。例如&#xff0c;使用体素网格滤波&#xff08;VoxelGrid&#xff09;可以减少点云数据量&#xff0c;同时保留重要的形状特征。此外&#…

Leetcode经典题20--长度最小的子数组

题目描述 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0 。 输入输出示例 输入&…

【Django】测试带有 CSRF 验证的 POST 表单 API 报错:Forbidden (CSRF cookie not set.)

【Django】测试带有 CSRF 验证的 POST 表单 API 报错&#xff1a;Forbidden (CSRF cookie not set.) 问题描述 Django 使用 Apifox 测试 POST 表单报错。 Forbidden (CSRF cookie not set.): /api/parse [20/Dec/2024 15:17:25] "POST //api/parse HTTP/1.1" 403 …

YoloDotNet OBB(定向边界框)

文章目录 前言1、代码示例引入命名空间:初始化 Yolo 对象:加载图像和执行 OBB 检测:处理检测结果:2、自定义 OBB 绘制(可选)3、性能和准确性考虑前言 OBB(Oriented Bounding Box)在目标检测中用于更精确地框定非水平或垂直放置的物体。与常规边界框不同,OBB 可以根据物…

vscode的keil assistant 中搜索不到全局变量

搜不到 但是在包含的文件中输入 ../../../,就是全局搜索的结果 我的文件结构是&#xff1a;\Desktop\LVGL文件系统移植&#xff08;lvgl8&#xff0e;&#xff13;&#xff09;\Projects\MDK-ARM 盲猜是keil assistant 当前文件夹打开的时候是进入到了MDK-ARM文件夹层次&…

园区网大综合实验

首先按照题目要求划分网络环境&#xff08;如图所示&#xff09; 进行 sw1 sw2 sw3 sw4 vlan基本配置 sw1 sw2 sw3 sw4 ip配置 r1 isp 在r1 0/0/0接口做nat设置 stp生成树配置 sw1 sw2 sw3 sw4 stp参数修改 根保护 mstp边缘接口 vrrp