数据结构自学笔记(C语言)栈和队列

先明确概念,栈和队列是两种重要的线性结构,从数据结构角度看,栈和队列是特殊的线性表,是操作受限的线性表,即限定性数据结构。限定插入和删除只能在表的端点进行的线性表。

栈的定义和特点

定义:限定仅在表尾进行插入或删除操作的线性表,表尾——栈顶,表头——栈底,不含元素的空表称空栈
特点:先进后出(FILO)或后进先出(LIFO)
栈的运算:构造空栈、清空栈、销毁栈、判空栈、取栈顶元素、入栈、出栈
既然栈是特殊的线性表,线性表又分为顺序表和链表,那么栈自然也分顺序栈和链栈。
顺序栈:顺序栈,即栈的顺序存储结构是通过一组地址连续的单元依次存放自栈底到栈顶的数据元素,同时附加指针指向栈顶元素的地址。栈在使用过程中所需最大空间很难估计,因此一般在初始化时先分派一个基本容量,然后不够大了在逐段扩大,和之前将的顺序表一样。栈的结构体成员也是三个,但是和顺序表有一些差异
在这里插入图片描述在这里插入图片描述
由于栈只对栈顶进行操作,所以需要引入top指针,时刻指向栈底,这样表长还是可以知道的,因为物理地址是连续的,所以用栈顶指针减去栈底指针就是length了,一样的,只不过换了一种表达形式,顺序表该有的操作还是不变的。

先把顺序栈整体代码上上来

#include<stdio.h>//printf函数
#include<stdlib.h>//malloc函数
#define STACK_ININ_SIZE 20//初始分配量
#define STACK_INCREMENT 10//每次增量
typedef struct{
	int *base;
	int *top;
	int stackSize;
}SeqStack,*SeqStack1;

SeqStack1 InitStack()//构造空栈
{
	SeqStack1 stack;
	stack = (SeqStack1)malloc(sizeof(SeqStack));
	stack->base = (int*)malloc(STACK_ININ_SIZE*sizeof(int));
	stack->stackSize = STACK_ININ_SIZE;
	stack->top = stack->base; 
	return stack;
}

int IsEmptyStack(SeqStack1 stack)//判空栈
{
	if(stack->top == stack->base)
		return 1;
	else
		return 0;
}

int CleanStack(SeqStack1 stack)//清空栈
{
	if(IsEmptyStack(stack))
		return 1;
	else
	{
		stack->top = stack->base;
		return 1;
	}
}

int DestroyStack(SeqStack1 stack)//销毁栈
{
	CleanStack(stack);
	free(stack->top);
	free(stack);
	return 1;
}

int PushStack(SeqStack1 stack,int elem)//入栈
{
	int* a;
	if(stack->top - stack->base >= stack->stackSize)
	{
		a = (int*)realloc(stack->base,(stack->stackSize+STACK_INCREMENT)*sizeof(int));
		if(!a)
			return -1;
		else
		{
			stack->base = a;
			stack->top = stack->base+stack->stackSize;
			stack->stackSize+=STACK_INCREMENT;
		}
	}
	*stack->top++ = elem;
	return 1;
}

int PopStack(SeqStack1 stack,int* e)//出栈
{
	*e = *--stack->top;
	return 1 ;
}

void main()
{
	int i;
	int *e = (int*)malloc(sizeof(int));
	SeqStack1 stack1 = InitStack();
	for(i=0;i<31;i++)
	{
		PushStack(stack1,i+1);
	}
	CleanStack(stack1);
	DestroyStack(stack1);
	while(!IsEmptyStack(stack1))
	{
		PopStack(stack1,e);
		printf("%d\n",*e);
	}
}

讲几点:
1.清空栈和销毁栈是不一样的,清空栈只需要把头指针指向栈底就可以了,销毁需要把空间释放
2.有个疑惑,一个int占四个字节,stack->top - stack->base得到的是4还是1呢,后来验证了是1,指针相减得到的是指针指向数据类型的个数,不是字节数。
3.出栈时要注意是先指针减1,然后再取出来
大部分还是和顺序线性表差不多的,就不多说了,有问题还是留言吧。

