上面的两张图很清晰的表明了我们的栈和队列的特点和他们的结构。
栈和队列的结构和特点
栈:我们的栈是一种特殊的线性结构,他是只能在栈顶进行进栈和出栈。是一种先进后出的结构。
队列:而我们的队列同样也是一种线性结构,但是他和栈的不同的是他是在对头出数据,队尾入数据,是属于一种先进先出的结构。
对于我们的栈我们的出栈顺序可能会有不同的,如果我们要入栈的顺序是1 2 3 4 5,
那么出栈的顺序可以是1 2 3 4 5,因为可以边入边出。也可以是3 2 1 5 4。我们先入1 2 3,在将这山歌数据全部出栈,在入 4 5 在进行出栈的处理。只是我们先入栈的不会在我们后入栈的前面出栈。
而我们的队列出队的顺序就只有一种,我们进队顺序是1 2 3 4 5。出队顺序就只有 1 2 3 4 5。
我们的栈和队列既然是一种线性结构,我们可以用链表和顺序表来实现。
栈的实现方式的选择:但是这两种方法实现会导致不同的难度,我们的栈是在一端实现我们的入和出的操作,这也说明了其实用数组来实现是会更加简单的,因为我们只需要移动一下我们的栈顶指针,这里的指针不是我们所说的存储地址的那个数据类型,而是指向我们栈顶位置的数字,我们的数组的下标而已。如果我们用链表来实现我们还需要将我们出栈的数据元素给释放,还要噶变我们栈顶指针的指向,实现起来会比我们的数组复杂。
队列的实现方式的选择:对于我们队列我们会选择用链表来实现,你可以能回想数组会更加简单一点,但是我们的队列他用数组实现的话会有一个更加麻烦的问题,
我们这里有个满队的队列,然后我们的对他进行出队的操作,全部出队,最后我们的head和tail指向同一个地方,然后我们如果还想继续在里面进行入队是不行的了,因为我们的tail已经来到了数组的最后一个位置,不能再继续假如元素了,这就导致了其实我们前面还有空间可以用但是我们却不能再次利用。导致了我们的假溢出。当然这个是可以解决的,我们需要创建一个环形队列。
栈的实现
我们这里用数组实现我们的栈。
我们的栈用一个结构体来写:里面包括我们的一个指针(开阔空间之后数组的作用),和一个栈顶指针,和我们数组的空间大小。
typedef int STDataType;
typedef struct Stack
{
STDataType* _a;
int _top; // 栈顶
int _capacity; // 容量
}Stack;
我们所需要实现的接口如下:
// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);
初始化栈
我们初始化我们的栈,可以给我们的的数组开辟空间,并给我们结构体内其他的数据赋初值。我们的栈顶指针赋值-1,因为此时我们还没有任何数据,还只是一个空栈。
代码:
// 初始化栈
void StackInit(Stack* ps)
{
ps->_a = (STDataType*)malloc(sizeof(STDataType) * 4);//初始化先分配四个空间
ps->_capacity = 4;
ps->_top = 0;//栈顶指针指向我们的栈顶的下一个位置
}
入栈
入栈的操作就是我们从我们的栈顶插入元素,将我们的栈顶指针的位置改变一下,操作还是挺简单的。
代码:
// 入栈
void StackPush(Stack* ps, STDataType data)
{
if (ps == NULL)
{
perror("ps ");
return;
}
if (ps->_top == ps->_capacity)
{
//当我们的栈,满的时候就需要扩容
ps->_capacity = ps->_capacity * 2;
ps->_a = (STDataType*)realloc(ps->_a, sizeof(STDataType) * ps->_capacity);
assert(ps->_a);
}
ps->_a[ps->_top++] = data;
}
出栈
我们出栈的操作由于栈的限定也只能在栈顶进行操作。我们并不需要将我们的栈顶的位置的元素改变,只需要将我们的栈顶指针改变就行。
代码:
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
if (ps->_top == 0)
{
printf("栈为空\n");
return;
}
ps->_top--;
}
获取栈顶元素
我们这个就是将我们的栈顶元素返回就行,当然在取栈顶元素之前得判断栈是不是为空。
代码:
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->_top != 0);
return ps->_a[ps->_top - 1];
}
获取栈的数据的元素个数
我们只需要返回我们的栈的top的值就行,因为我们的top指向的是我们栈顶的下一个位置,那么他的大小就是我们元素的个数。
代码
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->_top;
}
判空
我们只需要判断栈顶指针top的值是不是为0;
代码:
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps)
{
assert(ps);
return ps->_top == 0 ? 1 : 0;
}
销毁栈
我们的栈在销毁的时候只需要将我们数组动态申请的空间释放就行,对于连续的空间的释放操作是非常简单的
代码
// 销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->_a);
ps->_a = NULL;
}
队列实现
对于我们的对列我们需要他的队头和队尾指针就行, 我们需要两个结构体来实现。
我们的里面的元素是一种一个结构体,因为我们用链式存储来实现我们的队列的话,其实实现和我们的链表本质上区别不是很大。只是我们的队列用两个指针来指向我们的链表的头和尾,也只能用这两个指针来访问我们的队列。我们也用一个size来记录我们来队列的大小。
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType val;
}QNode;
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
初始化队列
初始化我们的队列,就是使我们队列的队头指针和队尾指针置空,因为我们的队列还没有元素,
代码:
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
入队和出队
我们入队是在我们的队尾进行,而我们的出队操作是在我们的队头进行。
入队的时候我们要将我们的队尾指针的next指针指向我们的新节点。 再去将我们的队尾指针改成指向我们的新节点。我们队列的为空的时候和我们的队列不为空的时候的入队操作是不一样的。如果为空,那么我们需要将两个指针都指向该新节点。不为空的话就是就改变我们队尾指针改变就行。
出队的时候就是我们的对头指针改成我们的原来对头的下一个指针,但是在这之前我们要将我们的原来的队头指针指向的空间释放,因为链式存储不是连续的一块物理空间,在我们进行出队操作的时候不释放那我们后面就找不到这块空间,会导致哦内存泄漏。还需检查我们的队列是不是空的。
在进行这两个操作的时候需要我们也去改变我们的size的大小。
代码:
// 队尾插入
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->next = NULL;
newnode->val = x;
if (pq->ptail == NULL)
{
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
// 队头删除
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->size != 0);
/*QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
if (pq->phead == NULL)
pq->ptail = NULL;*/
// 一个节点
if (pq->phead->next == NULL)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else // 多个节点
{
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
取队尾和对头的值
我们只需要将我们的队头和队尾的值返回就行。
代码:
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead);
return pq->phead->val;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail);
return pq->ptail->val;
}
求队列的元素个数
我们在定义我们队列的结构体的时候,在里面添加了一个size,这个就是我们的队列元素的个数。
直接返回其值就行;
代码:
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
判空
我们对列判空,我们有两个方法一个就是我们看我们的size的大小是不是为0,为0就是空的,另一个其实是可以看我们的队头指针是不是为空指针。
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}