目录
---------------------------------------------------------++本文开始++------------------------------------------------------
一.栈
1.栈的概念及结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。
栈中的数据元素遵守后进先出 LIFO(Last In First Out) 的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
下面是栈的概念图:
2.栈的实现
对于栈的实现我们可以使用数组或者是链表来完成,这里我们用数组来模拟实现一个栈:
3.实现讲解
对于栈,我们可以采用顺序表的形式来编写,首先是来对Stack结构体的定义,我们定义容量,栈顶数,以及数据指针:
cpp
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:
cpp
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置为空即可:
cpp
void StackDestroy(Stack* q)
{
free(q->data);
q->data = NULL;
}
3.压栈
将数据压入栈内,这里我们首先先来对栈内的容量和栈顶数做一个判断:
如果栈顶数等于栈的容量数,那么就说明此时栈已经满了,我们需要对栈进行扩容,
这里我们**使用realloc函数,对栈内的data指针重新调整一块空间,**这里我们同样简单判断一下空间是否开辟成功,并将栈内的容量数+4,代表我们扩容成功,
随后我们将要进行压栈的数据,放入data中,这里top就发挥作用了,可以让我们直接访问栈顶对应的空间,赋值即可,随后把top++代表放入数据成功:
cpp
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--即可:
cpp
void StackPop(Stack* q)
{
assert(q);
assert(!StackEmpty(q));
q->top--;
}
5.返回栈顶元素
操作之前先来判断一下栈内是否为空,如果为空那么取栈顶数top-1就是错误访问数组了,
如果不为空,我们才返回data中的第top-1的元素:
cpp
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top-1];
}
6.返回栈内元素个数
这里不需要进别的操作,直接返回栈顶数top即可:
cpp
int StackSize(Stack* ps)
{
return ps->top;
}
7.判断栈内是否为空
返回一个判断栈顶数top是否为0的表达式即可:
cpp
bool StackEmpty(Stack* ps)
{
return ps->top == 0;
}
二.队列
1.队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out) 的性质
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
2.队列的实现
如果要实现一个队列,如果我们模仿栈使用一个顺序表,那么出队列的时候就很麻烦了!,因此我们在这里使用更为方便的链表实现队列的功能:
先来定义队列中每个成员节点的内容以及队列结构体中的首尾指针,哦对了!千万别忘记了添加一个int size成员,这关联到一个队列的重要功能!:
cpp
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,就完成了队列的初始化:
cpp
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即可:
cpp
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自加!:
cpp
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自减一个,代码如下:
cpp
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的表达式即可,值得一提的是,在前面出队列的时候,我们可以先用这一函数来判断队列是否为空,代替断言:
cpp
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
6.返回队列内元素个数
这里我们返回size就可以了,同样比较简单:
cpp
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
7.返回队列首个元素
请注意,这里如果直接对head指针进行解引用,那么head可能为空,引发非法解引用的错误,因此,我们需要用到上面的函数,来判断队列是否为空,其次再返回头部成员:
cpp
QDatatype QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
8.返回队列尾部元素
同样的道理,这里如果直接对tail指针进行解引用,那么tail可能为空,引发非法解引用的错误,因此,我们需要用到上面的函数,来判断队列是否为空,其次再返回尾部成员:
cpp
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*** :
cpp
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即可,下面是代码实现:
cpp
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即可:
cpp
void HeapDestroy1(HP* php)//这里函数名后面加1是因为可能会与系统的函数冲突!
{
free(php->a);
php->a = NULL;
free(php);
php = NULL;
}
3.单个成员插入树
这里首先要来判断树的容量是否足够大,也就是树中的capacity是否与size相等,如果相等那么就需要扩容,这里我们**使用realloc重新申请一块capacity+增长值的空间,**再重新赋给a,
同时capacity加上增长的值就完成了扩容,我们将要插入的数据放在最后,size++,到这里还没有结束,这也是树不同于前面链表与顺序表的地方,我们还需要对树进行调整,
这里我选择的是建大堆,那么就是说父亲节点比孩子节点要大,我们插入一个AdjustUp调整树函数:
cpp
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,**因此最佳的解决方案是判断孩子节点的下坐标:
cpp
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函数:
cpp
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:
cpp
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的判断表达式即可:
cpp
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
6.返回树中元素个数
直接返回size的值即可:
cpp
int HeapSize1(HP* php)
{
assert(php);
return php->size;
}
7.返回树的顶部元素
这里需要判断树是否为空,在返回数组的第一个元素,也就是树的顶部元素:
cpp
HPDataType HeapTop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
return php->a[0];
}
数据结构就更新到这里啦!觉得对你有帮助的话可以多多支持作者哦~
------------------------------------------------------本文结束------------------------------------------------------