第3章 栈 和 队列

http://blog.csdn.net/xiaoxiaopengbo/article/details/51705954ios

转载自:http://blog.csdn.net/hguisu/article/details/7674195

1.栈

1.1 栈的定义c++

栈是一种特殊的线性表。其特殊性在于限定插入和删除数据元素的操做只能在线性表的一端进行。以下所示:算法

结论:后进先出(Last In First Out),简称为LIFO线性表。数组

 

栈的基本运算有六种:app

构造空栈:InitStack(S)、ide

判栈空: StackEmpty(S)、测试

判栈满: StackFull(S)、大数据

进栈: Push(S,x)、可形象地理解为压入,这时栈中会多一个元素ui

退栈: Pop(S) 、 可形象地理解为弹出,弹出后栈中就无此元素了。spa

取栈顶元素:StackTop(S),不一样与弹出,只是使用栈顶元素的值,该元素仍在栈顶不会改变。

    因为栈也是线性表,所以线性表的存储结构对栈也适用,一般栈有顺序栈和链栈两种存储结构,这两种存储结构的不一样,则使得实现栈的基本运算的算法也有所不一样。

 

咱们要了解的是,在顺序栈中有"上溢"和"下溢"的概念。顺序栈比如一个盒子,咱们在里头放了一叠书,当咱们要用书的话只能从第一本开始拿(你会把盒子翻过来吗?真聪明^^),那么当咱们把书本放到这个栈中超过盒子的顶部时就放不下了(叠上去的不算,哼哼),这时就是"上溢","上溢"也就是栈顶指针指出栈的外面,显然是出错了。反之,当栈中已没有书时,咱们再去拿,看看没书,把盒子拎起来看看盒底,仍是没有,这就是"下溢"。"下溢"自己能够表示栈为空栈,所以能够用它来做为控制转移的条件。

链栈则没有上溢的限制,它就象是一条一头固定的链子,能够在活动的一头自由地增长链环(结点)而不会溢出,链栈不须要在头部附加头结点,由于栈都是在头部进行操做的,若是加了头结点,等于要在头结点以后的结点进行操做,反而使算法更复杂,因此只要有链表的头指针就能够了。

 

1.2 栈的顺序存储

使用c++的面向对象封装:

/ Test.cpp : Defines the entry point for the console application.  
//  
#include "stdafx.h"    
#include <iostream>  
using namespace std;  
#define MAX 10 // MAXIMUM STACK CONTENT  
class stack     
{     
private:     
    int arr[MAX];   
    int top;   
public:     
    stack()   
    {     
        inItStack();   
    }  
    /************************************************************************/  
    /* 初始化栈                                                                     */  
    /************************************************************************/  
    void inItStack()   
    {   
        top=-1;   
    }   
    /************************************************************************/  
    /* 入栈                                                                     */  
    /************************************************************************/  
    void push(int a)   
    {     
        top++;  
        if(top < MAX)  {     
            arr[top]=a;   
        }   else   {     
            cout<<"STACK FULL!!"<<top;     
        }     
    }     
    /************************************************************************/  
    /* 出栈                                                                     */  
    /************************************************************************/  
    int pop()  
    {      
        if(isEmpty())   {     
            cout<<"STACK IS EMPTY ";  
            return NULL;     
        } else {     
            int data=arr[top];   
            arr[top]=NULL;   
            top--;  
            return data;   
        }     
    }     
  
    /************************************************************************/  
    /* 是否为空                                                                     */  
    /************************************************************************/  
    bool isEmpty()  
    {  
        if(top == -1) return true;  
        else return false;  
    }  
};     
int main()     
{     
    stack a;     
    a.push(3);     
    a.push(10);     
    a.push(1);     
    cout<<"Pop:"<<a.pop();        
    return 0;     
}  

 

 

结论:因为栈的插入和删除操做具备它的特殊性,因此用顺序存储结构表示的栈并不存在插入删除数据元素时须要移动的问题,但栈容量难以扩充的弱点仍就没有摆脱。

 

1.3 栈的链式存储
如果栈中元素的数目变化范围较大或不清楚栈元素的数目,就应该考虑使用链式存储结构。人们将用链式存储结构表示的栈称做"链栈"。链栈一般用一个无头结点的单链表表示。如图所示:

栈的操做是线性表操做的特例。

