栈和队列的基本概念及操作

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考研数据结构)第三章栈和队列-第二节:队列基本概念顺序栈和链栈基本操作

数据结构栈与队列

数据结构栈与队列

爆肝两万字,我爷爷都看的懂的《栈和队列》,建议各位观众姥爷先收藏

爆肝两万字,我爷爷都看的懂的《栈和队列》,建议各位观众姥爷先收藏