栈和队列的基本概念及操作
Posted 蚍蜉撼树谈何易
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了栈和队列的基本概念及操作相关的知识,希望对你有一定的参考价值。
一、栈的定义及使用
栈的定义:栈是仅限于在表尾进行插入和删除操作的线性表。我们通常把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何元素的称为空栈。栈是一种后进先出的线性表,简称(Last In First Out),简称LIFO结构
二、栈的特性
2.1 地址分布来看:
栈顶地址最低,栈底的地址最高。
2.2栈的特殊之处在于限制了这个线性表的插入与删除位置,它始终只在栈顶进行。这也就使得:栈底是固定的,最先进栈的只能在栈底。
三、栈的应用场景
比如四则运算表达式的求值:
比如我们平常所使用的
9+(3-1)*3 +10/2
这就是我们平时所用的表达式。这里在计算机中可以理解为中缀表达式
在计算机就行运算的时候,它就会将这个运算式变为后缀表达式。也称为逆波兰表达式。
规则:从左至右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分,若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号则栈顶元素依次出栈并输出。
栈的应用–递归
比如我们通常所使用的那些递归函数,其本质就相当于是栈的操作。,比如斐波那契数列,其就是从栈顶到栈底依次出栈。还有链表的逆序打印,其本质也是栈的入栈与出栈操作,先进的反而后走。
在不改变原链表的情况下逆序打印链表
栈的定义
typedef int datatype1;
typedef struct Stack {
datatype1* val1;
int size;
int capacity;
}Stack;
链表的定义
typedef int datatype;
typedef struct list
{
datatype val;
struct list* next;
}Node;
typedef Node* LinkNode;
栈的初始化
void init1(Stack* s)
{
assert(s);
s->val1 = (datatype1*)malloc(sizeof(datatype1) * 5);
s->capacity = 5;
s->size = 0;
}
链表的初始化,这个链表是带头结点的,方便操作
LinkNode init()
{
LinkNode pheader = (LinkNode)malloc(sizeof(Node));
pheader->val = -1;
pheader->next = NULL;
return pheader;
}
栈的扩容
void extend(Stack* s)
{
if (s->size == s->capacity)
{
s->val1 = (datatype1*)realloc(s->val1, sizeof(datatype1) * s->capacity * 2);
if (s->val1 == NULL)
{
printf("内存扩容失败\\n");
return;
}
s->capacity *= 2;
}
}
链表内存开辟
LinkNode my_malloc(datatype val)
{
LinkNode newnode = (LinkNode)malloc(sizeof(Node));
newnode->next = NULL;
newnode->val = val;
return newnode;
}
链表的插入,默认的是头插,因为简单才这么插入
void insert(LinkNode pheader, datatype val)
{
if (pheader == NULL)
{
return;
}
LinkNode newnode = my_malloc(val);
newnode->next = pheader->next;
pheader->next = newnode;
}
入栈操作
void pushStack(Stack* s, datatype1 num)
{
assert(s);
extend(s);
s->val1[s->size++] = num;
}
出栈操作
void popStack(Stack* s)
{
if (is_empty(s))
{
return;
}
else
{
s->size--;
}
}
利用递归实现链表逆序打印
void show_bydigit(LinkNode pheader)参数传递是show_bydigit(pheader->next)
{
/*if (pheader)
{
show(pheader->next);
printf("%d\\n", pheader->val);
}
}
利用栈来实现,首先执行入栈操作,将链表有效元素依次压入栈中
void show(LinkNode pheader,Stack *s)
{
//这块是让pheader不为空的情况下,先让它入栈,最后再执行出栈,出栈会在后面给出
while (pheader)
{
pushStack(s, pheader->val);
pheader = pheader->next;
}
}
栈的判空操作:
bool is_empty(Stack* s)
{
assert(s);
if (s->size != 0)
{
return false;
}
return true;
}
利用栈来实现逆序打印操作
void stack_show(Stack* s)
{
//因为在上面已经给出了入栈操作,我们这时可以利用栈的特性先进后出来实现逆序打印
while (!is_empty(s))
{
printf("%d\\n", return_top(s));
popStack(s);
}
destory1(s);//当打印完成后,销毁栈
}
链表的销毁
void destory(LinkNode pheader)
{
if (pheader == NULL)
{
return;
}
LinkNode pcur = pheader->next;
while (pcur)
{
LinkNode pnext = pcur->next;
free(pcur);
pcur = pnext;
}
pheader->next = NULL;
}
栈的销毁:
void destory1(Stack* s)
{
assert(s);
if (s->val1 != NULL)
{
free(s->val1);
s->val1 = NULL;
}
}
测试用例:
void test_list()
{
LinkNode pheader = init();
insert(pheader, 5);
insert(pheader, 4);
insert(pheader, 3);
insert(pheader, 2);
insert(pheader, 1);
Stack s;
init1(&s);
show(pheader->next, &s);//这边是为了执行入栈操作,因为头结点不含有有效元素,所以直接从pheader->next开始
stack_show(&s);
destory(pheader);
}
四、栈的操作及相关函数
利用数组来模拟栈的实现
typedef int datatype;
typedef struct Stack {
datatype *val;//此时记录的是一个整形数组
int size;//当前栈的有效元素
int capacity;//栈的容量
}Stack;
4.1初始化栈
void init(Stack* s)
{
assert(s);
s->val = (datatype*)malloc(sizeof(datatype) * 5);
s->capacity = 5;
s->size = 0;
}
4.2栈的扩容。假如说栈满的话,必须对其执行扩容操作方可插入
void extend(Stack* s)
{
if (s->size == s->capacity)
{
s->val = (datatype *)realloc(s->val,sizeof(datatype)* s->capacity * 2);//这边利用realloc函数进行栈的扩容,默认情况下扩容其当前容量的两倍
if (s->val == NULL)
{
printf("内存扩容失败\\n");
return;
}
s->capacity *= 2;
}
}
4.3入栈操作
void pushStack(Stack* s, datatype val)
{
assert(s);
extend(s);//目的是为了避免栈的溢出
s->val[s->size++] = val;
}
4.4出栈操作
void popStack(Stack* s)
{
if (is_empty(s))//首先对栈是否为空进行检测,避免错误出栈,造成越界访问
{
return;
}
else
{
s->size--;
}
}
4.5返回栈顶元素
datatype return_top(Stack* s)
{
if (is_empty(s))
{
return;
}
else
{
return s->val[s->size - 1];//因为栈顶始终指向的是栈顶元素的上一个,所以-1之后才是它的第一个有效元素
}
}
4.6返回栈的有效元素个数
int return_num(Stack* s)
{
assert(s);
return s->size;
}
4.7判断栈是否为空
bool is_empty(Stack* s)
{
assert(s);
if (s->size != 0)
{
return false;
}
return true;
}
4.8栈的销毁,因为涉及到在堆上开辟空间,所以必须对该空间进行销毁
void destory(Stack* s)
{
assert(s);
if (s->val != NULL)
{
free(s->val);
s->val = NULL;
}
}
4.9栈的测试
void test01()
{
Stack s;
init(&s);
pushStack(&s, 5);
pushStack(&s, 4);
pushStack(&s, 3);
pushStack(&s, 2);
pushStack(&s, 1);
pushStack(&s, 1);
printf("栈顶元素为%d\\n", return_top(&s));
printf("元素的有效个数为%d\\n", return_num(&s));
popStack(&s);
popStack(&s);
popStack(&s);
printf("栈顶元素为%d\\n", return_top(&s));
printf("元素的有效个数为%d\\n", return_num(&s));
}
五、栈的注意事项
首先栈怎么遍历?
栈是不可以被遍历的,因为栈是单方向操作的线性表,只可以在栈顶进行插入与删除。
一、队列的定义及使用
队列的定义:什么是队列?
队列就比如你比如你去食堂买饭一样,先到食堂的人肯定排在前面,当他买到饭后就会端走(出队列),后到的人只可以排在队尾(入队列)。
定义:队列是只允许在一段进行插入操作,而在另一端进行删除操作的线性表,队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的那一端称为队尾,允许删除的一端称为队头。
二、队列的模拟实现
因为队列是头部去除的情况,使用数组的话会牵扯到元素的搬移,空间复杂度较高,所以我们采用链表来模拟队列
定义相应的元素及指针
typedef int datatype;
typedef struct queue
{
datatype val;
struct queue* next;//指向下一个元素
}Qnode;
typedef struct s1
{
Qnode* front;//用来维护它的队头
Qnode* back;//用来维护它的队尾
}Queue;
初始化函数
void init(Queue* p)
{
assert(p);
p->back = p->front = my_malloc(0);//该单链表带头结点,首先让维护头结点和尾结点的指针都指向头结点
}
开辟空间函数
Qnode* my_malloc(datatype val)
{
Qnode* newnode = (Qnode*)malloc(sizeof(Qnode));
newnode->val=val;
newnode->next = NULL;
return newnode;
}
入队操作
void push_queue(Queue* p,datatype val)
{
assert(p);
p->back->next = my_malloc(val);//让它的尾结点的next指向新开辟的结点
p->back = p->back->next;//同时更新尾结点
}
出队操作
void pop_queue(Queue* p)
{
Qnode* del = NULL;
assert(p);
//分两种情况,一个是删完后没有有效元素,一个是有的
if (!is_empty(p))
{
del = p->front->next;
p->front->next = del->next;
free(del);
//这里需要确定是不是出队列后就没有有效元素了
if (p->front->next == NULL)
{
p->back = p->front;//让back重新指向头结点
}
}
else
{
return;
}
}
判断队列是否为空
bool is_empty(Queue* p)
{
return p->front == p->back;//若此时指针同一指向,则表示没有有效元素
}
返回队头与队尾元素
//获取队头元素
datatype return_top(Queue* p)
{
assert(!is_empty(p));
return p->front->next->val;
}
//获取队尾元素
datatype return_back(Queue* p)
{
assert(!is_empty(p));
return p->back->val;
}
返回元素的有效个数
int return_value(Queue* p)
{
assert(p);
Qnode* cur = p->front->next;
int count = 0;
while (cur)
{
++count;
cur = cur->next;
}
return count;
}
销毁队列
void destory(Queue* p)
{
Qnode* del = p->front;
while (del)
{
p->front = del->next;
free(del);
del = p->front;
}
p->back = p->front = NULL;
}
测试用例:
void test()
{
Queue p;
init(&p);
push_queue(&p, 1);
push_queue(&p, 2);
push_queue(&p, 3);
push_queue(&p, 4);
push_queue(&p, 5);
printf("当前有效元素为%d\\n", return_value(&p));
printf("队头元素为%d\\n", return_top(&p));
printf("队尾元素为%d\\n", return_back(&p));
printf("当前队列是否为空%d\\n", is_empty(&p));
pop_queue(&p);
pop_queue(&p);
printf("当前有效元素为%d\\n", return_value(&p));
printf("队头元素为%d\\n", return_top(&p));
printf("队尾元素为%d\\n", return_back(&p));
printf("当前队列是否为空%d\\n", is_empty(&p));
pop_queue(&p);
pop_queue(&p);
printf("当前有效元素为%d\\n", return_value(&p));
printf("队头元素为%d\\n", return_top(&p));
printf("队尾元素为%d\\n", return_back(&p));
printf("当前队列是否为空%d\\n", is_empty(&p));
destory(&p);
printf("当以上是关于栈和队列的基本概念及操作的主要内容,如果未能解决你的问题,请参考以下文章
(王道408考研数据结构)第三章栈和队列-第二节:队列基本概念顺序栈和链栈基本操作