目录
一.栈
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.栈的概念及结构
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.队列的概念及结构
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.树的相关概念
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];
}
数据结构就更新到这里啦!觉得对你有帮助的话可以多多支持作者哦~
——————————————————本文结束——————————————————