关闭

结构之美学习二《栈和队列》

1192人阅读 评论(0) 收藏 举报

栈和队列其实是个抽象的数据类型。

对于他们的实现,既可以用数组,也可以用链表实现。

对于栈和队列,看书的时候可能仅仅只是了解的一个概念。

因为要在程序中真正运用上去,个人觉得又是一个高度的思想境界。

我只是看了书,了解基本概念。


一。栈

定义

栈是仅限在表尾进行插入和删除的操作线性表。
我们把允许插入和删除数据的一段称之为栈顶(top)。另一端称之为栈底(bottom)
不含任何数据元素的栈称为空栈。LIFO后进先出表。

1.栈的数组结构实现,也就是栈顺序存储结构定义。

typedef struct 
{
	ElemType data[MAX_SIZE];
	int top;
}SqStack;

栈的常见操作就是进栈push好出栈pop操作

Status push(SqStack * s, ElemType e)
{
	if(s->top == MAX_SIZE-1);//栈已经满了
	return error;
	//入栈
	s->top++;
	s->data[s->top] = e;
	return ok;
}

Status pop(SqStack * s, ElemType *e){
	if(s->top ==-1)
	return error;
	*e = s->data[s->top];
	s->top--;
	return ok;
}

对于栈的顺序结构实现,是有很多的局限性的。

之后有又提出一个概念是 共享栈。

两个栈共享空间。
栈的顺序存储结构就和线性表的顺序存储一样,同样存在对开辟的存储空间无法很好的确定。
如果此时有两个相同类类型的栈。他们各自用一个数组空间。此时极有可能其中一个栈想插入元素却要溢出了。

而另外一个栈还留有很多存储空间。此时可以稍微 改进下,让两个栈共享一个数组空间。
我们将数组的始端作为第一个栈的栈底,数组的末端最为另一个栈的栈底。两个栈都是像中间延伸。

Status Push(SqStack * s, ElemType e, int stackNumber)
{
	 if(s->top1+1 == s->top2);
    	return error;
	if(stackNumber == 1)
	{
	 s->data[++s->top1] = e;
	}
	else if(stackNumber == 2)
	{
	s->data[--s->top2] = e;
	}
	return ok;
}

2.栈的链式实现。

typedef struct StackNode
{
	ElemType data;
	struct StackNode *next;
}StackNode *LinkStackhead

typedef struct LinkStack
{
	LinkStackhead top;//栈顶指针
	int count;//元素个数
}

进栈和出栈操作

Status push(LinkStack * ls,ElemType e)
{
	LinkStackhead l = (LinkStack)malloc(sizeof(StackNode));//开辟一块存储空间
	l->data = e;//赋值
	l->next = ls->top;
	ls->top = l;
	ls->count++;
	return ok;
}

Status pop(LonkStack * ls ,ElemType *e)
{
	LinkStakchead h;
	if(ls->count == -1)
		return error;
	//*e = ls->top;
	h = ls->top
	ls->top = p->next;
	free(h)
	ls->count--;
	return ok;

}

栈的应用。

在现实中,先进后出的式的栈应用:比如文件浏览工具就是一个典型。

其次比如我们程序的执行顺序也是可以理解为一个栈的形式 。 A 方法中调用了B,B中调用了C。

那么要执行完这端代码,我们先按调用的顺序将A,B,C依次压入栈中,然后C  B  A依次pop出执行操作。

因此我们可以联想到一个最常见的应用场景,就是递归。

递归中有个必定听到过的的内容,就是Fibonacci数列。

斐波那契的递归函数。

int Fbi(int i){
if(i<2)
return i == 0?0:1
return Fbi(i-1)+Fbi(i-2);
}
即,我要取得该数列中的第i个数,必须先知道位于第 i-2和i-1这两个数,

于是就产生一个递归的算法。

在递归中要注意的算法必须有跳出的条件。

从代码可以看出,递归用到的是选择结构。

显然递归代码很优雅,但是优雅是要有代价的,他会不断调用和建立函数副本,耗费大量的时间和内存。
对于斐波那契数列求第i相的算法有一种改进的做法,

因为这边我们假设计算 i项的数,

那么要先求 i-1和i-2项的数,

求i-1项的数则又需要求i-2和i-3项数;

到这边,我们就看出来了,在求i项时,我们去计算了 i-2项,在求i-1项时,我们有去求了i-2项。

也就是说,其实在计算第i项数,不停的递归时,很多计算都是重复的。

那么觉得可以考虑是否把每项的数在一次计算后就存起来,后面被调用到时在先去查找该项是否已经计算,有的话就直接取出来,不存在那就计算咯

