栈(Stack)
①栈的概念:
栈是一种特殊的线性表,只允许在固定的一段进行插入、删除操作。进行插入、删除的一端称为栈顶,另一端称为栈底。遵守“先进后出”的规则。
入、出数据都在栈顶,入数据称为压栈/入栈/进栈,出数据称为出栈。
“数据和栈”与“子弹和弹匣”的关系一致,均是先进后出 。
②栈的实现:
如何去实现栈呢?数组与链表相比更简单,且可通过下标访问,所以我们用数组实现。
typedef int STDataType;//将int 重命名,方便更改类型
typedef struct Stack
{
STDataType* _a; //指向数组的指针
int _top; // 栈顶
int _capacity; // 容量
}Stack;
注:容量是申请的空间大小
// 初始化栈
void StackInit(Stack* ps)
{
assert(ps);
ps->_a = NULL;
ps->_top = -1;//注:这里写的是-1,不应该是0吗?(有说明的
ps->_capacity = 0;//初始空间随意
}
// 入栈
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
if ((ps->_top) + 1 == ps->_capacity)//判断数组空间大小是否足够
{
int newcapacity = ps->_capacity == 0 ? 10 : ps->_capacity * 2;//三目操作符
//每次扩充成原空间的两倍 ,研究表明扩两倍既可以减少扩充次数,还可以减少空间浪费
STDataType* tmp = realloc(ps->_a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
assert(realloc);
}
ps->_a = tmp;
ps->_capacity = newcapacity;
}
ps->_top++;//每次入数据,栈顶上升“1”
ps->_a[ps->_top] = data;
}
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
ps->_top--;//每次出数据,栈顶下降“1”
}
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
return ps->_a[ps->_top];
}
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->_top + 1;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps)
{
if (ps->_top == 0)
return 1;
else
return 0;
}
// 销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->_a);//注意:free(指针)是释放指针指向的空间
ps->_top = -1;
ps->_capacity = 0;
}
ps->_top = -1;//注:这里写的是-1,也可以是0,两者并不相同
总结:我们用顺序表的方式实现了栈这一数据结构,有先进后出的特点。
队列(Queue)
①认识队列
队列是一种可以“先进先出”的数据结构,也属于一种线性表,其只允许在队一端入数据(队尾),另一端删除数据(队头)
②实现队列
我们前面用数组实现了栈,而要实现队列“先进先出”,链表会更好。在数组头删数据,效率不高。
// 链式结构:表示队列
typedef int QDataType;
typedef struct QListNode
{
QDataType _data;
struct QListNode* _next;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* _front;
QNode* _rear;
int size;
}Queue;
我们可以想象一下没有Queue这一结构体,我们想要修改QNode中的数据就必须用二级指针(因为需要修改QNode中的数据,还需要修改指向QNode的指针),待我们引入QNode* _front;
QNode* _rear;就只需要传入一个Queue*类型的参数即可。(也用双指针实现了对队头、队尾的控制)
// 初始化队列
void QueueInit(Queue* q)
{
assert(q);//每一个函数起始处都断言一下,更好发现错误
q->_front = NULL;
q->_rear = NULL;
q->size = 0;//表示链表节点个数
}
// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return ;
}
else
{
newnode->_next = NULL;
newnode->_data = data;
}
if (q->_rear == NULL)//判断队列是否为空,如果是空,将头指针、尾指针均指向新节点
{
q->_front = newnode;
q->_rear = newnode;
}
else//如果不是空,头指针不动,将新旧节点连接后,尾指针指向新节点,
{
q->_rear->_next = newnode;
q->_rear = newnode;
}
q->size++;
}
// 队头出队列
void QueuePop(Queue* q)
{
assert(q);
assert(q->_front);
QNode*M = q->_front->_next;
free(q->_front);
q->_front = M;
if (q->_front == NULL)
q->_rear = NULL;
q->size--;
}
// 获取队列头部元素
QDataType QueueFront(Queue* q)
{
assert(q);
assert(q->_front);
return q->_front->_data;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{
assert(q);
assert(q->_rear);
return q->_rear->_data;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q)
{
assert(q);
return q->size;//有了size,则不需要遍历链表
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q)
{
assert(q);
if (q->_rear == NULL)
return 1;
else
return 0;
}
// 销毁队列
void QueueDestroy(Queue* q)
{
assert(q);
QNode* cur = q->_front;
while (cur)
{
QNode* nex = cur->_next;
free(cur);
cur = nex;
}
q->_front = q->_rear = NULL;
q->size = 0;
}
总结:用链表实现了队列,并使用双指针可以进行头和尾的删除、插入。