您的位置:首页 > 理论基础 > 数据结构算法

C数据结构-栈和队列

2018-03-17 00:01 295 查看
栈和队列在逻辑上属于线性表的范畴,只是运算受到了严格的限制,称它们为运算受限的线性表。
一,栈
  栈是限定仅在表尾进行插入和删除运算的线性表。我们把表尾称为栈顶(TOP),表头称为栈底。当栈中没有元素时称为空栈。入栈是指在栈顶插入数据元素,出栈是指在栈顶删除数据元素。
对于栈来说,最后进栈的元素,最先出栈,故把栈称为后进先出的数据结构,或先进后出。
栈的用途:汇编处理程序中的句法识别,表达式计算,回溯问题,在函数调用时的参数传递和函数值的返回。
1,栈的顺序存储表示--顺序栈
   用数组来存储。由于栈底是固定不变的,而栈顶是随进栈出栈操作动态变化的,因此为了实现对栈的操作,必须记住栈顶的当前位置。另外,栈是有容量限制的。struct Stack
{
datatype elements[maxsize];
int Top; //表示当前栈顶位置
};置空栈时设S->top=-1;
注意出栈时的操作:datatype *Pops(struct Stack *S){
S->top--;
ret=(datatype *)malloc(sizeof(datatype));
*ret=S->elements[S->top+1];
return ret;
} //如果返回值是整型的话就没必要这么做多个栈共享存储空间:
   两个栈共享存储空间时,将两个栈的栈底设在存储空间的两端,让两个栈的栈顶各自向中间延伸。
2,栈的链式存储表示--链栈
top是栈顶指针,它唯一的确定一个链栈。当top等于NULL时,该链栈为空栈。struct Node{
datatype element;
struct Node *next;
};
struct Node *top;入栈操作:struct Node *push(struct Node *top,datatype e)
{
struct Node *p;
p=(struct Node *)malloc(sizeof(struct Node));
p->element=e;
p->next=top;
top=p;
return top;
}3,栈的应用
(过程递归调用)
要正确实现程序的递归调用,必须解决参数的传递和返回地址问题。进行调用时,每递归一次,都要给所有参变量重新分配存储空间,并要把前一次调用的实参和本次调用后的返回地址保留。递归结束时要逐层释放这些参数所占的存储空间,并按后调用先返回的原则返回各层相应的返点。

实现这种存储分配和管理最有效的工具是栈。
(地图染色问题--“回溯问题”)
算法的基本思想是:从行政区1开始染色,每个区域用所有颜色依次试验,若当前颜色与周围颜色都不重色,则用栈记下该区域颜色序号;否则依次用下一个颜色进行试探。若出现所有颜色均与相邻区域的颜色重色,则必须退栈回溯,修改当前栈顶的颜色序号,再进行试探。
在计算机实现上述算法时,用一个关系矩阵R

来描述各行政区域间的相邻关系。除关系矩阵外,还需要设置一个栈S,用来记录行政区域的所染颜色的序号S[i]=k;   k表示行政区域i+1所染颜色的序号。#define N 7
void MapColor(int R[]
,int n,int S[]){
int color,area,k;
s[0]=1; //第一个行政区域染色1号,共四种颜色
area=1; //从第二个行政区域开始试探染色
color=1; //从颜色1开始试探
while(area<N)
{
while(color<=4)
{
k=0; //指示已染色区域
while((k<area)&&(s[k]*R[area][k]!=color))
k++; //判断当前area区域与k区域是否重色

if(k<area) //area区域与k区域重色
color++;
else
{
s[area]=color; //保存area区域的颜色
area++;
if(area>N)
break;
color=1;
}
}
if(color>4) //area区找不到合适的颜色
{
area-=1; //区域回溯并修改area-1域所使用的颜色
color=s[area]+1;
}
}
}
(表达式求值)
在用高级语言编写的源程序中,一般都含有表达式,如何将他们翻译成能够正确求值的指令序列,是语言处理程序要解决的基本问题,下面介绍“算符优先法”。
任何一个表达式都是由操作数,操作符和界限符组成的。在这里仅讨论简单算术表达式的求值问题,仅包含加减乘除。
算符之间的优先关系表如下:



