一:栈
栈的概念:
栈(Stack)是一种常见的数据结构,它基于后进先出( Last In, First Out, LIFO)的原则工作。这意味着最后进入栈的元素最先被移除,而最先进入栈的元素最后被移除。栈通常有两个主要操作:
压栈(Push):将元素添加到栈的顶部。
出栈(Pop):从栈的顶部移除元素。
栈的特性决定了:栈只能访问栈顶的数据,并遵循着先入后出的原则,如果像访问栈底数据,只能将栈上剩余的数据全部依次弹出,不能去遍历,否则会破坏栈的性质。
栈的实现:
栈的结构:
栈可以用数组的方式实现也可以用链表的方式实现,但数组在存取元素时具有 O(1) 的时间复杂度。栈的基本操作包括压栈(push)和弹栈(pop),这两个操作在数组中可以通过索引直接进行,效率非常高。相比之下,链表的操作(除非是双向链表且操作在头部进行)通常需要 O(N) 的时间复杂度,数组在存储元素时,除了实际存储的数据外,还需要一定的额外空间来维护数组的结构。但是相比链表,链表需要额外的指针空间来维护节点间的连接关系,这会增加存储开销,使用数组实现栈比使用链表更为简单和直观。数组不需要处理节点间的指针关系,操作起来更加直接。 综上所述,栈的实现用数组会更加方便快捷。
这里用typedef 将int类型重新起了一个STDataType的名字,因为在存入栈数据的时候,不一定存的就是整型数据,也可能是字符型,在后续的维护修改中,只需要将typedef int STDataType改成
typedef char STDataType,可以大大减小维护的成本。 接着定义一个名为Stack的结构体,重命名为ST。
STDataType*a:表示一个指向栈的内存的指针
int top:表示指向栈顶有效数据的下一个位置
int capaticy:表示栈数据的容量
栈的初始化与销毁:
初始化:
第一步:断言asset(ps),表示如果传进来的参数为NULL就报错。
第二步:使用malloc申请一块空间给ps->a(栈指针),大小为16个字节(4个STDataType),如果申请失败,那么就进行报错并结束程序。
第三步:初始化将capaticy容量初始化为4,并将top初始化为0;
出栈:
第一步:先判断传入的数据是否为NULL;
第二步:判断栈结构体里是否还有数据,如果没有数据是不能进行删除的。
第三步:将top--,这里不需要去管top里的值,因为后续插入的会进行覆盖。
入栈:
第一步:进行断言assert(ps);
第二步:检查有效数据是否等于容量,如果等于则进行扩容;
第三步:将需要传入的数据放入到top位置,并将top++。top指向的是有效数据的下一个位置,所以直接放在top位置上就行。
计算栈的有效数据长度:
访问栈顶元素:
这里只需要注意top为指向的是有效数据的下一个位置,在取栈顶数据的时候将top-1,取得有效数据。
判断栈里是否有数据:
如果top等于零,栈中没有数据那么就条件为真返回true,反之返回false。
二:队列
队列的概念:
队列(Queue)是一种常见的数据结构,它遵循先进先出(FIFO,First In First Out)的原则,即最先进入队列的元素最先被取出。队列通常支持两种基本操作:入队(enqueue),即向队列尾部添加元素;出队(dequeue),即从队列头部移除元素。其他常见的操作还包括获取队列头部元素(peek)和判断队列是否为空(isEmpty)。
也就是说队列在插入数据的时候是在队尾进行插入,而在弹出数据的时候是在队头进行弹出。那么在实现队列的时候,数组和链表均可实现队列操作使⽤链表的结构实现更优⼀些,因为如果使⽤数组的结构,出队列在数组头上出数据,时间复杂度为O(N),效率会⽐较低,更推荐使用链表的形式。
队列的实现:
队列的形式:
第一步:还是先typedef int 命名为QDataType类型节约后续维护成本。
第二步:先定义队列节点结构,队列的底层为链表形式,所以在节点里定义一个next指针以及data数据。
第三步:定义队列结构,QNode*head里存放着链表的头节点地址,QNode*tail里存放着链表的尾节点地址,最后定义一个size变量用于表示的队列的长度。
队列的初始化以及销毁:
初始化队列:
第一步:
先对传进的队列结构体指针进行断言,如果指针为NULL,则进行报错;
第二步:
将队列的头指针与尾指针先初始化为NULL,并将size置为0;
销毁队列:
第一步:
先对传进的指针进行断言
第二步:
定义一个cur指针用来指向当前队列的头节点,由于队列的底层是顺序表,所以在销毁的时候需要单个节点依次销毁,使用while进行销毁
第三步:
重新将头节点以及尾节点置为NULL,防止野指针,并重新将size置为0;
入队:
第一步:
先对传进的指针进行断言,判断是否为NULL;
第二步:
申请新的节点,并将要插入的数据给newnode的值,并将newnode的next指针指向空。
第三步:
先对头指针进行判断,如果头指针为空,说明此时链表里没有节点,直接将头节点与尾节点指向新申请的节点。如果有节点,就将尾节点的next指针指向新节点,并重新将tail地址赋值为tail->next用来下次插入数据时,能快速找到尾节点,并同时对size++。
出队:
第一步:
将pq指针以及pq->head指针进行断言,如果有一个为空进行报错
接下来将size进行断言,如果size为0,表示队列里没有数据,无法进行删除操作,进行报错。
第二步:
判断头节点的next指针是否为空,表示只有一个节点的话需要进行特殊处理,直接将头节点释放,并将头节点与尾巴节点同时置为NULL。
如果是多个节点的话,由于这里是队列形式,队列想出数据只能从头部弹出,所以定义cur指针保存头节点->next节点,接着将头节点释放,并让头节点重新指向cur节点,最后将size--。
返回队列的长度以及判断队列是否为空:
返回队列头数据及返回队列尾数据: