栈和队列在数据结构和算法中算是比较基础的知识,两者之间有着相似之处,也有各自的特点。今天我们来看一下栈和队列的基本概念和代码操作。
目录
一.栈
1.栈的基本概念
栈(Stack)是一种特殊的线性表,是限定只在一端进行插入与删除的线性表。
在栈中,一端是封闭的,既不能插入元素,也不能删除元素;而另一端是开口的,既能插入元素,也能删除元素。通常称插入、删除的这一端为“栈顶”(top),另一端为“栈底”(bottom)。
栈的特点:(1).栈只能从栈顶进行插入和删除。
(2).栈的修改原则:先进后出或后进先出。
(3).栈底指针不变,栈中元素随着栈顶指针变化而变化。
2.栈的相关代码操作
(1).栈的顺序存储结构
typedef struct
{
Elemtype *base; //指向栈底的指针
Elemtype *top; //指向栈顶的指针
int stackSize; //表示栈当前可使用的最大容量
}sqStack;
(2).创建一个栈
#define STACK_INIT_SIZE 100 //宏定义栈的最大容量
initStack(sqStack *s)
{
s->base = (Elemtype *)malloc(STACK_INIT_SIZE * sizeof(Elemtype) ); //申请空间
if( !s->base ) //若申请失败则退出
exit(0);
s->top = s->base; //最开始(空栈)栈顶就是栈底
s->stackSize = STACK_INIT_SIZE;
(3).入栈操作
入栈操作要在栈顶进行,每次向栈中压入一个数据,top指针就要+1.直到栈满为止;特别要注意栈是否已满。
#define SATCKINCREMENT 10 //宏定义增长量为10
Push(sqStack *s,ElemType e)
{
if( s->top - s->base >= s->stackSize ) //如果栈满,则追加空间
{
s->base = (ElemType *)realloc(s->base,(s->stackSize + SATCKINCREMENT) * sizeof(ElemType)); //调用realloc函数追加空间
if( !s->base )
exit(0);
s->top = s->base + s->stackSize; //设置栈顶(栈底+当前容量)
s->stackSize = s->stackSize + SATCKINCREMENT; //设置栈的最大容量(原基础上+10)
}
*(s->top) = e;
s->top++;
}
(4).出栈操作
出栈操作就是在栈顶取出数据,栈顶指针随之下移的操作,每当从栈内取出一个元素后,栈的当前容量就-1。
Pop(sqStack *s,ElemType *e)
{
if( s-top ==s->base) //若栈已空,则返回
return;
*e = *--(s->top); 将栈的最上面的元素取出放到变量e中,并且top-1
}
二.队列
1.队列的基本概念
队列(queue)是指允许在一端进行插入操作,而在另一端进行删除操作的线性表。允许删除的一端称为队首(front),允许插入的一端称为队尾(rear)。
队列的特点:(1).队列只允许在队尾进行插入,队首进行删除。
(2).队列的修改原则:先进先出或后进后出。
(3).队列中的元素随着队尾指针和队首指针的变化而变化。
2.队列的相关代码操作
(1).队列的链式存储结构
typedef struct QNode {
ElemType data; //数据
struct QNode *next; //指向下一个元素的指针
} QNode, *QueuePrt;
typedef struct {
QueuePrt front, rear; //队头、尾指针
} LinkQueue;
(2).创建一个队列
创建一个队列要完成两个任务:一是在内存中创建一个头结点,二是将队列的头指针和尾指针都指向这个生成的头结点,因为此时是空队列。
initQueue(LinkQueue *q)
{
q->front=q->rear=(QueuePtr)malloc(sizeof(QNode)); //创建一个头结点并且使队列的头指针和尾指针指向该头结点
if(!q->front)
exit(0);
q->front->next = NULL; //此时为空队列
}
(3).入队列操作
主要是将最后一个元素指向下一个元素的指针由NULL变成新入元素的指针,并且将尾指针(rear)从指向最后一个元素变成指向新入队列的元素。
InsertQueue(LinkQueue *q,ElemType e) //队列q,新入队列元素e
{
QueuePtr p;
p = (QueuePtr)malloc(sizeof(QNode)); //生成新结点p,暂时存放元素e的值
if( p == NULL )
exit(0);
p->data = e;
p->next = NULL; //新元素的next要指向NULL
q->rear->next = p; //将队列的尾指针指向新元素的指针
q->rear = p; //将新入元素的指针赋给尾指针
}
(4).出队列操作
出队列操作是将队列中的第一个元素移出,队列头指针不发生改变,改变头结点的next指针即可。
DeleteQueue(LinkQueue *q,ElemType *e)
{
QueuePtr p; //生成一个结点p用来存放要出队列的元素并最后释放掉
if( q->front == q->rear ) //队列为空
return;
p = q-front->next;
*e = p->data;
q->front->next = p->next;
if( q->rear == p ) //队列只有一个元素
q->rear = q->front;
free(p); //释放结点p
}
3.循环队列的基本概念
循环队列就是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用。从队列首指针front指向的后一个位置到队列尾指针rear指向的位置之间所有的元素均为队列中的元素。循环队列的实现只需要灵活改变front和rear指针即可,也就是然后让front或rear指针不断加1,即使超出了地址范围,也会自动从头开始。我们可以采取取模运算来实现:
-(rear+1)%QueueSize
-(front+1)%QueueSize
取模就是取余数的意思,取模运算得到的值永远不会大于除数,随着被除数的增大,取模运算的值会一直循环下去。
(1).定义一个循环队列的操作
#define MAXSIZE 100
typedef struct
{
ElemType *base; //用于存放内存分配基地址
int front;
int rear;
}
(2).初始化一个循环队列
initQueue(cycleQueue *q)
{
q->base = (ElemType *)malloc(MAXSIZE *sizeof(ElemType)); //生成一个空间给基地址
if( !q->base )
exit(0);
q->front = q->rear = 0; //一开始为空队列
}
(3).入队列操作
InsertQueue(cycleQueue *q,ElemType e)
{
if((q->rear+1)%MAXSIZE == q->front) //队列已满
return;
q->base[q->rear] = e; //模拟数组,将e存入队列
q->rear = (q->rear+1)%MAXSIZE; //尾指针“加”1
}
(4).出队列操作
DeleteQueue(cycleQueue *q,ElemType *e)
{
if(q->front == q->rear) //队列为空
return;
*e=q->base[q->front]; //将要出队列的元素给e
q->front=(q->front+1)%MAXSIZE; //将头指针指向下一个
}
结尾:人之所以痛苦,在于追求错误的东西。如果你不给自己烦恼,别人也永远不可能给你烦恼。
希望我们能放弃追求某些东西,这样我们才会活得快乐。