栈和队列
栈和队列是两种重要的线性结构,从数据结构角度看,栈和队列也是线性表,其特殊性在于栈和队列的基本操作是线性表操作的子集,他们是操作受限的线性表,因此可称为限定性的数据结构。但从数据类型角度看,他们是和线性表大不相同的两类重要的抽象数据类型。
思想架构
如果把物质层面的人体比作数据存储的物理结构,那么精神层面的 人格则是数据存储的逻辑结构。逻辑结构是抽象的概念,它依赖于物理 结构而存在。栈和队列。这两者都属于逻辑 结构,它们的物理实现既可以利用数组,也可以利用链表来完成。
栈
概念及结构
---------后进先出
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
实现
- 数组方式实现
1、初始化(申请空间)
2、进栈(尾插)
3、出栈(尾删)
4、数据打印
5、数据销毁(内存释放)
//顺序栈
typedef struct SeqStack
{
StackElemType *base; //
size_t capacity;
int top;
}SeqStack;
bool IsFull(SeqStack *pst)
{return pst->top >= pst->capacity;}
bool IsEmpty(SeqStack *pst)
{return pst->top == 0;}
void SeqStackInit(SeqStack *pst, int sz)
{
assert(pst);
pst->capacity = sz > SEQSTACK_DEFAULT_SIZE ? sz : SEQSTACK_DEFAULT_SIZE;
pst->base = (StackElemType*)malloc(sizeof(StackElemType) * pst->capacity);
assert(pst->base != NULL);
pst->top = 0;
}
void SeqStackPush(SeqStack *pst, StackElemType x)
{
assert(pst);
if(IsFull(pst))
{
printf("栈空间已满,%d不能入栈\n", x);
return;
}
pst->base[pst->top++] = x;
}
void SeqStackPop(SeqStack *pst)
{
assert(pst);
if(IsEmpty(pst))
{
printf("栈已空,不能出栈.\n");
return;
}
pst->top--;
}
StackElemType SeqStackTop(SeqStack *pst)
{
assert(pst && !IsEmpty(pst));
return pst->base[pst->top-1];
}
void SeqStackShow(SeqStack *pst)
{
assert(pst);
for(int i=pst->top-1; i>=0; --i)
printf("%d\n", pst->base[i]);
}
void SeqStackDestroy(SeqStack *pst)
{
assert(pst);
free(pst->base);
pst->base = NULL;
pst->capacity = pst->top = 0;
}
- 链表方式实现
1、初始化(为链表头指针置空,这里采用无头链表)
2、进栈(头插)
3、出栈(头删)
4、数据打印
5、数据销毁(内存回收)
typedef struct LinkStackNode
{
StackElemType data;
struct LinkStackNode *next;
}LinkStackNode;
typedef struct LinkStack
{
LinkStackNode *head;
}LinkStack;
void LinkStackInit(LinkStack *pst)
{
assert(pst);
pst->head = NULL;
}
void LinkStackPush(LinkStack *pst, StackElemType x)
{
assert(pst);
LinkStackNode *s = (LinkStackNode*)malloc(sizeof(LinkStackNode));
assert(s != NULL);
s->data = x;
s->next = pst->head;
pst->head = s;
}
void LinkStackPop(LinkStack *pst)
{
assert(pst);
if(pst->head != NULL)
{
LinkStackNode *p = pst->head;
pst->head = p->next;
free(p);
}
}
StackElemType LinkStackTop(LinkStack *pst)
{
assert(pst && pst->head != NULL);
return pst->head->data;
}
void LinkStackShow(LinkStack *pst)
{
assert(pst);
LinkStackNode *p = pst->head;
while(p != NULL)
{
printf("%d\n", p->data);
p = p->next;
}
}
void LinkStackDestroy(LinkStack *pst)
{
assert(pst);
while(pst->head != NULL)
{
LinkStackNode *p = pst->head;
pst->head = p->next;
free(p);
}
}
对比分析
内存空间:数组形式存储是一次性开辟好空间,这样既可能出现空间不够用和空间浪费两种情况,而链式存储是采取插入一个数据开辟一个空间的存储机制,故链式存储对空间的掌控更加灵活。
时间复杂度:入栈和出栈只会影响到最后一个元 素,不涉及其他元素的整体移动,所以无论是以数组还是以链表实 现,入栈、出栈的时间复杂度都是O(1)。
队列
概念及结构
---------先进先出
如上图,车队驶入单行隧道,要想让车辆驶出隧道,只能按照它们驶入隧道的顺序,先驶 入的车辆先驶出,后驶入的车辆后驶出,任何车辆都无法跳过它前面的 车辆提前驶出。
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出
FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头
实现
- 链表方式实现
1、初始化(使用front和rear指针分别维护队列的头部和尾部)
2、入队(尾插);出队(头删)
3、数据打印
4、队首队尾数据获取
5、队列摧毁(释放空间)
//链队列
typedef struct LinkQueueNode
{
QueueElemType data;
struct LinkQueueNode *next;
}LinkQueueNode;
typedef struct LinkQueue
{
LinkQueueNode *head;
LinkQueueNode *tail; //指向队尾节点
}LinkQueue;
void LinkQueueInit(LinkQueue *pq);
void LinkQueueDestroy(LinkQueue *pq);
void LinkQueueEn(LinkQueue *pq, QueueElemType x);
void LinkQueueDe(LinkQueue *pq);
void LinkQueueShow(LinkQueue *pq);
QueueElemType LinkQueueBack(LinkQueue *pq);
QueueElemType LinkQueueFront(LinkQueue *pq);
void LinkQueueInit(LinkQueue *pq)
{
assert(pq);
pq->head = pq->tail = NULL;
}
void LinkQueueEn(LinkQueue *pq, QueueElemType x)
{
assert(pq);
LinkQueueNode *s = (LinkQueueNode *)malloc(sizeof(LinkQueueNode));
assert(s != NULL);
s->data = x;
s->next = NULL;
if(pq->head == NULL)
pq->head = pq->tail = s;
else
{
pq->tail->next = s;
pq->tail = s;
}
}
void LinkQueueDe(LinkQueue *pq)
{
assert(pq);
if(pq->head != NULL)
{
LinkQueueNode *p = pq->head;
pq->head = p->next;
if(pq->head == NULL)
pq->tail = NULL;
free(p);
}
}
void LinkQueueShow(LinkQueue *pq)
{
assert(pq != NULL);
LinkQueueNode *p = pq->head;
while(p != NULL)
{
printf("%d<--", p->data);
p = p->next;
}
printf("Nil.\n");
}
QueueElemType LinkQueueBack(LinkQueue *pq)
{
assert(pq && pq->head);
return pq->tail->data;
}
QueueElemType LinkQueueFront(LinkQueue *pq)
{
assert(pq && pq->head);
return pq->head->data;
}
void LinkQueueDestroy(LinkQueue *pq)
{
assert(pq);
while(pq->head != NULL)
{
LinkQueueNode *p = pq->head;
pq->head = p->next;
free(p);
}
pq->head = pq->tail = NULL;
}
- 数组方式实现
以插入数据容量8为例
入队:每插入一个数据,rear++;rear是要插入数据的位置
出队:每删除一个数据,front++;
问题:
如果像这样不断出队,队头(front)左边的空间 失去作用,那队列的容量岂不是越来越小了?于是便通过循环队列来避免这一问题。
- 循环队列
//顺序队列 --> 循环队列
#define QUEUE_DEFAULT_SIZE 8
typedef struct SeqQueue
{
QueueElemType *base;
size_t capacity;
int front;
int rear;
}SeqQueue;
void SeqQueueInit(SeqQueue *pq, int sz);
void SeqQueueDestroy(SeqQueue *pq);
void SeqQueueEn(SeqQueue *pq, QueueElemType x);
void SeqQueueDe(SeqQueue *pq);
QueueElemType SeqQueueBack(SeqQueue *pq);
QueueElemType SeqQueueFront(SeqQueue *pq);
void SeqQueueShow(SeqQueue *pq);
void SeqQueueInit(SeqQueue *pq, int sz)
{
assert(pq);
pq->capacity = sz > QUEUE_DEFAULT_SIZE ? sz : QUEUE_DEFAULT_SIZE;
pq->base = (QueueElemType*)malloc(sizeof(QueueElemType) * (pq->capacity+1));
assert(pq->base != NULL);
pq->front = pq->rear = 0;
}
void SeqQueueDestroy(SeqQueue *pq)
{
assert(pq);
free(pq->base);
pq->base = NULL;
pq->capacity = pq->front = pq->rear = 0;
}
void SeqQueueEn(SeqQueue *pq, QueueElemType x)
{
assert(pq);
if((pq->rear+1)%(pq->capacity+1) == pq->front)
{
printf("队列已满, %d 不能入队.\n", x);
return;
}
pq->base[pq->rear] = x;
pq->rear = (pq->rear+1) % (pq->capacity+1);
}
void SeqQueueDe(SeqQueue *pq)
{
assert(pq);
if(pq->front == pq->rear)
{
printf("队列已空,不能出队.\n");
return;
}
pq->front = (pq->front+1) % (pq->capacity+1);
}
QueueElemType SeqQueueBack(SeqQueue *pq)
{
assert(pq && (pq->front!=pq->rear));
return pq->base[(pq->rear-1+pq->capacity+1) % (pq->capacity+1)];
}
QueueElemType SeqQueueFront(SeqQueue *pq)
{
assert(pq && (pq->front!=pq->rear));
return pq->base[pq->front];
}
void SeqQueueShow(SeqQueue *pq)
{
assert(pq);
for(int i=pq->front; i!=pq->rear; )
{
printf("%d<--", pq->base[i]);
i = (i+1)%(pq->capacity+1);
}
printf("Nil.\n");
}
这样将队列循环起来即可将出队的空间再次利用,为了区分空队列和队列已满的情况,通常多余开辟一个空间,把队头下标==队尾下标作为空队列情况,而把(队尾下标+1)%数组长度 = 队头下标作为队列已满的情况。这样即可通过数组方式有效的完成队列的实现。