需要指出的是,为了使算法简洁,在表达式的最左边会虚设一个‘#’构成表达式的一对括号。表中'(' = ')'表示当左右括号相遇时,括号内的运算已经完成;同理,'#'='#'表示整个表达式求值完毕。空格处表示表达式中不允许它们相继出现,假设下面的输入不会出现这种情况。
我们设置两个栈,一个称作optr,用来存放运算符;另一个称作opnd,用以存放运算结果。
算法的基本思想:
(1)置操作数栈为空栈,表达式起始符#为运算符optr的栈底元素。
(2)依次读入表达式中的每个字符,若是操作数则进opnd栈,若是运算符,则在和栈optr的栈顶元素比较优先权后做相应的操作,直至整个表达式求值完毕为止。int evaluateexpression(){
int a,b,result,ret;
char ch,theta;
setnull(opnd);
setnull(optr); //置optr,opnd为空栈
push(optr,'#'); //置栈底元素'#'
ch=getche();
while((ch!='#')||(gettop(optr!='#')){ //整个表达式未扫描完毕
if(ch!='+'&&ch!='-'&&ch!='*'&&ch!='/'&&ch!='('&&ch!=')')
{
push(opnd,ch);
ch=getche(); //读入的不是运算符或结束符,则入栈opnd
}
else
switch(precede(gettops(optr,ch)){ //判断读入运算符的优先级
case '<':push(optr,ch); ch=getche(); break;
case '=':pop(optr); ch=getche(); break;
case '>':theta=pop(optr);
b=pop(opnd);
a=pop(opnd);
result=operate(a,theta,b);
push(opnd,result);
break;
}
}
ret=getpop(opnd)
return ret;
}
二,队列
只允许在表的一段进行插入,而在另一端进行删除。允许删除的一端称为队头,允许插入的一端称为队尾。
队列被称为先进先出的线性表。
当队列中没有元素时称为空队列。
1,队列的存储结构
    (1)顺序队列
运算受限的线性表,使用数组来存放当前队列的元素。由于队列的队头和队尾都是变化的,因此需要设置两个下标来指示当前队头和队尾元素在数组中的位置。struct sequence{
datatype data[maxsize];
int front,rear;
};
struct sequence *sq;为方便起见,规定头指针front总是指向当前队头元素的前一个位置,尾指针指向当前队尾元素。
一开始,队列的头,尾指针指向向量空间下界的前一个位置,在此设置为-1.
当前队列中的元素个数为(sq->rear)-(sq->front).若sq->rear==sq->front,则队列长度为0,即当前队列是空队列。
队满条件是当前队列长度等于向量空间大小,即(sq->rear)-(sq->front)=maxsize。
但是,如果当前尾指针等于数组的上界,即使队列不满(即当前队列长度小于maxsize),再做入队操作也会引起溢出。这种情况称之为“假上溢”。
解决方案:设想向量是一个首尾相接的圆环,即sq->data[0]在sq->data[maxsize-1]之后,我们称之为循环队列。
在循环意义下的尾指针加一操作:if(sq->rear+1>=maxsize)      sq->rear=0;
                                                  else     sq->rear++;
或利用模运算:sq->rear=(sq->rear+1)%maxsize;
若头指针从后面追赶上了尾指针,即sq->front==sq->rear,则当前队列为空。
若尾指针从后面追赶上了头指针,即sq->rear==sq->front,则队列已满。
因此不能用此方法判断队列为空还是为满。
解决方案:入队前测试尾指针在循环意义下加1之后是否等于头指针,若相等则认为队满。
             (sq->rear+1)%maxsize==sq->front ,队空sq->rear==sq->front.
这样做使sq->data[sq->front]是空闲的。置空队列
sq->front=maxsize-1;
sq->rear=maxsize-1;
使之都指在位置0的前一个位置。
去队头元素:sq->data[(sq->front+1)%maxsize]     //头指针的下一个位置
2,链队列
表头插入和表尾删除的单链表。增加一个尾指针,指向链表的最后一个结点。一个链队列由一个头指针和一个尾指针唯一确定。typedef int datatype;

typedef struct node{

datatype data;

struct node *next;

}linklist;
typedef struct{
linklist *front,*rear;
}linkqueue;
linkqueue *q;
队空:q->rear==q->front;
用两个指针指向一个单链表,相当于单链表增加了两个节点(头结点照常),front 指向头结点,而rear指向尾结点。

因此一个队列为空时,其头指针和尾指针均指向头结点。置空队
q->front=(linklist *)malloc(sizeof(linklist); //申请头结点
q->front->next=NULL; //front和rear是
ac56
相对于单链表多余的两个指针,分别指向头和尾,因此q->front是一个指针,指向头结点,q->front->next是开始结点
q->rear=q->front;
return q;
入队
q->rear->next=(linklist *)malloc(sizeof(linklist));
q->rear=q->rear->next;
q->rear->data=x;
q->rear->next=NULL;
出队
s=q->front->next;
if(s->next==NULL){                    //当前链队列长度为1
q->front->next=NULL;
q->rear=q->front;
}
else
q->front->next=s->next;
3,队列的应用,
(1)划分子集问题
将集合中的元素划分为互不相交的子集,同时要求划分的子集数最少。
实际应用中比如安排运动会比赛项目的日程,使一个运动员参加的不同项目不在同一天举行,同时使比赛日程最短。
假设共有九个项目,汇报后有冲突的项目给出。
采用循环筛选法:从集合的第一个元素开始,凡与第一个元素无冲突且与该组中其他元素无冲突的元组划归一组,再将剩余元素按相同方式划归。。。
在计算机实现时,将集合中的冲突关系设置一个冲突矩阵,二维数组,有冲突为1,否则为0.
设置循环队列cq
,用来存放集合中的元素;数组result
用来存放每个元素的分组号;newr
为工作数组。void function(int n,int R[]
,int cq
,int result[]){
int front,rear,group,pre,I,i;
front=n-1;
rear=n-1;
for(i=0;i<n;i++)
{
newr[i]=0;
cq[i]=i+1;
}
group=1;
pre=0; //前一个出队元素编号
do{
front=(front+1)%n;
I=cq[front];
if(I<pre){ //开始下一次筛选准备
group=group+1;
result[I-1]=group;
for(i=0;i<n;i++)
newr[i]=R[I-1][i];
}
else
if(newr[I-1]=group){ //发生冲突元素入队
rear=(rear+1)%n;
cq[rear]=I;
}
else{
result[I-1]=group;
for(i=0;i<n;i++)
newr[i]+=R[I-1][i];
}
pre=I;
}while(rear!=front)
}
(2)离散仿真事件
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: