栈是一种t的线性表,只允许在固定的一段进行插入和删除操作,进行插入、删除操作的叫特殊栈顶,另一端叫做栈底。栈的操作遵守后进先出的原则(last in first out)。
压栈:也就是输数据插入也叫做压栈,入栈、进栈,数据的操作在栈顶。
出栈:也就是数据删除也叫做出栈,数据的操作也在栈顶。
在实际的内存中,栈是向下生长的。
线性表有两种结构,顺序表和链表,具体该选用哪一种作为栈呢。
栈的特性是数据的操作只在栈顶,不需要任意部分的插入和删除,所以顺序表更合适,因为顺序表在尾部的操作相率很高,而且缓存命中率高。
定义一个栈
typedef int STDataType;
typedef struct ST
{
STDataType * st; //数据存储的内容
int top; //已经存入的数据
int capacity; //空间容量
}ST;
栈的初始化
void StackInit(ST * pst)
{
assert(pst);
pst->st = NULL;
pst->top = pst->capacity = 0;
//初始化时可以给st开辟空间也可以不开辟。
}
栈的释放
void StackDestory(ST * pst)
{
assert(pst);
free(pst->st);
pst->st = NULL;
pst->top = pst->capacity = 0;
}
压栈
void StackPush(ST * pst,STDataType x)
{
assert(pst);
//开辟空间
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType * newStack = (STDataType *)realloc(pst->st, newcapacity*sizeof(STDataType));
if (newStack == NULL)
{
perror("STInit realloc is error");
exit(-1);
}
pst->st = newStack;
pst->capacity = newcapacity;
}
pst->st[pst->top] = x;
pst->top++;
}
出栈
出栈并不是真的修改顺序表中的数据,只是将top前移一位。
void StackPop(ST * pst)
{
assert(pst);
//修改之前,需要对栈进行判空,当栈中没有元素时,不需要弹出元素。
assert(!StackEmpty(pst));
pst->top--;
}
栈的打印
//打印,注意打印栈实际上就是在弹出栈,当栈不为0时,从栈顶开始不断弹出元素
void StackPrint(ST * pst)
{
assert(pst);
while (pst->top)
{
pst->top--;
printf("%d->", pst->st[pst->top]);//先打印栈顶元素。
}
printf("\n");
}
返回栈顶元素
STDataType StackTop(ST * pst)
{
assert(pst);
assert(!StackEmpty(pst));
return pst->st[pst->top - 1];
}
判断栈是否为空
//判空
//判空需要返回两个值,为空,不为空,所以bool类型最合适。
//为空时返回true 不为空返回false
bool StackEmpty(ST * pst)
{
assert(pst);
return pst->top==0;
}
栈中元素个数
int StackSize(ST * pst)
{
assert(pst);
return pst->top;
}
队列与栈不同,队列是从一段插入数据,另一端删除数据,队列遵守的是先进先出的顺序。
入队:插入数据的一端是队尾。
出队:删除数据的一端是队头。
队列定义
队列应该用什么实现呢?
队列的特性要求在两端进行操作,如果使用顺序表,在数组前操作是时间复杂度为O(n),使用链表在插入删除时会非常简单。
需要注意,为了让队列更节省时间,减少对队列的遍历,需要定义两个结构体,一个结构体是队列的节点,另一个结构体是队列包含两个结构体变量,指向队头和队尾的指针。
typedef int QDataType;
typedef struct Node
{
QDataType val;
struct Node* next;
}Node;
typedef struct Queue
{
Node *head;
Node *tail;
int size;
}Queue;
队列初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail=NULL;
pq->size = 0;
}
队列释放
void QueueDestory(Queue* pq)
{
assert(pq);
Node* cur = pq->head;
while (cur)
{
Node* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
入队
void QueuePush(Queue* pq,QDataType val)
{
assert(pq);
Node * newnode = (Queue*)malloc(sizeof(Node));
if (newnode==NULL)
{
perror("Node malloc fail\n");
exit(-1);
}
else
{
newnode->val = val;
newnode->next = NULL;
}
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
入队时需要对队头队尾进行判断,当队头队尾为NULL时,说明队列中没有节点,队头与队尾指向新节点,如果不 为空说明队列中已经有元素,只改变队尾指针即可。
出队
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));//判空
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
Node* del = pq->head;
pq->head = pq->head->next;
free(del);
del = NULL;
}
pq->size--;
}
出队需要对队列进行判空,如果队头队尾都为空,不能出队,出队有个特殊情况,当队头地址与队尾地址相等时,需要将队尾队头指针置空,其余情况改变队头指针即可。
打印
void QueuePrint(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
Node *cur = pq->head;
while (cur)
{
printf("%d->", cur->val);
Node * del = cur;
cur = cur->next;
free(del);
del = NULL;
}
pq->head = pq->tail=NULL;
printf("\n");
}
队头数据
//队头数据
QDataType QueueFrontData(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->val;
}
队尾数据
//队尾数据
QDataType QueueBackData(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->val;
}
判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return (pq->head == NULL&&pq->tail == NULL);
}
队列长度
//队列长度
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
栈 | 队列 | |
数据结构 | 多使用顺序表 | 链表 |
进出方向 | 后进先出 | 先进先出 |
效率 | 栈顶数据操作效率高 | 队头队尾指针确定可以直接操作效率高 |
空间 | 开辟空间消耗大,存在浪费 | 不存在空间概念,用多少开多少,但是数据量大时,总体比栈更浪费 |