zoukankan      html  css  js  c++  java
  • [转载]栈、队列 .

    http://blog.csdn.net/zhenyusoso/article/details/6095354

    2044人阅读 评论(9) 收藏 举报

        图1为线性表(ZHAO, QIAN, SUN, LI, ZHOU, WU, ZHENG, WANG)的逻辑状态。头指针 指示链表中第一个结点(即第一个数据元素的存储映像)的存储位置。同时,由于最后一个数据元素没有直接后继,则线性链表中最后一个结点的指针为“空”(NULL)。

    图2.6

    图1 线性链表的逻辑状态

    由上述描述可见,单链表可由头指针来唯一确定,在C语言中可用“结构指针”来描述。

    1. //-----线性表的单链表存储结构-----   
    2. typedef struct LNode{  
    3.     ElemType   data;  
    4.     struct LNode  *next;  
    5. }LNode, *LinkList;  

    有时在单链表的第一个结点之前附设一个结点,称之为头结点 头结点的数据域可以不存储任何信息,也可以存储如线性表长度等类的附加信息,头结点的指针域存储指向第一个结点的指针(即第一个元素结点的存储位置)。如图2(a)所示,此时,单链表的头指针指向头结点。若线性表为空,则头结点的指针域为“空”,如图2(b)所示。

    图2.7 图2 带头结点的单链表   (a)非空表;(b)空表

        循环链表 是另一种形式的链式存储结构。它的特点是表中最后一个节点的指针域指向头结点,整个链表形成一个环。由此,从表中任一结点出发均可找到表中其他结点,如图3所示为单链的循环链表

    图2.12  图3 单链循环表 (a)非空表;(b)空表

    循环链表的操作和线性链表基本一致,差别仅在于算法中的循环条件不是p或p->next 是否为空,而是它们是否等于头指针,但有的时候,若在循环链表中设立尾指针而不设头指针(如图4(a)所示),可使某些操作简化。例如将两个线性表合并成一个表时,仅需将一个表的尾表和另一个表的头表相接。当线性表以图2.4(a)的循环链表作存储结构时,这个操作仅需改变两个指针值即可,运算时间为O (1)。合并后的表如图4(b)所示。

    图2.13 图4 仅设尾指针的循环链表 (a)两个链表;(b)合并后的表

         以上讨论的链式存储结构的节点中只有一个指示直接后继的指针域,由此,从某个结点出发只能顺指针往后寻查其他结点。若要寻查节点的直接前趋,则需从表头指针出发。换句话说,在单链表中,NextElem的执行时间为O(1),而PriorElem的执行时间为O(n)。为克服单链表这种单向性的缺点,可利用双向链表 。顾名思义,在双向链表的结点中有两个指针域,其一指向直接后继,另一指向直接前趋。在C语言中可描述如下:

    1. //-----线性表的双向链表存储结构-----   
    2. typedef struct DuLNode{  
    3.     ElemType   data;  
    4.     struct DuLNode  *prior;  
    5.     struct DuLNode  *next;  
    6. }DuLNode, *DuLinkList;  

    和单链的循环表类似,双向链表也可以有循环表,如图5(c)所示,链表中存有两个环,图5(b)所示为只有一个表头结点的空表。在双向链表中,若d为指向表中某一个结点的指针(即d为DuLinkList型变量),则显然有

    d->next->prior=d->prior->next=d

    图2.14

    图5 双向链表示例 (a)结点结构;(b)空的双向循环链表;(c)非空的双向循环链表

      顺序栈,即栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top指示栈项元素在顺序栈中的位置。通常的习惯做法是以top=0表示空栈,鉴于C语言中数组的下标约定从0开始,则当以C作描述语言时,如此设定会带来很大不便;另一方面由于栈在使用过程中所需最大空间的大小很难估计,因此,一般来说,在初始化设空栈时不应限定栈的最大容量。一个较合理的做法是:先为栈分配一个基本容量,然后在应用过程中,当栈的空间不够使用时再逐段扩大。为此,可设定两个常量:STAC_INT_SIZE(存储空间初始分配)和STACKINCREMENT(存储空间分配增量),并以下述类型说明作为顺序栈的定义。

    typedef struct{
      SElemType *base;
      SElemType *top;
      int stackseze;
    }SqStack;

    其中,stacksize只是栈的当前可使用的最大容量。栈的初始化操作为:按设定的初始分配量进行第一次存储分配,base可称为栈底指针,在顺序栈中,它始终指向栈底的位置,若base的值为NULL,则表明栈结构不存在。称top为栈顶指针,其初值指向栈底,即top=base可作为栈空的标记,每当插入新的栈顶元素时,指针top增1;删除栈顶元素时,指针top减1,因此,非空栈中的栈顶指针始终在栈顶元素的下一个位置上。图1展示了顺序栈中数据元素和栈顶指针之间的对应关系。

    图3.2

    图1 栈顶指针和栈中元素之间的关系

    以下是顺序栈的模块说明。

    //=====ADT Stack 的表示与实现 =====
    //-----栈的顺序存储表示-----
    #define STACK_INIT_SIZE 100; //存储空间初始分配量
    #define STACKINCREMENT 10; //存储空间分配增量
    typedef struct{
      SElemType *base; //在栈构造之前和销毁之后,base的值为NULL
      SElemType *top; //栈顶指针
      int stacksize; //当前已分配的存储空间,以元素为单位
    }SqStack;
     
    //-----基本操作的函数原型说明(针对几个易错易考的)-----
    Status GetTop (SqStack S, SElemType &e);
       //若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERRO
    Status Push (SqStack &S, SElemType e);
       //插入元素e为新的栈顶元素
    Status Pop (SqStack &S, SElemType &e);
       // 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR 

        栈的引入简化了程序设计的问题,划分了不同的关注层次,使思考范围缩小了。而用数组不仅掩盖了问题的本质,还要分散精力去考虑数组下标增减等细节问题。(栈这个数据结构模型正是基于一些问题本质而构建的,具有了一些实际的模型含义,不像数组那样原始。

        队列是一种先进先出(first infirst out,缩写为FIFO)的线性表。它只允许在标的一端进行插入,而在另一端删除元素。这和我们日常生活中的排队是一致的,最早进入队列的元素最早离开。在队列中,允许插入的一端叫做队尾(rear),允许删除的一端则称为对头(front)(排队买票,窗口一端叫对头,末尾进队叫队尾)。

        用链表表示的队列称为链队列,如图2所示。一个链队列显然需要两个分别指向对头和队尾的指针(分别称为头指针和尾指针)才能唯一确定。这里,和线性表的单链表一样,为了操作方便起见,我们先给链队列添加一个头结点,并令头指针和尾指针均指向头结点,如图3(a)所示。链队列的操作即为单链表的插入和删除操作的特殊情况,只是尚需修改尾指针或头指针,图3(b)~(d)展示了这两种操作进行时指针变化的情况。下面给出链队列类型的模块说明。

                                     图3.10 图3.11

                                                               图2 链队列示意图                       图3 队列运算指针变化情况 (a)空队列;(b)元素x入队;(c)元素y入队;(d)元素x出队

    //=====ADT Queue的表示与实现=====
    //-----单链队列——队列的链式存储结构-----
    typedef struct QNode{
        QElemType   data;
        struct QNode  *next;
    }QNode, *QueuePtr;

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

    //-----基本操作的函数原型说明(几个易错常考的)-----
    Status GetHead(LinkQueue Q, QElemType &e)
       //若队列不空,则用e返回Q的对头元素,并返回OK;否则返回ERROR
    Status EnQueue(LinkQueue &Q, QElemType e)
        //插入元素e为Q的新的队尾元素
    Status DeQueue(LinkQueue &Q, QElemType &e)
        //若队列不空,则删除Q的对头元素,用e返回其值,并返回OK;
        //否则返回ERROR

         和顺序栈相类似,在队列的顺序存储结构中,除了用一组地址连续的存储单元依次存放从队列头到队列尾的元素之外,尚需附设两个指针front和rear分别之时队列头元素和队列尾元素的位置。为了在C语言中描述方便起见,在此我们约定:初始化建空队列时,令front=rear=0,每当插入新的队列尾元素时,“尾指针增1”;每当删除队列头元素时,“头指针增1”。因此,在非空队列中,头指针始终指向队列头元素,而尾指针始终指向队列尾元素的下一个位置。如图4所示。

    图3.12 图4 头、尾指针和队列中元素之间的关系

    (a)空队列;(b)J1、J2和J3相继入队列;(c)J1和J2相继被删除;(d)J4、J5和J6相继插入队列之后J3及J4被删除

        假设当前为队列分配的最大空间为6,则当队列处于图4(d)的状态时不可再继续插入新的队尾元素,否则会因数组越界而遭致程序代码被破坏。然而此时又不宜如顺序栈那样,进行存储再分配扩大数组空间,因为队列的实际可用空间并未占满。一个较巧妙的办法是将顺序队列臆造为一个环状的空间,如图5所示,称之为循环队列。指针和队列元素之间关系不变,如图6(a)所示循环队列中,队列头元素时J3,队列尾元素是J5,之后J6、J7和J8相继插入,则队列空间均被占满,如图6(b)所示,此时Q.front=Q.rear;反之,若J3、J4和J5相继从图6(a)的队列中删除,使队列呈“空”的状态,如图6(c)所示。此时亦存在关系式Q.front=Q.rear,由此可见,只凭等式Q.front=Q.rear无法判别队列空间是“空”还是“满”。可由两种处理方法:其一是另设一个标志位以区别队列是“空”还是“满”;其二是少用一个元素空间,约定以“队列头指针在队列尾指针的下一位置(指环状的下一位置)上”作为队列呈“满”状态的标志。

                                                                    图3.13

    图5 循环队列示意图

                                                        图3.14

    图6 循环队列的头尾指针 (a)一般情况;(b)队列满时;(c)空队列;


        从上述分析可见,在C语言中不能用动态分配的一维数组来实现循环队列。如果用户的应用程序中设有循环队列,则必须为它设定一个最大队列长度;若用户无法预估所用队列的最大长度,则宜采用链队列。循环队列类型的模块说明如下:

    //-----循环队列———队列的顺序存储结构-----
    #define MAXQSIZE 100   //最大队列长度
    typedef struct{
        QElemType *base;  //初始化的动态非配存储空间
        int front;        //头指针,若队列不空,指向队列的头元素
        int rear;         //尾指针,若队列不空,指向队列尾元素的下一个位置
    }SqQueue;

    //-----循环队列的基本操作的算法描述-----
    Status InitQueue(SqQueue &Q){
        //构造一个空队列Q
        Q.base=(ElemType *)malloc(MAXQSIZE*sizeof(ElemType));
        if(!Q.base) exit (OVERFLOW);  // 存储分配失败
        Q.front=Q.rear=0;
        return OK;
    }

    int QueueLength(SqQueue Q){
        //返回Q的元素个数,即队列的长度
        return (Q.rear-Q.front+MAXQSIZE) % MAXQSIZE;
    }

    Status EnQueue(SqQueue &Q, QElemType e){
        //插入元素e为Q的新的队尾元素
        if((Q.rear+1) % MAXQSIZE == Q.front) return ERROR;  // 队列满
        Q.base[Q.rear]=e;
        Q.rear=(Q.rear+1) % MAXQSIZE;
        return OK;
    }

    Status DeQueue(SqQueue &Q, QElemType &e){
        //若队列不空,则删除Q的对头元素,用e返回其值,并返回OK;
        //否则返回ERROR
        if(Q.front==Q.rear)  return ERROR;
        e=Q.base[Q.front];
        Q.front=(Q.front+1) % MAXQSIZE;
        return OK;
    }

  • 相关阅读:
    如何选择一家公司?
    教你一招最屌的阅读开源项目的姿势
    我是如何管理我的团队的?
    我面试到底问什么?
    如何正确使用开源项目?
    如何选择开源项目?
    html表格中的tr td th用法
    如何用 Java 实现 Web 应用中的定时任务?
    java定时任务实现的几种方式
    Java 定时任务 & 任务调度
  • 原文地址:https://www.cnblogs.com/fx2008/p/2226564.html
Copyright © 2011-2022 走看看