简单用c实现:

// Test.cpp : Defines the entry point for the console application.    
//    
#include "stdafx.h"    
#include <stdio.h>    
#include "stdlib.h"  
#include <iostream>  
using namespace std;  
//宏定义    
#define TRUE   1    
#define FALSE   0    
#define OK    1    
#define ERROR   0    
#define INFEASIBLE -1    
#define OVERFLOW -2   
#define STACKEMPTY -3    
  
#define LT(a,b)   ((a)<(b))    
#define N = 100           
  
typedef int Status;    
typedef int ElemType;    
  
typedef struct LNode{    
    ElemType        data;                 
    struct LNode   *next;       
}LNode, *LinkList;   
  
typedef struct stack{  
    LinkList top;  
} STACK;  
  
  
/************************************************************************/  
/*     接口: 
*/  
/************************************************************************/  
void InitStack(STACK &S);  
void Push(STACK &S,ElemType e);  
void Pop(STACK &S, ElemType *e);  
ElemType GetTop(STACK S,ElemType *e);  
int StackEmpty(STACK S);  
  
/************************************************************************/  
/*  
*/  
/************************************************************************/  
void InitStack(STACK &S)  
{  
    S.top=NULL;  
}  
  
/************************************************************************/  
/* 入栈  
*/  
/************************************************************************/  
void Push(STACK &S,ElemType e)  
{  
    LinkList p;  
    p = (LinkList )malloc(sizeof(LNode));  
    if (!p) exit(OVERFLOW);  
    p->data = e;  
    p->next = S.top;  
    S.top = p;  
}  
/************************************************************************/  
/* 出栈 
*/  
/************************************************************************/  
void Pop(STACK &S, ElemType *e)  
{  
    LinkList p;  
    if(StackEmpty(S)) exit(STACKEMPTY);  
    *e = S.top->data;  
    p = S.top;  
    S.top = p->next;   
    free(p);  
}  
/************************************************************************/  
/* 获取栈顶元素内容 
*/  
/************************************************************************/  
ElemType GetTop(STACK S, ElemType *e)  
{  
    if(StackEmpty(S)) exit(STACKEMPTY);  
    *e = S.top->data;  
}  
  
/************************************************************************/  
/* 判断栈S是否空  
*/  
/************************************************************************/  
int StackEmpty(STACK S)   
{  
    if(S.top==NULL) return TRUE;  
    return   FALSE;  
}  
  
void main()    
{    
  
    STACK S;  
    InitStack( S);  
    Push(S, 3);  
    Push(S, 4);  
    ElemType e;  
    Pop(S,&e);  
    cout<<"Pop elem:"<<e;  
}    

 

 

1.4 栈的应用

1)  数制转换

2)语法词法分析

3)表达式求值等

 

1.5 栈的递归和实现

汉诺塔的问题:

解决:

1)若是有一个盘子,直接从X移到Z便可。
2)若是有n个盘子要从X移到Z,Y做为辅助。问题能够转化为,先将上面n-1个从X移动到Y,Z做为辅助,而后将第n个从X移动到Z,最后将剩余的n-1个从Y移动到Z,X做为辅助。

完整实现代码,包括栈的实现:// Test.cpp : Defines the entry point for the console application.    

//    
#include "stdafx.h" #include <stdio.h> #include "stdlib.h" #include <iostream>  
using namespace std; //宏定义 
#define TRUE   1    
#define FALSE   0    
#define OK    1    
#define ERROR   0    
#define INFEASIBLE -1    
#define OVERFLOW -2   
#define STACKEMPTY -3    
  