链栈其实就是链表的简化版,因为链表找前面比找后面容易,为了满足FILO,在建链表的时候采用反向的方式,在前面的程序里都有,而且链栈不需要头结点,定义个指针一直指向栈顶就好了。看到网上说,链栈一般不需要头结点,有头指针就行,自己也就作了一下试了试。其实都一样,就省了一个节点的空间,好像也没太大意义。查了一下资料,使用头结点的好处就是删除第一个数据元素后可以返回表头,但不实用头结点,就只剩下一个指针了,再插入的时候还要新申请空间,重新指过去,这样链表的初始位置就会一直变。对于是否需要使用头结点也是有争议的。但换个方式操作一下会对指针和链表的了解更深入一些,期间也遇到了问题,最后也解决了。看代码吧

#include<stdio.h>//printf函数
#include<stdlib.h>//malloc函数
#define STACK_ININ_SIZE 20//初始分配量
#define STACK_INCREMENT 10//每次增量

typedef struct Node{
	int data;
	struct Node* next;
}LinkStack,*LinkStack1;//节点结构

LinkStack1 PushStack(LinkStack1 top,int elem)//入栈
{
	LinkStack1 L1 = (LinkStack1)malloc(sizeof(LinkStack));
	L1->data = elem;
	L1->next = top;
	top = L1;
	return top;
}

LinkStack1 PopStack(LinkStack1 top,int* e)//出栈
{
	LinkStack1 L1 = top;
	if(top)
	{
		*e = top->data;
		top = top->next;
		free(L1);
		return top;
	}
}
void PopStack1(LinkStack1 top,int* e)//错误出栈
{
	LinkStack1 L1 = top;
	if(top)
	{
		*e = top->data;
		top = top->next;
		free(L1);
	}
}

void main()
{
	int i =0;
	int *e = &i;
	LinkStack1 top = NULL;
	for(i=0;i<10;i++)
		top = PushStack(top,i+1);
	while(top)
	{
		top = PopStack(top,e);
		printf("%d\n",*e);
	}
}

这段程序卡的时间太长了,因为犯了一个很典型的错误,总觉得是BUG,最后想了很近才想明白。最开始是想和以前一样,通过传结构体地址的方式,直接在子函数里面进行操作就好了,也就是程序里的错误出栈。但是发现每次在子程序里面的状态是对的,但一出子程序就还原了,我明明传递的是指针啊,指针在子函数可以更改指向地址的内容啊。问题出现在top = top->next;这句话,可以通过指针在子函数中修改指针指向地址内容没错,但是不能在子函数中改变指针,其实改的是形参top不是实参top,所以是错误的。

栈的FILO的特性让它在递归上有很大的应用空间,递归就是函数一直调用自己,然后一层一层递归回去,其实我们从主函数调用子函数都是要入栈的,最后入的最先出,大家自己感受一下就知道为什么递归要用栈了。

队列的定义和特点

定义及特点:队列是限定只能在表的一端进行插入,在另一端进行删除的线性表,是先进先出(FIFO)。队尾(rear)是允许插入的一端,对头(front)是允许删除的一端。队列和栈正好是相反的,栈是在哪插在哪删。
队列和栈的实现方法都是一样的,其实只要对顺序表和链表熟悉了,这些程序写起来都是一样的,就不贴程序了,就用文字数一说。
链队列:由于队列是一头进一头出,所以就需要在之前的链表基础上在引入一个尾指针,两个指针分别指向链表头和尾,插入从尾插入,取出从头取。
顺序队列:顺序队列有一个问题,就是会假溢出。因为顺序队列是要提前申请号空间的,然后也是两个指针操作,一个负责插入,一个负责删除。但是当插入端到了申请空间的最后时,有可能前面已经删除了已经删除了一些数据元素,也就是说现在队列并不是满的,这就是假溢出。假溢出的解决方案是循环队列,把队列设想成环形,首尾相接,用取余的方式实现首尾相接。判断队列满的条件是头指针和尾指针指向同一个地方。但是这又有新的问题,判断队列为空也是头指针和尾指针指向同一个地方,所以又提出改进,少用一个元素空间,当尾指针加一和头指针指向同一个地方的时候就判定队列满了。

PS:本来觉得会写的很详细,但是当自己动手把指针、结构体、链表这些东西都应用一遍后就觉得没什么东西好写了,都是一个套路。所以线性表也就这么草草收尾了,日后如果有人在下面留言有问题或者自己又想起来哪里需要补充再更新把

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值