【C语言数据结构】栈和队列

栈种的数据会一个压着一个往上叠。先放上去的,就会被压在下面。因此是种“先入后出(FILO,Fist In Last Out)”的结构。

数组还是链表?

那么对栈的实现应该是用数组还是链表呢?

数组是双方向的,索引既可以++也可以--。链表是单方向的,索引只能够++。为了实现栈,那么就需要在插入时++,在读取时--,因此用数组去实现栈更为合适。但数组的内存往往是固定的,但栈不一定固定,因此还需要动态内存开辟realloc。

实际上,数组和链表都可以实现栈。这里只以数组为例。

栈的实现

栈是用数组来实现的,因此需要有个数组的头指针a。栈是后入先出,也就是说对栈的操作都是在栈头进行的,因此需要索引top记录栈这个数组的尾部(也就是栈的头部)。进行动态内存开辟需要对栈的空间进行判断,因此需要一个数记录栈的容量。

由于不能确定这个栈是用于存放int还是char等类型,因此需要通过typedef来对栈的数据类型进行宏定义。

栈需要完成的功能有:栈初始化、头插、头删、读取栈头数据、栈的销毁等。

typedef int STDataType;
typedef struct Stack
{
    STDataType* a;
    int top;
    int capacity;
}ST;

void STInit(ST* pst);//栈初始化
void STPush(ST* pst, STDataType x);//头插
void STPop(ST* pst);//头删
STDataType STTop(ST* pst);//读取栈头数据
bool STEmpty(ST* pst);//检测栈是否为空
int STSize(ST* pst);//获取栈的尺寸
void STDestroy(ST* pst);//栈的销毁

现在逐一实现:

栈的初始化

栈是一个数组,那么在main中会先有个ST* stack;来定义一个结构体。对栈的初始化就是对stack这个结构体中数据的初始化,其中stack.a才是实际的栈。stack是一个指针,指向所定义的栈。

void STInit(ST* pst)
{
    assert(pst);//确保你在main中定义了ST* stack
    pst->a=NULL;//栈内无空间
    pst->top=0;//栈头为0
    pst->capacity=0;//栈内空间为0
}

这里的top实际是栈中数据的数量,因此栈头的指针应该为top-1。栈头top-1会作为栈的数组a的索引,a[top-1]。

栈的头插

既然要插入数据,那么就要考虑的一个问题是,空间够不够用。因此在这个函数中,需要判断并扩容。那么如何判断?

我们有一个栈头索引top,还有栈的内存capacity,很明显,当top=capacity时,就说明栈满了需要扩容。

数组扩容使用的是realloc,来确保扩容的空间具有连续性。

void STPush(ST* pst,STDataType x)//main中定义的指向栈的指针,要插入的数据
{
    assert(pst);
    if(pst->top == pst->capacity)
    {
        int newCapacity = (pst->capacity==0)? 4:pst->capacity*2;
        //使用三目操作符,如果空间为空,则扩容4个,如果不是,则扩容2倍
        STDataType* tmp=(STDataType*)realloc(pst->a,newCapacity*sizeof(STDataType));
        if(tmp==NULL)
        {perror("realloc fail");return;}
        pst->a=tmp;//将新空间传给栈
        pst->capacity=newCapacity;
    }
    pst->a[pat->top]=x;//将数据插到栈头
    pst->top++;//栈头索引++
}

栈的头删

栈的头删很简单,直接将top--就行,这样新插入数据时,会将原来的数覆盖。

但存在个问题,如果栈本身是空的,也就是top=0,那么再删除数据,就会造成越界。因此需要一个函数bool STEmpty(ST* pst)来判断栈是否为空。

bool STEmpty(ST* pst)
{
    assert(pst);
    return pst->top==0;//如果为空,则返回true
}

void STPop(ST* pst)
{
    assert(pst);
    assert(!STEmpty(pst))//为空则直接断言
    pst->top--;
}

读取栈头数据/栈中数据个数

就是直接读取a[top-1]和top。同样需要判断栈是否为空,毕竟空的不能读取。

STDataType STTop(ST* pst)
{
    assert(pst);
    assert(!STEmpty(pst));
    return pst->a[pst->top-1];//注意这里的-1
}

int STSize(ST* pst)
{
    assert(pst);
    return pst->top;
}

栈的摧毁

这里注意不能直接free(stack),因为stack只是指向a的指针,真正的栈是stack.a。

