引言
基于上一篇博客讲述了链表建立的头插法和尾插法,所以这里先介绍链栈和链式队列,因为在头插法和尾插法的时候就分析了不同方法建立出来的数据的特点:一个逆序一个正序,基于这个特点来实现链栈和链式队列。
链栈
用链表的存储结构来实现栈的特点,栈有着先进后出的特点,元素只能栈顶入栈栈顶出栈,也就是说我先输入的数字,输出的时候却是在最后才能输出。联系上篇博客的头插法输入输出逆序的特点,我们可以想到链栈用头插法来实现压栈。
下面直接上代码
代码和运行结果
#include <stdio.h>
#include <malloc.h>
#include <stdbool.h>
#include <windows.h>
/*
内容:链栈的实现
功能:压栈、入栈、判空、清空、销毁
*/
typedef struct stacknode
{
int data;
struct stacknode *pNext;
} STACKNODE, *pSTACKNODE;
pSTACKNODE init_stack();
bool push_stack(pSTACKNODE, int); //压栈
bool is_empty(pSTACKNODE); //判空 (栈顶指针指向栈底元素就认为栈为空)
bool pop_stack(pSTACKNODE, int *);//出栈
bool destroy_stack(pSTACKNODE);: //销毁 (内存被释放认为是销毁)
void dump_stack(pSTACKNODE); //清空 (栈顶指针指向栈底元素就认为是清空完成)
int main()
{
pSTACKNODE pStack;
int val;
//初始化
pStack=init_stack();
//压栈
push_stack(pStack, 1);
printf("%d has been pushed into stack\n", pStack->pNext->data);
push_stack(pStack, 2);
printf("%d has been pushed into stack\n", pStack->pNext->data);
push_stack(pStack, 3);
printf("%d has been pushed into stack\n", pStack->pNext->data);
push_stack(pStack, 4);
printf("%d has been pushed into stack\n", pStack->pNext->data);
//出栈
pop_stack(pStack, &val);
printf("%d has been popped\n", val);
//清空
dump_stack(pStack);
//销毁
destroy_stack(pStack);
return 0;
}
pSTACKNODE init_stack() //栈顶指针初始化
{
pSTACKNODE p;
p = (pSTACKNODE)malloc(sizeof(STACKNODE));
if(p==NULL) //动态内存分配失败返回空
{
return NULL;
}else
{
p->pNext = NULL; //防止野指针生成
return p;
}
}
bool push_stack(pSTACKNODE pHead, int val) //压栈
{
pSTACKNODE pNew=(pSTACKNODE)malloc(sizeof(STACKNODE));
if(pNew==NULL)
{
return false;
}else
{
pNew->data = val; //头插法的核心过程
pNew->pNext = NULL;
pNew->pNext = pHead->pNext;
pHead->pNext = pNew;
return true;
}
}
bool is_empty(pSTACKNODE pHead) //判断栈是否为空
{
if(pHead->pNext==NULL)
{
printf("the stack is empty!\n");
return true;
}else
{
return false;
}
}
bool pop_stack(pSTACKNODE pHead,int *val) //栈顶元素出栈
{
if(is_empty(pHead))
{
return false;
}else{
pSTACKNODE q = pHead->pNext; //令q=首节点,便于之后free
*val = q->data; //存储删除节点的值
pHead->pNext = q->pNext; //第二个节点接在头节点之后,第二个节点变成第一个节点
free(q); //释放分配的动态内存
q = NULL; //把指针置空
}
}
void dump_stack(pSTACKNODE pHead)
{
pSTACKNODE q = pHead->pNext;
printf("It's time to dump the stack\n");
while(q!=NULL)
{
printf("%d\t", q->data);
q = q->pNext;
}
printf("\n");
}
bool destroy_stack(pSTACKNODE pHead)
{
pSTACKNODE q = pHead->pNext;
printf("It's time to destroy the stack\n");
if(is_empty(pHead))
{
return false;
}else{
while (q != NULL)
{
free(q);
pHead->pNext = q->pNext;
}
free(pHead);
q = NULL;
}
}
根据运行结果可以看到,4最晚进入但是第一个就被弹出。
过程图片
如果用尾插法的话需要将栈顶指针移动指向新插入的节点,而头插法的栈顶指针始终指向新插入的节点
链式队列
用链表的存储结构来实现队列的特点,栈有着先进先出的特点,也就是说我第一个输入的数字,输出的时候也是第一个输出。联系上篇博客的尾插法输入输出正序的特点,我们可以想到链栈用头插法来实现压栈。
下面直接上代码
代码和运行结果
#include<stdio.h>
#include<windows.h>
#include<stdbool.h>
#include<malloc.h>
typedef struct queuenode
{
int data;
struct queuenode *pNext;
} node, *pnode;
typedef struct QUEUE
{
pnode p_head;
pnode p_rear;
} Queue, *pQueue;
pQueue init_queue();
bool is_empty(pQueue);
bool en_queue(pQueue,int);
bool out_queue(pQueue, int *);
void dump_queue(pQueue); //清空队列并输出值
bool destroy_queue(pQueue);
int main()
{
pQueue pQ;
int val1,val2;
pQ = init_queue();
en_queue(pQ,1);
en_queue(pQ, 2);
en_queue(pQ, 3);
en_queue(pQ, 4);
out_queue(pQ, &val1);
printf("out queue:the first one is %d\n", val1);
out_queue(pQ, &val2);
printf("out queue:the second one is %d\n", val2);
printf("It's the time to dump the queue\n");
dump_queue(pQ);
destroy_queue(pQ);
}
pQueue init_queue()
{
pQueue p = (pQueue)malloc(sizeof(Queue));
if(p==NULL)
{
return NULL;
}
else
{
p->p_head = NULL;
p->p_rear = NULL;
return p;
}
}
bool en_queue(pQueue pQ,int val)
{
pnode pNew = (pnode)malloc(sizeof(node));
pNew->pNext = NULL;
if(pNew == NULL)
{
return false;
}else
{
pNew->data = val;
if(pQ->p_head==NULL) //要注意这个判断条件,初始化的时候让head和rear都指向了一个空节点
{
pQ->p_head = pNew; //所以第一次入队要让head和rear都指向第一个节点,即把New的地址发给head和rear
}else
{ //如果不是指向空节点,就可以直接进行else后面的步骤
pQ->p_rear->pNext = pNew; //尾插法关键步骤
}
pQ->p_rear = pNew; //第一次是让rear指向第一个节点,之后操作都是更新rear指针,指向最 后一个元素.
return true;
}
}
bool is_empty(pQueue pQ)
{
if(pQ->p_head == NULL) //判空条件应该是pQ->p_head==NULL,而不是pQ->p_head->pNext=NULL,否则到
{ //由于出队是先判空再删除,最后一个节点的时候先判空,再删除节点实际上最后一个节点还没删除就认为是空队列了.
printf("The queue is empty!\n");
return true;
}
else
{
return false;
}
}
bool out_queue(pQueue pQ, int *val) //出队
{
*val = pQ->p_head->data; //保存出队的值
if(is_empty(pQ))
{
return false;
}else{
pnode p = pQ->p_head;
pQ->p_head = p->pNext;//也可以直接写成 pQ->p_head=pQ->p_head->pNext
free(p);
p=NULL;
return true;
}
}
void dump_queue(pQueue pQ) //清空队列并输出值
{
while(pQ->p_head!=NULL)
{
printf("%d\t", pQ->p_head->data);
pQ->p_head = pQ->p_head->pNext;
}
}
bool destroy_queue(pQueue pQ) //销毁队列
{
pnode q = pQ->p_head;
if(q==NULL)
{
return false;
}else
{
q = q->pNext;
free(q);
return true;
}
q = NULL;
}
从结果可以看出1、2先进入队列同时出队也是1、2先出队
过程图片
总结
通过链表的两种创建方式头插法和尾插法特点来建立链栈和链式队列在我眼里有两点好处
1.符合我们一开始学习的链表:元素的出都是从头结点开始遍历输出,不会有较大的思维转换
2.基于1的特点,可以减轻对指针移动不清楚的压力也使代码更简洁