#define LT(a,b)   ((a)<(b))    
#define N = 100            typedef int Status; typedef int ElemType; typedef struct LNode{ ElemType data; struct LNode   *next; }LNode, *LinkList; typedef struct stack{ LinkList top; } STACK; /************************************************************************/  
/* 接口: */  
/************************************************************************/  
void InitStack(STACK &S); void Push(STACK &S,ElemType e); void Pop(STACK &S, ElemType *e); ElemType GetTop(STACK S,ElemType *e); int StackEmpty(STACK S); /************************************************************************/  
/*  
*/  
/************************************************************************/  
void InitStack(STACK &S) { S.top=NULL; } /************************************************************************/  
/* 入栈 */  
/************************************************************************/  
void Push(STACK &S,ElemType e) { LinkList p; p = (LinkList )malloc(sizeof(LNode)); if (!p) exit(OVERFLOW); p->data = e; p->next = S.top; S.top = p; } /************************************************************************/  
/* 出栈 */  
/************************************************************************/  
void Pop(STACK &S, ElemType *e) { LinkList p; if(StackEmpty(S)) exit(STACKEMPTY); *e = S.top->data; p = S.top; S.top = p->next; free(p); } /************************************************************************/  
/* 获取栈顶元素内容 */  
/************************************************************************/ ElemType GetTop(STACK S, ElemType *e) { if(StackEmpty(S)) exit(STACKEMPTY); *e = S.top->data; } void printStack(STACK S){ LinkList p; p = S.top; printf("栈: "); while (p) { printf("%d ", p->data); p = p->next; } } /************************************************************************/  
/* 若是有一个盘子,直接从X移到Z便可。 若是有n个盘子要从X移到Z,Y做为辅助。问题能够转化为,先将上面n-1个从X移动到Y,Z做为辅助,而后将第n个从X移动到Z,最后将剩余的n-1个从Y移动到Z,X做为辅助。 */  
/************************************************************************/  
  
void move(STACK &Sa,STACK &Sb) { ElemType e; Pop(Sa,&e); Push(Sb, e); } void hanoi(int n,STACK  &X,STACK &Y,STACK &Z) { if(n==1) return move(X, Z);     //将圆盘1号直接移到z 
    hanoi(n-1,X,Z,Y);               //将x上的1大n-1圆盘移到y,z作辅助塔 
    move(X, Z);                     //将编号为n的圆盘移z 
    hanoi(n-1,Y,X,Z);               //将y上的1大n-1圆盘移到z,x作辅助塔 
} /************************************************************************/  
/* 判断栈S是否空 */  
/************************************************************************/  
int StackEmpty(STACK S) { if(S.top==NULL) return TRUE; return FALSE; } void main() { STACK Sx, Sy,Sz; InitStack( Sx); InitStack( Sy); InitStack( Sz); int i, n = 10; for (i = 10 ; i>=1 ;i--) { Push(Sx, i); } printStack(Sx); hanoi(n, Sx,Sy,Sz); printStack(Sz); } 

 

2.队列

2.1 队列定义 

队列(Queue)也是一种运算受限的线性表,它的运算限制与栈不一样,是两头都有限制,插入只能在表的一端进行(只进不出),而删除只能在表的另外一端进行(只出不进),容许删除的一端称为队尾(rear),容许插入的一端称为队头 (Front)

,队列的操做原则是先进先出的,因此队列又称做FIFO表(First In First Out)

队列的基本运算也有六种:

置空队 :InitQueue(Q)

判队空: QueueEmpty(Q)

判队满: QueueFull(Q)

入队 : EnQueue(Q,x)

出队 : DeQueue(Q)

取队头元素: QueueFront(Q),不一样与出队,队头元素仍然保留。

 

队列也有顺序存储和链式存储两种存储结构,前者称顺序队列,后者为链队。

对于顺序队列,咱们要理解"假上溢"的现象。

咱们现实中的队列好比人群排队买票,队伍中的人是能够一边进去从另外一头出来的,除非地方不够,总不会有"溢出"的现象,类似地,当队列中元素彻底充满这个向量空间时,再入队天然就会上溢,若是队列中已没有元素,那么再要出队也会下溢。

那么"假上溢"就是怎么回事呢?

由于在这里,咱们的队列是存储在一个向量空间里,在这一段连续的存储空间中,由一个队列头指针和一个尾指针表示这个队列,当头指针和尾指针指向同一个位置时,队列为空,也就是说,队列是由两个指针中间的元素构成的。在队列中,入队和出队并非象现实中,元素一个个地向前移动,走完了就没有了,而是指针在移动,当出队操做时,头指针向前(即向量空间的尾部)增长一个位置,入队时,尾指针向前增长一个位置,在某种状况下,好比说进一个出一个,两个指针就不停地向前移动,直到队列所在向量空间的尾部,这时再入队的话,尾指针就要跑到向量空间外面去了,仅管这时整个向量空间是空的,队列也是空的,却产生了"上溢"现象,这就是假上溢。

