数据结构与算法&&线性表的变异体——栈与队列

本文由个人笔记整理得出,材料主要来自《大话数据结构》和网络各博主

栈的定义:限定仅在表尾进行插入和删除操作的线性表。

栈的插入操作叫作进栈,也称压栈、入栈。
栈的删除操作,也叫出栈,弹栈。
栈底:下标为0的一端作为栈底。

ADT栈(stack)
Data 
Operation
		InitStack(*S):初始化操作,建立一个空栈S;
		DestroyStack(*S):若栈存在,则销毁它;
		ClearStack(*S):清空栈
		StackEmpty(S):若栈为空,返回ture,否则返回false
		GetTop(S,*e):若栈存在,用e返回S的栈顶元素
		Push(*S,e):若栈存在,插入新的元素e到栈S中并成为栈顶元素
		Pop(*S,*e):删除栈S中的栈顶元素,并用e返回其值
		StackLength(S):返回栈S的元素个数
endADT

栈的结构定义:

 typedef int SElemType;
 typedef struct 
 {
		SElemType data[MAXSIZE];//定义数组
		int top;//设置栈顶
}SqStack;

Status Push(SqStack *S,SElemType)//进栈
{
		if(S->top == MAXSIZE-1)//栈已满
			return ERROR;
		S->top++;
		S->data[S->top] = e;
		return OK;
}
Status Pop(SqStack *S,SElemType *e)//出栈
{
		if(S->top == -1)
			return ERROR;
		 e= S->data[S->top];
		S->top--;
		return OK;
}
typedef strcut//定义两个栈,占用同一个数组;由两边向中间靠拢
{
		SElemType data[MAXSIZE];
		int top1;
		int top2;
}SqDoubleStack;

Status Push(SaDoubleStack *S,SElemType e,int stackNumber)//进栈,先判断要进哪一个栈;然后再进
{
		if(S->top+1==S->top2)//栈满
						return ERROR;
		if(stackNumber==1)//判断是栈1
				S->data[++S->top1] = e;
		else if(stackNumber == 2)//判断是栈2
			S->data[--S->top2] = e;
		return OK;
					
	}
	Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber)
	{
				if(stackNumber == 1)
				{
						if(S->top == -1)
								return ERROR;
						*e = S->data[S->top1--];
						else if(stackNumber == 2)
						{
								if(S->top2 == MAXSIZE)
										return ERROR;
								*e = S->data[S->top2++];
						}
						return OK;
				}
	}

栈的链式存储结构及实现
上篇文章我们可以知道,线性表有顺序表和链表;那么对于栈也是一样的;上面介绍的栈只有数组,没有指针,属于顺序栈。一般情况下,栈只是用来做插入和删除操作,但是上面我们谈到的栈是有空间限制的;对比于上篇文章中写到的线性表,那么我们应该就不难理解链栈存在的意义了:最大的好处就是没有空间的限制,通过指针指向将结点像一个链子一样把结点链接。

先来个定义:
typdef struct StackNode
{
		SElemType data;//数据
		struct StackNode *next;//指针
}StackNode,*LinkStackPtr;

typdef struct LinkStack
{
		LinkStackPtr top;//栈顶
		int count;
}LinkStack;

Stauct Push(LinkStack *S,SElemType e)//插入元素e为新的栈顶元素
{
		LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));//创建新结点
		s->data = e;//数据
		s->next = s->top;//把当前的栈顶数据赋值给新结点的后继
		s->top = s;//新结点s赋值给栈顶指针
		s->count++;
		return OK;

}

Stauct Pop(LinkStack *S,SElemType *e)//若栈不空,则删除S的栈顶元素,用e返回其值;
{
		LinkStackPtr p;
		if(StackEmpty(*S))
				return ERROR;
		*e = S->top->data;//数据
		p = S->top;//把当前的栈顶数据赋值给新结点的后继
		S->top = S->top->next;//新结点s赋值给栈顶指针
		S->count++;
		return OK;
}

其实我们很容易就可以看出,栈的时间复杂度为O(1);对比顺序栈和链栈,顺序栈需要事先确定一个固定的长度,可能会存在内存空间浪费的问题,但它的优势是存取定位很方便,而链栈则要求每一个元素都有指针域,坏处是增加了一些内存的开销,但对于栈的长度,我们就可以不跟顺序栈一样死板定义了。

总结一下,从上篇文章我们知道,线性表的插入和删除是可以在表中、表头和表尾插入的;但栈不一样,只能删除和加入元素只能栈顶上操作;这个的本质意义上是简化了程序设计的问题,将数据的处理划分了不同的关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题。

