栈和队列(C语言版)

1.栈

1.1栈的概念

栈是一种特殊的线性表,其只允许在固定的一端进行元素的插入和删除操作,进行插入和删除操作的一端叫栈顶,另一端叫做栈底,因此栈是一种后进先出的结构。

压栈:栈的插入操作叫做进栈/压栈/入栈。

出栈:栈的删除操作叫做出栈

1.2栈的结构图形表

1.3栈底层结构的选取

1.3.1顺序存储结构

底层采用顺序表来实现,数组的头部为栈底,数组的尾部为栈顶,采用顺序表的尾删和尾插,这样插入和删除的数据的时间复杂度都为o(1)。

1.3.2链式存储结构
1.3.2.1单链表

底层采用单链表,表头作为栈底,表尾作为栈顶,这样采用单链表的尾插和尾删,这样插入和删除数据的时间复杂度为o(n)。

那如果我们换一种思路,,表头为栈顶,表尾为栈底,这样采用的是单链表的头插和头删,这样插入和删除数据的时间复杂度和数组一样都是o(1)。

1.3.2.2双向链表

用表头当栈底,用表尾当栈顶,,这样采用的是双向链表的尾插和尾删,,插入和删除数据的时间复杂度都为o(1)。

既然这三种存储结构都有时间复杂度的情况,那我们该选取哪种结构呢?

首先是单链表和双链表,双链表比单链表多一个指针因此,单链表比双链表更吃内存,然后是单链表和数组,数组是每次申请一块连续的空间容量一般是成而被增长的,而单链表每插入一个数据都要申请一个节点的内存,因此在数据量非常庞大的时候采用数组比链表更节省时间。

1.4栈代码

1.4.1栈的结构定义
typedef int DataType;
//栈的结构
typedef struct Stack
{
    DataType* arr;
    int capacity;
    int top;
}ST;

arr是一个指向数组存储空间的指针,内存是动态开辟的。

top是栈中的元素个数。

capacity代表栈空间的大小。

1.4.2栈的初始化
//栈的初始化
void StackInit(ST* st)
{
    assert(st);
    st->arr = NULL;
    st->capacity = st->top = 0;
}

初始化栈的内存,将arr指向的空间置为NULL,并且把栈顶和栈容量初始化为0。

1.4.3栈的销毁
//栈的销毁
void StackDestory(ST* st)
{
    assert(st);
    if (st->arr)
    {
        free(st->arr);
    }
    st->arr = NULL;
    st->capacity = st->top = 0;
}

如果栈底层的数组不为空需要手动释放,并且把该指针指向的空间置为NULL,把栈顶和栈容量置为0,到此栈销毁成功。

1.4.4栈的插入(入栈)
//入栈
void StackPush(ST* st, DataType x)
{
    assert(st);
    if (st->capacity == st->top)
    {
        int newscapacity = st->capacity == 0 ? 4 : st->capacity * 2;
        DataType* tmp = (DataType*)realloc(st->arr, sizeof(DataType) * newscapacity);
        if (tmp == NULL)
        {
            exit(-1);
        }
        st->arr = tmp;
        st->capacity = newscapacity;
    }
    st->arr[st->top++] = x;
}

首先判断栈的存储空间是否足够,如果足够,就直接将数据插入到arr指向的空间中的未使用空间,如果不够,就先给arr扩容然后插入到未使用的空间中,并且有效元素个数要加一。

1.4.6栈的判空
//判空
bool StackEmpty(ST* st)
{
    assert(st);
    return st->top == 0;
}

看栈结构中的有效个数即Top变量是否为0,如果为0则证明栈中不存在有效数据。

1.4.5栈的删除(出栈)
//出栈
void StackPop(ST* st)
{
    assert(st);
    assert(!StackEmpty(st));
    st->top--;
}

先判断栈中的有效数据是否存在,如果存在就让栈中的有效个数减一,如果不存在就会命中assert报错。

1.4.7返回栈顶数据
//获取栈顶元素
DataType StackFront(ST* st)
{
    assert(st);
    return st->arr[st->top - 1];
}

返回arr中最后一个有效位置的元素,top指向有效个数的最后一个,因此最后一个有效个数的下标为Top-1,如果没有数据则命中assert断言报错。

1.4.8返回栈中元素个数
//获取栈中元素的个数
int StackSize(ST* st)
{
    assert(st);
    return st->top;
}

栈结构中的Top标记的就是元素的个数。

2.队列

2.1队列的概念

只允许在一端插入数据,在另一端进行删除数据的特殊的线性表,队列具有先进先出的特性。

2.2队列的结构图形表示

2.3队列底层结构的选取

2.3.1顺序存储结构