为了克服这种现象形成的空间浪费,咱们引入循环向量的概念,就比如是把向量空间弯起来,造成一个头尾相接的环形,这样,当存于其中的队列头尾指针移到向量空间的上界(尾部)时,再加1的操做(入队或出队)就使指针指向向量的下界,也就是从头开始。这时的队列就称循环队列

一般咱们应用的大都是循环队列。因为循环的缘由,光看头尾指针重叠在一块儿咱们并不能判断队列是空的仍是满的,这时就须要处理一些边界条件,以区别队列是空仍是满。方法至少有三种,一种是另设一个布尔变量来判断(就是请别人看着,是空仍是满由他说了算),第二种是少用一个元素空间,当入队时,先测试入队后尾指针是否是会等于头指针,若是相等就算队已满,不准入队。第三种就是用一个计数器记录队列中的元素的总数,这样就能够随时知道队列的长度了,只要队列中的元素个数等于向量空间的长度,就是队满。


2.2 队列的顺序存储

顺序存储如图:


因为是顺序存储结构的存储空间是静态分配的,因此在添加数据的时,有可能没有剩余空间的状况。

解决这种“假溢出”状况,使用循环队列在c语言中,不能用动态分配的一维数组来实现循环队列。若使用循环队列,必须设置最大队列长度,若没法估计最大长度,就使用链式队列。

c实现:

// Test.cpp : Defines the entry point for the console application.    
//    
#include "stdafx.h"    
#include <stdio.h>    
#include "stdlib.h"  
#include <iostream>  
using namespace std;  
//宏定义    
#define TRUE   1    
#define FALSE   0    
#define OK    1    
#define ERROR   0    
#define INFEASIBLE -1    
#define OVERFLOW -2   
#define QUEUEEMPTY  -3    
        
#define MAX_QUEUE 10 //队列的最大数据元素数目  
  
typedef int Status;    
typedef int ElemType;    
  
typedef struct queue{    
    ElemType        elem[MAX_QUEUE] ;     ///假设当数组只剩下一个单元时认为队满            
    int front;      //队头指针  
    int rear;       //队尾指针     
}QUEUE;   
  
  
/************************************************************************/  
/*     各项基本操做算法。 
*/  
/************************************************************************/  
void InitQueue(QUEUE *&Q);  
void EnQueue(QUEUE *Q,ElemType elem);  
void DeQueue(QUEUE *Q,ElemType *elem);  
int QueueEmpty(QUEUE Q);  
  
/************************************************************************/  
/*  
  初始化 
  直接使用结构体指针变量,必须先分配内存地址,即地址的指针 
*/  
/************************************************************************/  
void InitQueue(QUEUE *&Q)   
{  
  
    Q = (QUEUE *) malloc (sizeof(QUEUE));  
    Q->front = Q->rear = -1;  
  
}  
/************************************************************************/  
/*     入队                                                               
*/  
/************************************************************************/  
   
void EnQueue(QUEUE *Q, ElemType elem)  
{  
    if((Q->rear+1)% MAX_QUEUE == Q->front) exit(OVERFLOW);  
    Q->rear = (Q->rear + 1)%MAX_QUEUE;  
    Q->elem[Q->rear] = elem;   
}  
/************************************************************************/  
/*     出队                                                                
*/  
/************************************************************************/  
void DeQueue(QUEUE *Q,ElemType *elem)  
{  
    if (QueueEmpty(*Q))  exit(QUEUEEMPTY);  
    Q->front =  (Q->front+1) % MAX_QUEUE;  
    *elem=Q->elem[Q->front];  
}  
/************************************************************************/  
/*    获取队头元素内容                                                             
*/  
/************************************************************************/  
  
void GetFront(QUEUE Q,ElemType *elem)   
{  
    if ( QueueEmpty(Q) )  exit(QUEUEEMPTY);  
    *elem = Q.elem[ (Q.front+1) % MAX_QUEUE ];  
}  
/************************************************************************/  
/*    判断队列Q是否为空                                                              
*/  
/************************************************************************/  
int QueueEmpty(QUEUE Q)  
{  
    if(Q.front==Q.rear) return TRUE;  
    else return FALSE;  
}  
  
void main()    
{    
  
    QUEUE *Q;  
    InitQueue( Q);  
    EnQueue( Q, 1);  
    EnQueue( Q, 2);  
    ElemType e;  
    DeQueue( Q,&e);  
    cout<<"De queue:"<<e;  
}    

