栈和队列学习
使用到的链表函数
- 链式队列和链式栈都用到了链表的一些函数,在这里声明
// 定义链表
struct Node {
int data;
struct Node* next;
};
// 创建链表头
struct Node* createList() {
struct Node* headNode = (struct Node*)malloc(sizeof(struct Node));
headNode->next = NULL;
return headNode;
}
// 创建节点 (和创建链表头基本相同)
struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
链式栈 (Stack)
- 栈:先进后出
- 和链表的表头插入法特别像 (和链表唯一的区别就是栈没有头节点)
链式栈的定义
- 链式栈和链表特别像
- 代码示例:
// 定义链表
struct Node {
int data;
struct Node* next;
};
// 定义栈
struct Stack {
struct Node* topStack;
int size;
};
- 在定义链表的外部新增栈顶指针 (topStack) 和 栈的元素个数 (size)
初始化栈
- 为栈分配内存空间,初始化栈的两个值
- 代码示例:
// 初始化栈
struct Stack* createStack() {
// 分配内存空间
struct Stack* newStack = (struct Stack*)malloc(sizeof(struct Stack));
// 初始化两个值
newStack->size = 0;
newStack->topStack = NULL;
return newStack;
}
入栈 (push)
-
栈比较特殊,他没有头节点,第一个入栈的相当于链表的最后一个元素 (很符合先进后出的原理)
-
然后类似于头插法,只不过没有头节点,所以只需要插入的元素指向下一个元素,不需要有其他元素指向它 (直到下一个元素要入栈时)
-
代码示例:
// 入栈操作(push)
void pushStack(struct Stack* stack,int data) {
// 新建一个节点
struct Node* newNode = createNode(data);
// 新节点指向上一个入栈的元素
newNode->next = stack->topStack;
// 栈顶指针指向新入栈的元素
stack->topStack = newNode;
// 栈的大小加一
stack->size++;
}
- 看这个图片应该能更好理解
读取栈顶元素
- 因为栈顶指针永远都指向栈顶,所以直接利用它就可以输出栈顶元素了
- 代码示例:
// 读取栈顶元素
int topStack(struct Stack* stack) {
// 栈不能为空
if (stack->size == 0)
return NULL;
return stack->topStack->data;
}
出栈 (pop)
- 类似于链表的删除,而且是只能从链表头开始删除的那种
- 删除步骤就是先将下一个元素的地址用一个中间变量指针保存起来,然后栈顶指针 (topStack) 指向下一个元素,然后 free 掉上一个节点就可以了
- 通过判断 size 的值来判断链表是否为空
- 代码示例:
// 出栈
void popStack(struct Stack* stack) {
if (stack->size == 0)
cout << "栈为空,无法出栈" << endl;
else
{
// 先临时保存下一个节点的地址
struct Node* nextNode = stack->topStack->next;
// free掉上一个节点
free(stack->topStack);
// 栈顶指针指向下一个节点
stack->topStack = nextNode;
nextNode = NULL;
stack->size--;
}
}
栈的主函数
- 测试栈的函数:
int main() {
struct Stack* myStack = createStack();
for (int i = 0; i < 5; i++) {
pushStack(myStack, i);
}
for (int i = 0; i < 5; i++) {
cout << topStack(myStack) <<"\t"<< endl;
popStack(myStack);
}
return 0;
}
// 输出结果
// 4
// 3
// 2
// 1
// 0
链式队列
- 队列:先进先出
- 和尾插法特别像 (同样没有头节点)
链式队列的定义
- 创建一个结构体,里面包括头节点,尾节点和队列长度
- 代码示例:
// 定义队列
struct Queue {
struct Node* frontNode; // 头节点
struct Node* tailNode; // 尾节点
int size; // 队列大小
};
创建队列
- 分配内存空间,并且给里面的数据赋初值
- 代码示例:
// 创建队列
struct Queue* createQueue() {
// 分配内存空间
struct Queue* myQueue = (struct Queue*)malloc(sizeof(struct Queue));
// 赋初值
myQueue->frontNode = myQueue->tailNode = NULL;
myQueue->size = 0;
return myQueue;
}
入队
- 入队分成两种情况,一种队为空时,另一种队不为空时
- 队为空时入队队里只有一个元素,这时头节点和尾节点都指向那唯一一个元素
- 对不为空时头节点指向头元素,尾节点指向尾元素
- 代码示例:
// 入队
void pushQueue(struct Queue* myQueue,int data) {
// 新建一个节点
struct Node* newNode = createNode(data);
// 如果队为0,那么头节点和尾节点都指向第一个入队的元素
if (myQueue->size == 0) {
myQueue->frontNode = myQueue->tailNode = newNode;
}
// 队里面有两个及以上的元素时,头节点不变,尾节点向后移动一位
else {
myQueue->tailNode->next = newNode;
myQueue->tailNode = newNode;
myQueue->size++;
}
// 队列长度加一
myQueue->size++;
}
出队
- 先把队头元素的下一个元素的地址储存起来,然后 free 掉队头元素 (frontNode指向的元素),然后队头指针 (frontNode) 指向刚开始储存的地址
- 代码示例:
// 出队
void popQueue(struct Queue* myQueue) {
if (myQueue->size == 0) {
cout << "栈为空" << endl;
}
else
{
// 暂存下一个元素地址
struct Node* nextNode = myQueue->frontNode->next;
free(myQueue->frontNode);
// 指向刚才储存的元素地址
myQueue->frontNode = nextNode;
myQueue->size--;
}
}
获取队顶元素
- 先判断一下队是否为空,然后返回 frontNode 指向的元素就可以了
- 代码示例:
// 获取队顶元素
int frontQueue(struct Queue* myQueue) {
if (myQueue->size == 0) {
cout << "栈为空" << endl;
return NULL;
}
else {
return myQueue->frontNode->data;
}
}
队列主函数
- 测试上面所写的函数
int main() {
struct Queue* myQueue = createQueue();
for (int i = 0; i < 5; i++) {
pushQueue(myQueue, i);
}
// myQueue = {0,1,2,3,4}
for (int i = 0; i < 5; i++) {
cout << frontQueue(myQueue) << endl;
popQueue(myQueue);
}
return 0;
}
// 输出结果:
// 0
// 1
// 2
// 3
// 4