private  Map numsMap = new HashMap<Integer,Integer>();
	public  int Fbi(int i){
	int x,y;
	if(i<2)
	return i == 0?0:1;
	if(numsMap.get(Integer.valueOf(i-1)) !=null){
		x = (Integer) numsMap.get(Integer.valueOf(i-1));
		}else{
			x=Fbi(i-1);
		}
	if(numsMap.get(Integer.valueOf(i-2)) !=null){
		y = (Integer) numsMap.get(Integer.valueOf(i-2));
		}else{
			y=Fbi(i-2);
		}
	return x+y;
	}

抱歉我的java和C的混写。因为C的话语法不是太熟,为了测试,熟练度java写起来比较快。

这也算是空间换时间的一种做法。


     栈另外一个重要的的应用:四则运算表达式求值。
1.后缀表示法,(逆波兰):简单说就是遇到数字就进栈,遇到符号就栈顶两个数字出栈。
2.中缀表达式也就是我们平常书写的四则运算表达式。
后缀表达式虽简单,但是怎么把一个中缀表达式转成一个后缀呢?
同样的,从左往右遍历,若是数字就输出,若是符号,判断其于栈顶符号的优先级,若是右括号或是优先级低于栈内元素的,

则栈顶元素依次出栈并输出,将当前符号进栈。


二:队列

定义

队列是只允许在一端进行插入操作,在另一端进行删除操作。FIFO线性表。

1.队列顺序存储结构

相对于栈在插入和删除上操作时间为O(1),在队列插入元素只是在数组末端追加一个元素,时间也为O(1),

但是如果是出列一个元素,此时后面的元素都需要往前面移动一个位置,时间就是O(N)。

因此提出了队头队尾指针。front指向队头元素,rear指向队尾元素的下一个位置。

但此时还是会出现一个问题,就是不停的出队入队,front和rear一直在往后移动,前面的数组单元空着,但是指针已移动到数组末尾,

此时造成一个假溢出的现象。

于是又提出一个改进思路,循环队列。

队列头尾相接的顺序存储结构称之为循环队列。
此时当在数组最后位置插入元素后,rear指针移动到数组的0的位置,既形成一个循环。
但是又会出现一个问题,在我继续插入元素,rear指针往后移动,就会有rear等于front的时候。上面我们说这个情况的队列是空队列出现矛盾,既我们无法判断当rear等于front时,队列到底是满呢,还是空的?
方法一:设置一个标志位Tag,rear还没有循环到数组0时,tag为0,循环到tag设置为1.
方法二:当队列空时rear = front,,当队列满时,我们保留一个元素空间
计算队列的长度通用公式:(rear -front +QueueSize)%QueueSize;
队列满的判断 (rear -front +QueueSize)%QueueSize ==front;

循环队列的顺序结构定义

typedef struct{
	ElemType data[MAX_SIZE];
	int front;
	int rear;
}SqQueue;

循环队列的入队出队操作

Status EnQueue(SqQueue *Q,ElemType e){
	if((Q->rear -Q->front +MAXSZIE)%MAXSIZE ==Q->front)
		return error;
	Q->data[Q->rear] = e;
	Q->rear = (Q->rear+1)%MAXSZIE;
		return ok;
	}

Status DeQueue(Sqqueue *Q,ElemType *e){
	if(Q->rear ==  Q->front)
		rerurn error;

	*e = Q- >data[Q->front];
	Q->front = (Q->front+1)%MAXSIZE;
		return ok;
}

2.队列的链式存储实现

链队列的结构
typedef int Elemtype ;
typedef struct QNode{
   ElemType data;
   struct QNode  *next;
}QNode,*QueuePtr;

typedef struct{//队列的链表结构
   Queueptr front,rear;

}LinkQueue;


链式队列的插入和删除

Status EnQueue(LinkQueue * Q , ElementType e)
{
	Queueptr p = (Queueptr) malloc(sizeof(QNode));

	if(!s)//分配失败;
		exit(OVERFLOW);

	p->data = e ;
	P->next = NULL;
	Q->rear->next = p ;//在队尾插入
	Q->rear = s ;//将p作为队尾节点
	return ok ;
}

Status DeQueue(LinkQueue * Q , ElementType *e)
{
	Queueptr p ;
	if( Q-> front == Q-> rear) //空队列
		return error;
	p = Q->front->next;//保存要删除的元素
	*e = p->data;
	Q->front->next = p->next;
	if(Q->rear == p)//若队头是队尾,删除后将队尾指向队头
	Q->rear = Q->front;
	free(p);
	eturn ok;
}





1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:911503次
    • 积分:8272
    • 等级:
    • 排名:第2590名
    • 原创:69篇
    • 转载:7篇
    • 译文:0篇
    • 评论:398条
    博客专栏
    ----My App---
    最新评论