注意:InitQueue(QUEUE *&Q) 传的是指针的地址。

 

 

2.3 链式队列:

 

// Test.cpp : Defines the entry point for the console application.    
//    
#include "stdafx.h"    
#include <stdio.h>    
#include "stdlib.h"  
#include <iostream>  
using namespace std;  
//宏定义    
#define TRUE   1    
#define FALSE   0    
#define OK    1    
#define ERROR   0    
#define INFEASIBLE -1    
#define OVERFLOW -2   
#define QUEUEEMPTY  -3    
        
  
typedef int Status;    
typedef int ElemType;    
  
typedef struct LNode {          //链式队列的结点结构  
    ElemType elem;          //队列的数据元素类型  
    struct LNode *next;      //指向后继结点的指针  
}LNode, *LinkList;  
  
typedef struct queue{   //链式队列  
    LinkList front;     //队头指针  
    LinkList rear;      //队尾指针  
}QUEUE;   
  
/************************************************************************/  
/*     各项基本操做算法。 
*/  
/************************************************************************/  
void InitQueue(QUEUE *Q);  
void EnQueue(QUEUE *Q,ElemType elem);  
void DeQueue(QUEUE *Q,ElemType *elem);  
void GetFront(QUEUE Q,ElemType *elem) ;  
bool QueueEmpty(QUEUE Q);  
  
/************************************************************************/  
  
  
/*初始化队列Q  */  
void InitQueue(QUEUE *Q)  
{  
    Q->front = (LinkList)malloc(sizeof(LNode));  
    if (Q->front==NULL) exit(ERROR);  
    Q->rear= Q->front;  
}  
/*入队 */   
void EnQueue(QUEUE *Q,ElemType elem)  
{  
    LinkList s;  
    s = (LinkList)malloc(sizeof(LNode));  
    if(!s) exit(ERROR);  
    s->elem = elem;  
    s->next = NULL;  
    Q->rear->next = s;  
    Q->rear = s;  
}  
  
/*出队  */   
void DeQueue(QUEUE *Q,ElemType *elem)  
{  
    LinkList s;  
    if(QueueEmpty(*Q)) exit(ERROR);  
    *elem = Q->front->next->elem;  
    s = Q->front->next;  
    Q->front->next = s->next;  
    free(s);  
}  
/*获取队头元素内容  */   
  
void GetFront(QUEUE Q,ElemType *elem)   
{  
    if(QueueEmpty(Q)) exit(ERROR);  
    *elem = Q.front->next->elem;  
}  
/*判断队列Q是否为空   */   
bool QueueEmpty(QUEUE Q)  
{  
    if(Q.front == Q.rear) return TRUE;  
    return FALSE;  
}  
  
void main()    
{    
  
    QUEUE Q;  
    InitQueue( &Q);  
    EnQueue( &Q, 1);  
    EnQueue( &Q, 2);  
    ElemType e;  
    DeQueue( &Q,&e);  
    cout<<"De queue:"<<e;  
}    

2. 4.队列的应用【举例1】银行排队【举例2】模拟打印机缓冲区。在主机将数据输出到打印机时,会出现主机速度与打印机的打印速度不匹配的问题。这时主机就要停下来等待打印机。显然,这样会下降主机的使用效率。为此人们设想了一种办法:为打印机设置一个打印数据缓冲区,当主机须要打印数据时,先将数据依次写入这个缓冲区,写满后主机转去作其余的事情,而打印机就从缓冲区中按照先进先出的原则依次读取数据并打印,这样作即保证了打印数据的正确性,又提升了主机的使用效率。因而可知,打印机缓冲区实际上就是一个队列结构。【举例3】CPU分时系统在一个带有多个终端的计算机系统中,同时有多个用户须要使用CPU运行各自的应用程序,它们分别经过各自的终端向操做系统提出使用CPU的请求,操做系统一般按照每一个请求在时间上的前后顺序,将它们排成一个队列,每次把CPU分配给当前队首的请求用户,即将该用户的应用程序投入运行,当该程序运行完毕或用完规定的时间片后,操做系统再将CPU分配给新的队首请求用户,这样便可以知足每一个用户的请求,又可使CPU正常工做

相关文章
相关标签/搜索