一、栈
(一)栈的结构
用数组作为栈的结构存储数据,使用结构体用于约束数组,在结构体中定义两个指针指向栈的栈顶top和栈底base,要求只能从栈顶入栈和出栈(只能从栈的一端操作);定义一个整型变量stacksize用于表示当前栈的容量(可以存放数据的最大数量)。
typedef int SElemType;
typedef int Status;
typedef struct{
SElemType *base;//栈底
SElemType *top;//栈顶
int stacksize;//栈容量
}SqStack;
(二)初始化栈
Status InitSqStack(SqStack *S),传入参数为结构体指针(需要为结构体申请空间,所以需要传指针),在堆区动态分配一块连续内存作为栈的存储空间,并让栈顶指针和栈底指针都指向该空间的起始位置,表示当前栈为空。
//初始化
Status InitSqStack(SqStack *S){
//申请定长空间
S->base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
if(!S->base) exit(OVERFLOW); //申请失败
S->top=S->base;
S->stacksize=STACK_INIT_SIZE; //更新容量
return OK;
}
(三)入栈
Status Push(SqStack *S,SElemType e),将数据e入栈:先判断栈是否满了,如果是满栈则需要使用realloc对其扩容。realloc扩容如果后面有足够的空间可以实现原地扩展(直接在后面扩容),如果后面空间不够则需要另外开辟一段更大的空间,逐字节拷贝过去并自动释放旧内存。所以扩容后需要更新栈顶指针与容量大小。最后将入栈元素赋值给栈顶指针,赋值后更新栈顶指针(指向入站元素的下一个位置)。
//入栈
Status Push(SqStack *S,SElemType e){
if(S->top - S->base ==S->stacksize){//栈满扩容
S->base=(SElemType *)realloc(S->base,(S->stacksize + STACKINCREMENT)*sizeof(SElemType));
if(!S->base) exit(OVERFLOW);
//扩容可能会另外申请空间,地址会改变,所以需要更新栈顶指针
S->top=S->base + S->stacksize;//需要更新栈顶指针
S->stacksize+=STACKINCREMENT;//扩容后更新容量
}
*S->top++=e;//先赋值后移动top指针
return OK;
}
(四)出栈
Status Pop(SqStack *S,SElemType *e),出栈并将出栈数据存入e中:判断是否为空栈,非空则先将栈顶指针移动到栈顶元素,再将其存入e。
//出栈
Status Pop(SqStack *S,SElemType *e){
if(S->base == S->top) return ERROR; //空栈无元素
e=*--S->top; //先将栈顶指针移动到栈顶元素,再将其赋给e
return OK;
}
(五)查看栈顶指针
Status GetTop(SqStack *S,SElemType *e),查看栈顶指针并存入e:先判断是否为空栈,若非空则将栈顶指针的前一个元素(栈顶元素)的数据赋给e。
//查看栈顶元素
Status GetTop(SqStack *S,SElemType *e){
if(S->base == S->top) return ERROR; //空栈无元素
e=*(S->top -1); //先将栈顶指针的前一个元素赋给e(不改变栈顶指针)
return OK;
}
(六)判空
比较栈底指针和栈顶指针是否指向同一位置,是则为空栈,否则非空。
//判空
Status isEmpty(SqStack *S){
return S->base == S->top;
}
(七)清空
移动栈顶指针,使其和栈底指针指向同一位置(不需要处理数据),则表示已清空。
//清空
void ClearLine(SqStack *S){
while(S->top != S->base) --S->top;
}
完整测试代码:
#include <stdio.h>
#include <stdlib.h>
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
#define OK 1
#define ERROR 0
#define OVERFLOW -1
typedef int SElemType;
typedef int Status;
typedef struct{
SElemType *base;//栈底
SElemType *top;//栈顶
int stacksize;//栈容量
}SqStack;
//初始化
Status InitSqStack(SqStack *S){
//申请定长空间
S->base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
if(!S->base) exit(OVERFLOW); //申请失败
S->top=S->base;
S->stacksize=STACK_INIT_SIZE; //更新容量
return OK;
}
//入栈
Status Push(SqStack *S,SElemType e){
if(S->top - S->base ==S->stacksize){//栈满扩容
S->base=(SElemType *)realloc(S->base,(S->stacksize + STACKINCREMENT)*sizeof(SElemType));
if(!S->base) exit(OVERFLOW);
//扩容可能会另外申请空间,地址会改变,所以需要更新栈顶指针
S->top=S->base + S->stacksize;//需要更新栈顶指针
S->stacksize+=STACKINCREMENT;//扩容后更新容量
}
*S->top++=e;//先赋值后移动top指针
return OK;
}
//出栈
Status Pop(SqStack *S,SElemType *e){
if(S->base == S->top) return ERROR; //空栈无元素
*e=*--S->top; //先将栈顶指针移动到栈顶元素,再将其赋给e
return OK;
}
//查看栈顶元素
Status GetTop(SqStack *S,SElemType *e){
if(S->base == S->top) return ERROR; //空栈无元素
*e=*(S->top -1); //先将栈顶指针的前一个元素赋给e(不改变栈顶指针)
return OK;
}
//判空
Status isEmpty(SqStack *S){
return S->base == S->top;
}
//清空
void ClearLine(SqStack *S){
while(S->top != S->base) --S->top;
}
int main(){
SqStack S;
int e=0;
InitSqStack(&S);
Push(&S,1);
Push(&S,2);
Push(&S,3);
Push(&S,4);
printf("依次入栈:");
for(int i=0;S.base+i<S.top;i++){
printf("%d ",*(S.base+i));
}
Pop(&S,&e);//出栈
printf("\n出栈:%d\n",e);
printf("出栈顺序:");
for(int i=1;S.top-i>=S.base;i++){
printf("%d ",*(S.top-i));
}
if(isEmpty(&S)) printf("\n栈空\n");
else {
printf("\n栈非空,需要清空\n");
ClearLine(&S);
if(isEmpty(&S)) printf("已清空!\n");
}
}
二、队列
(一)队列的结构
循环队列,使用数组存储数据,使用结构体进行约束。结构体中有两个整型变量分别用于表示队头与队尾的下标,一个指针指向存储数据的数组首地址。
#define MAXSIZE 100
typedef int ElemType;
typedef int Status;
typedef struct{
ElemType *base; //指针指向数组
int front;//队头下标
int rear;//队尾下标
}SqQueue;
(二)初始化
在堆区动态分配一块连续内存作为队列的存储空间,并将队头指针front和队尾指针rear都初始化为 0,表示这是一个空队列。
Status InitQueue(SqQueue *Q){
Q->base=(ElemType *)malloc(MAXSIZE*sizeof(ElemType));
if(!Q->base) exit(OVERFLOW);
Q->front=Q->rear=0;
return OK;
}
(三)入队
先检查队列是否已满(检查队尾下标的下一个位置是否等于队头下标,使用取模运算%MAXSIZE实现指针的循环移动)。将新元素e存入队尾rear,此时rear的值为下一个空闲位置(即队尾)。将队尾下标rear加一,使用取模运算实现循环:当rear到达数组末尾时,自动回到数组开头,更新后的rear表示新的空闲位置的下标。
//入队
Status EnQueue(SqQueue *Q,ElemType e){
if((Q->rear+1)%MAXSIZE == Q->front) return ERROR;//队满
Q->base[Q->rear]=e; //入队赋值
Q->rear=(Q->rear+1)%MAXSIZE; //更新队尾下标
return OK;
}
(四)出队
先检查队列是否为空(检查队头下标是否等于队尾下标)。将队头指针front所指向的元素值取出并保存到e中。将队头下标front加一,使用取模运算%MAXSIZE实现循环:当front到达数组末尾时,自动回到数组开头,更新后的front表示新的队头位置。
//出队
Status DeQueue(SqQueue *Q,ElemType *e){
if(Q->front == Q->rear) return ERROR;//队空
*e=Q->base[Q->front];
Q->front=(Q->front+1)%MAXSIZE;
return OK;
}
(五)查看队首元素
先检查队列是否为空(检查队头下标是否等于队尾下标)。将队头指针front所指向的元素值取出并保存到e中。由于只是查看元素而不删除它,因此队头指针front保持不变,不需要移动。
//查看队首元素
Status GetFront(SqQueue *Q,ElemType *e){
if(Q->front == Q->rear) return ERROR;//队空
*e=Q->base[Q->front];
return OK;
}
(六)判断队列是否为空
//判断队列是否为空
Status isEmpty(SqQueue Q){
return Q.front==Q.rear;
}
顺序队列测试代码:
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define OVERFLOW -1
//队列的基本操作
#define MAXSIZE 100
typedef int ElemType;
typedef int Status;
typedef struct{
ElemType *base; //指针指向数组
int front;//队头下标
int rear;//队尾下标
}SqQueue;
//初始化
Status InitQueue(SqQueue *Q){
Q->base=(ElemType *)malloc(MAXSIZE*sizeof(ElemType));
if(!Q->base) exit(OVERFLOW);
Q->front=Q->rear=0;
return OK;
}
//入队
Status EnQueue(SqQueue *Q,ElemType e){
if((Q->rear+1)%MAXSIZE == Q->front) return ERROR;//队满
Q->base[Q->rear]=e; //入队赋值
Q->rear=(Q->rear+1)%MAXSIZE; //更新队尾下标
return OK;
}
//出队
Status DeQueue(SqQueue *Q,ElemType *e){
if(Q->front == Q->rear) return ERROR;//队空
*e=Q->base[Q->front];
Q->front=(Q->front+1)%MAXSIZE;
return OK;
}
//查看队首元素
Status GetFront(SqQueue *Q,ElemType *e){
if(Q->front == Q->rear) return ERROR;//队空
*e=Q->base[Q->front];
return OK;
}
//判断队列是否为空
Status isEmpty(SqQueue Q){
return Q.front==Q.rear;
}
int main(){
SqQueue Q;
int e=0;
InitQueue(&Q);
EnQueue(&Q,1);
EnQueue(&Q,2);
EnQueue(&Q,3);
printf("队列元素:");
for(int i=0;i<Q.rear;i++){
printf("%d ",Q.base[i]);
}
DeQueue(&Q,&e);
printf("\n出队:%d",e);
if(isEmpty(Q)) printf("\n队空\n");
else {
printf("\n队非空\n");
}
return 0;
}
链队列测试代码:
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define OVERFLOW -1
//链表队列的基本操作
typedef int ElemType;
typedef int Status;
typedef struct QNode{ //链队列结点
ElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct{ //结构
QueuePtr front; //队头指针,指向头结点(无数据)
QueuePtr rear; //队尾指针,指向队尾结点(有数据)
}LinkQueue;
//初始化
Status InitQueue(LinkQueue *Q){
Q->front=Q->rear=(QueuePtr)malloc(sizeof(QNode));
if(!Q->front) exit(OVERFLOW);
Q->front->next=NULL;
return OK;
}
//入队
Status EnQueue(LinkQueue *Q,ElemType e){
QueuePtr p=(QueuePtr)malloc(sizeof(QNode));
if(!p) exit(OVERFLOW);
p->data=e;
p->next=NULL;
Q->rear->next=p;
Q->rear=p;
}
//出队
Status DeQueue(LinkQueue *Q,ElemType *e){
if(Q->front == Q->rear) return ERROR;//空队
QueuePtr p=Q->front->next;//队头结点
*e=p->data;
Q->front->next=p->next;
//如果需要删除的结点为队尾结点,Q->front->next=p->next=NULL
if(p == Q->rear) Q->rear=Q->front; //置空
free(p);
return OK;
}
//查看队首元素
Status GetFront(LinkQueue *Q,ElemType *e){
if(Q->front == Q->rear) return ERROR;//空队
*e=Q->front->next->data;
return OK;
}
int main(){
LinkQueue Q;
int e=0;
InitQueue(&Q);
for (int i = 1; i <= 5; i++) {
printf("入队: %d\n", i);
EnQueue(&Q, i);
}
GetFront(&Q, &e);
printf("队首元素: %d\n", e);
for (int i = 0; i < 3; i++) {
DeQueue(&Q, &e);
printf("出队: %d\n", e);
}
return 0;
}
链队列与顺序队列有什么区别?各自的优缺点?
(1)链队列与顺序队列的本质区别在于存储方式:
顺序队列使用连续的内存空间(数组)存储元素,通过队头指针front和队尾指针rear的下标移动实现入队出队。
链队列则使用不连续的链表结点存储元素,通过front指针指向头结点、rear指针指向尾结点实现入队出队。
(2)顺序队列的优点是访问速度快(连续存储)、实现简单、无指针额外开销;缺点是队列容量固定(静态数组)或需要动态扩容(动态数组),且为了区分队空和队满通常需要牺牲一个存储单元,存在空间浪费。
(3)链队列的优点是容量动态增长,不会因队列满而溢出(除非内存耗尽),插入删除操作只需修改指针无需移动数据;缺点是每个结点需要额外的指针域存储空间,访问速度相对较慢(不连续存储),且频繁的malloc/free可能导致内存碎片。

155

被折叠的 条评论
为什么被折叠?