补充:
递归(函数)
所谓递归就是一个直接或间接调用自己的函数。但是写递归最怕的就是陷入永不结束的无穷递归中,所以,每一个递归至少有一个条件,就是当满足条件再进行时,不再引用自身而是利用返回值退出。
如:

int Fbi(int i)
{
		if(i<2)
			return i == 0?0:1;//问号运算符的标准格式:表达式1?表达式2:表达式3
								若表达式1为真,则执行表达式2,为假,则执行表达式3
								对于本题,若i=0,则返回值0,否则返回值1
		return Fbi(i-1)-Fbi(i-2);
}
int main()
{
		int i;
		for(int i = 0;i<40;i++)
			printf("%d",Fbi(i));
		return 0;
}

队列定义:值允许在一端进行插入,而在另外一端进行删除操作的线性表。

队列,其实就跟排队一样,讲究先来后到,先进先出,简称FIFO。允许插入的一端成为队尾,允许删除的一端称为队头。

实际应用典例:电脑键盘的字母输入

ADT Queue
Data
Operation
	InitQueue(*Q):建立一个空队列
	DestroyQueue(*Q):如果队列存在,则销毁它
	ClearQueue(*Q):清空队列
	QueueEmpty(Q):判断队列是否为空,若为空,则返回true;否则返回false
	GetHead(Q,*e):若队列Q存在且位非空,用e返回队列Q的队头元素
	EnQueue(*Q,e):删除队列Q中队头元素,并用e返回其值
	QueueLength(Q):返回队列Q的元素的个数
endADT

循环队列:队列中头尾相接的顺序存储结构。

typedef int QElemType;
typedef struct
{
		QElemType data[MAXSIZE];
		int front;//头
		int rear;//尾
}SqQueue;

Status InitQueue()//初始化一个队列
{
		Q->front;
		Q->rear = 0;
		return OK;
}
int QueueLength(SqQueue Q)//求队列的长度
{
		return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}
Status EnQueue(SqQueue *Q,QElemType e)//若队列未满,则插入元素e为Q的新的队尾元素
{
		if((Q-rear+1)%MAXSIZE== Q->front)//已满
				return ERROR;
		Q->data[Q->rear] = e;//把元素e赋值给队尾
		Q->rear = (Q->rear+1)%MAXSIZE;//rear向后移一位置
		return OK;
}
Status DeQueue(SqQueue *Q,QElemType *e)//若队列不空,则删除元素e,用e返回其值
{
		if(Q->front == Q->rear)
			return ERROR;
		*e = Q-data[Q-front];
		Q-front = (Q-front+1)%MAXSIZE;
		
		return OK;
}

队列的链式存储结构及实现
写到队列这里其实我们都不用讲了,链式?呵呵,不就是在结点再加指针吗?不就是方便删除和插入吗?这谁不会?对吧

typedef int QElemType;
typedef struct QNode
{
		QElemType data;//数据
		struct QNode *next;//指针
}QNode,*QueuePtr;

typedef struct
{
		QueuePtr front,rear;//队头、队尾指针
}LinkQueue;

Status EnQueue(LinkQueue *Q,QElemtype e)//插入元素e为Q的新的队列元素
{
	QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
	if(!s)//保险一点,加了防止分配内存不成功
	{
		exit(0);
		
	}
	s->data =  e;
	s-next =  NULL;
	Q->rear->next = s;//把拥有元素e新结点s赋值给原队尾结点的后继,就是让队尾的后继指向s咯
	Q-rear = s;//当前的s设置为队尾结点,rear指向s
	return OK;
}

Status DeQueue(LinkQueue *Q,QElemtype *e)//删除结点
{
	QueuePtr p;
	if(Q->front == Q->rear)
		return ERROR;
	p = Q-front->next;//把欲删除的队头结点暂存给p,
	*e = p->data;//把欲删除的队友结点的值赋值给e
	Q->front->next = p-next;//把原队头结点后继p->next赋值给头结点后继
	if(Q-rear == p)//判断改链表是否只剩下一个元素,是的话则将rear指向头结点
		Q-rear = Q-front;
	free(p);
	return OK;
}

再总结一下:
栈:仅限定在表尾插入和删除操作的线性表;
队列:只允许在一端进行插入操作,而在另一端进行删除操作的线性表;
对于循环队列,相对于队列最大好处就是队头和队尾可以在数组中循环变化,解决了移动数据的时间损耗。使得本来的时间复杂度从O(n)变为了O(1)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章