void STDestroy(ST* pst)
{
    assert(pst);
    free(pst->a);
    pst->a=NULL;
    pst->top=pst->capacity=0;
}

队列

栈是竖着的,有底面,因此不能直接拿下面的,属于先入后出。

队列是横着的,通透的管道,排着队进排着队出,属于先入先出(FIFO,First In First Out)。

数组还是链表?

不同于栈只需要操作栈头,队列是需要操作头+尾。对如果用数组,那么删除head的数据时,就需要后面数据逐一前移,这样效率很低。

既然是链表,那么就需要对头和尾都有相应指针记录。

同样,队列需要能完成的任务有:队列初始化、读取队头/队尾数据、头删、尾插、队列销毁。

typedef int QDataType;
typedef struct QueueNode
{
    struct QueueNode* next;
    QDataType data;
}QNode;//定义链表,队列中每个数据都是在这里对应。

typedef struct Queue
{
    QNode* phead;//单独记录队头,用于读取
    QNode* ptail;//单独记录队尾,用于尾插
}Queue;

void QueueInit(Queue* pq);//队列初始化
void QueuePush(Queue* pq, QDataType x);//队列尾插
void QueuePop(Queue* pq);//队列头删
QDataType QueueFront(Queue* pq);//读取队头数据
QDataType QueueBack(Queue* pq);//读取队尾数据
int QueueSize(Queue* pq);//获取队列长度
bool QueueEmpty(Queue* pq);//判断队列是否为空
void QueueDestroy(Queue* pq);//队列销毁

队列的实现

队列初始化

在main函数中,使用Queue que;来定义一个队列。que中已经具有对phead、ptail的初始化。

void QueueInit(Queue* pq)
{
    assert(pq);
    pq->phead=NULL;
    pq->ptail=NULL;
}

队列尾插

栈开拓空间使用realloc来确保空间连续。队列使用的链表使用malloc就可以。

但是需要注意的一点是,如果是插入的第一个数据,那么phead和ptail都为NULL,必然不能只让ptail->next=newnode,同样需要处理下phead

void Queue(Queue* pq,QDataType x)
{
    assert(pq);
    QNode* newnode=(QNode*)malloc(sizeof(QNode));//创建队尾新节点
    if(newnode==NULL)
    {perror("malloc fail");return;}
    newnode->data=x;//数据传给新节点
    newnode->next=NULL;
    if(pq->ptail==NULL)//如果队列为空
    {
        assert(pq->phead==NULL);
        pq->phead = pq->ptail = newnode;
    }
    else
    {
        pq->ptail->next=newnode;//ptail->next指向新节点
        pq->ptail->newnode;//将newnode设置为队尾
    }
}

头删

删除就会面对一种情况就是,空队列是无法继续头删的。因此需要函数判断队列是否为空。

bool QueueEmpty(Queue* pq)
{
    assert(pq);
    return pq->phead==NULL;
}

然后面对的问题就是,如果只剩下一个结点,删除后,phead和ptail都需要=NULL。因此需要一个节点/多个节点分类讨论。

void QueuePop(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq));
    if(pq->head->next==NULL)//即只剩下一个结点
    {
        free(pq->head);
        pq->phead=pq->ptail=NULL;
    }
    else//剩下多个结点
    {
        QNode* next = pq->phead->next;
        free(pq->phead);
        pq->phead=next;
    }
}

获取队列长度

获取队列长度有两种方式,一种是在Queue结构体中添加一个size,即

typedef struct Queue
{
    QNode* phead;//单独记录队头,用于读取
    QNode* ptail;//单独记录队尾,用于尾插
    int size;//用于记录队列长度
}Queue;

然后在其它函数,如头删、尾插时操作下size。但由于size用的并不多,所以也可以不定义size,而是通过遍历一次队列来获取size,如

int QueueSize(Queue* pq)
{
    int cnt=0;
    QNode* cur=pq->phead;
    while(cur)
    {
        cur=cur->next;
        cnt++;
    }
    return cnt;
}

据读取

读取队头/队尾数据时,要考虑到空队列无法读取。

QDataType QueueFront(Queue* pq)//读取队头信息
{
    assert(pq);
    assert(!QueueEmpty(pq));
    return pq->phead->data;
}
QDataType QueueBack(Queue* pq)//读取队尾信息
{
    assert(pq);
    assert(!QueueEmpty(pq));
    return pq->ptail->data;
}

队列的销毁

队列的销毁要用到链表的销毁。不能像栈一样直接free。队列是链表,因此需要对每个元素free

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值