底层采用顺序表来实现,数组的头代表队头,数组的尾代表队尾,这样采用的是数组的头删和尾插,出队的时间复杂度是o(n),入队的时间复杂度是o(1)。

或者数组的头代表队尾,数组的尾代表队头,这样采用的是头插和尾删,入队的时间复杂度是o(n),出队的时间复杂度是o(1)。

2.3.2链式存储结构
2.3.2.1单链表

底层采用单链表,表头作为队头,表尾作为队尾,这样采用单链表的头删和尾插,这样出队的时间复杂度是o(1),入队的时间复杂度是o(n)。

或表头作为队尾,表尾作为队头,这样采用的是头插和尾删,这样入队的时间复杂度是o(1),出队的时间复杂度是o(n)。

2.3.2.2双向链表

与栈的情况类似因此不推荐。

那么单链表和顺序表的我们又该如何选取呢?

我们可以定义两个指针来维护链表的结点这样可以使单链表入队和出队的时间复杂度达到o(1),顺序表目前没有想到更好的办法,因此我们采用链表来实现队列的存储。

2.4队列的代码

2.4.1队列的结构定义
typedef int DataType;
typedef struct QueueNode
{
    DataType data;
    struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
    QueueNode* phead;
    QueueNode* ptail;
    int size;
}Queue;

首先,定义一个结构体,作为链表没接节点的类型变量,然后定义一个结构体,存储两个用来维护链表节点的指针,,里面还定义了一个size用来计算结点的个数。

2.4.2队列的初始化
//队列的初始化
void QueueInit(Queue* ps)
{
    assert(ps);
    ps->phead = ps->phead = NULL;
    ps->size = 0;
}

让维护队列的两个指针指向空,且将Size置为0到此初始化完成。

2.4.3队列的插入(入队)
//入队
void QueuePush(Queue* ps, DataType x)
{
    assert(ps);
    QueueNode* Next = (QueueNode*)malloc(sizeof(QueueNode));
    if (Next == NULL)
    {
        exit(-1);
    }
    Next->data = x;
    Next->next = NULL;
    if (ps->phead == NULL)
    {
        ps->phead = ps->ptail = Next;
    }
    else
    {
        ps->ptail->next = Next;
        ps->ptail = Next;
        
    }
    ps->size++;
}

先申请一个链表结点将链表节点的指针域置为空,将数据域置为想要的数据。如果维护队列的两个指针都为空,那么说明刚申请的结点为第一个结点,就将这两个指针同事指向该空间,如果该数据不是当前的第一个数据,则让维护队尾指针指向链表节点的指针域指向下一个结点,然后再将维护队尾的指针指向该结点,最后size加一增加一个有效数据位。

2.4.4队列的判空
//队列判空
bool QueueEmpty(Queue* ps)
{
    assert(ps);
    return ps->phead == NULL;
}

如果维护队列的两个结点都为空,则证明该队列为空。

2.4.5队列的删除(出队)
//出队
void QueuePop(Queue* ps)
{
    assert(ps);
    assert(!QueueEmpty(ps));
    if (ps->phead == ps->ptail)
    {
        free(ps->phead);
        ps->phead = ps->ptail = NULL;
    }
    else
    {
        QueueNode* tmp = ps->phead->next;
        free(ps->phead);
        ps->phead = tmp;
    }
    ps->size--;
}

首先要判断队列是否为空,如果为空就命中assert报错,如果只存在一个数据,直接销毁结点,并且将两个维护队列的指针置为空,如果有多个数据就销毁维护队头指针所指向的空间,并将其指向下一个结点,然后size--,减少一个有效的数据个数。

2.4.6队列取队头数据
//取队头元素
DataType QueueFront(Queue* ps)
{
    assert(ps);
    return ps->phead->data;
}

维护队尾指针,指向的空间的数据域为元素为队列数据。

2.4.7队列取队尾数据
//取队尾元素
DataType QueueBack(Queue* ps)
{
    assert(ps);
    return ps->ptail->data;
}

维护队尾指针,指向的空间的数据域为元素队尾数据。

2.4.8队列的元素个数
//队列的元素的个数
int QueueSize(Queue* ps)
{
    assert(ps);
    return ps->size;
}

队列结构中的Size保存的是队列中的元素个数

2.4.9队列的销毁
//队列的销毁
void QueueDestory(Queue* ps)
{
    assert(ps);
    while (ps->phead)//!QueueEmpty(ps)
    {
        QueueNode* Next = ps->phead->next;
        free(ps->phead);
        ps->phead = Next;
    }
    ps->phead = ps->ptail = NULL;
    ps->size = 0;
}

将队列中的每个结点都释放,将维护队列的两个指针置为空,将Size变量为0即可完成队列销毁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值