C数据结构-栈和队列

栈和队列在逻辑上属于线性表的范畴,只是运算受到了严格的限制,称它们为运算受限的线性表。

一,栈

  栈是限定仅在表尾进行插入和删除运算的线性表。我们把表尾称为栈顶(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[N][N]来描述各行政区域间的相邻关系。除关系矩阵外,还需要设置一个栈S,用来记录行政区域的所染颜色的序号S[i]=k;   k表示行政区域i+1所染颜色的序号。

#define N 7
void MapColor(int R[][N],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是相对于单链表多余的两个指针,分别指向头和尾,因此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[n],用来存放集合中的元素;数组result[n]用来存放每个元素的分组号;newr[n]为工作数组。

void function(int n,int R[][n],int cq[n],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)离散仿真事件






























































猜你喜欢

转载自blog.csdn.net/shen_zhu/article/details/79587384