数据结构======栈和队列

来源:互联网 发布:淘宝推广认准晨昊网络 编辑:程序博客网 时间:2024/05/16 10:05

                 栈和队列是在程序设计中被广泛使用的两种线性数据结构,它们的特点在于基本操作的特殊性,栈必须按"后进先出"的规则进行操作,而队列必须按"先进先出"的规则进行操作。和线性表相比,它们的插入和删除操作受更多的约束和限定,故又称为限定性的线性表结构。


栈的定义

          栈是一种特殊的线性表。其特殊性在于限定插入和删除数据元素的操作只能在线性表的一端进行。进行插入和删除的一端被称为栈顶(top),并用一个“栈顶指针”指示;不允许插入和删除的另一端称作栈底(bottom) 。我们经常将栈用下图3-1的形式描述:

   栈的顺序存储(顺序栈)

              栈的顺序存储结构是用一组连续的存储单元依次存放栈中的每个数据元素,并用起始端作为栈底。其结构定义:

 typedef struct {
  ElemType *base; // 存储空间基址
  int top;     // 栈顶指针
  int stacksize;  // 允许的最大存储空间以元素为单位
 } Stack;



          


基本操作算法:
1.  初始化栈S   
void InitStack (Stack &S,int maxsize)
 {
 // 构造一个最大存储容量为 maxsize 的空栈 S
  if (maxsize == 0)
   maxsize = MAXLISTSIZE;
  S.base = new ElemType[maxsize];
  if (!S.base) exit(1);    // 存储分配失败
  S.stacksize = maxsize;
  S.top = 0;    // 空栈中元素个数为0
 }

2.  读取栈顶元素
bool GetTop (Stack S, ElemType &e)
 {
 // 若栈不空,则用 e 返回S的栈顶元素,并返回TRUE;否则返回FALSE
  if (S.top == 0) return FALSE;
  e = *(S.base + S.top-1); // 返回非空栈中栈顶元素
  return TRUE;
 }

3.  入栈    
bool  Push (Stack &S, ElemType e)
 {
 // 若栈的存储空间不满,则插入元素 e 为新的栈顶元素,并返回 TRUE;否则返回 FALSE
 if (S.top == S.stacksize)  // 栈已满,无法进行插入
   return FALSE;
  *(S.base + S.top) = e;    // 插入新的元素
  ++S.top;          // 栈顶指针后移
  return TRUE;
 }

5.  出栈
         bool Pop (Stack &S, ElemType &e)
 {
 // 若栈不空,则删除S的栈顶元素,用 e 返回其值,并返回 TRUE;否则返回 FALSE
  if (S.top == 0) return FALSE;
  e = *(S.base + S.top-1);  // 返回非空栈中栈顶元素
  --S.top;     // 栈顶指针前移
  return TRUE;
 }

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

栈的链式存储
           若是栈中元素的数目变化范围较大或不清楚栈元素的数目,就应该考虑使用链式存储结构。人们将用链式存储结构表示的栈称作“链栈”。链栈通常用一个无头结点的单链表表示。如图3-3所示。
             由于栈的插入删除操作只能在一端进行,而对于单链表来说,在首端插入删除结点要比尾端相对地容易一些,所以,我们将单链表的首端作为栈顶端,即将单链表的头指针作为栈顶指针。



栈的链式存储结构(链栈)在C语言中可用下列类型定义实现:
             链栈的定义更简单,结点结构和单链表中的结点结构相同,无须重复定义。由于栈只在栈顶作插入和删除操作,因此链栈中不需要头结点。
      typedef struct {
   SLink top;    // 栈顶指针
   int length;   // 栈中元素个数
  } Stack;

下面我们将给出链栈各项基本操作的算法。
1.  初始化栈S  
void InitStack ( Stack &S )
 {
  // 构造一个空栈 S
  S.top = NULL;     // 设栈顶指针的初值为"空"
  S.length = 0;   // 空栈中元素个数为0
 } // InitStack

2.  入栈     
void Push ( Stack &S, ElemType e )
 {
  // 在栈顶之上插入元素 e 为新的栈顶元素
  p = new LNode;   // 建新的结点
  if(!p) exit(1);  // 存储分配失败
  p -> data = e;
  p -> next = S.top; // 链接到原来的栈顶
  S.top = p;     // 移动栈顶指针
  ++S.length;     // 栈的长度增1
 } // Push

3.  出栈
bool Pop ( Stack &S, SElemType &e )
 {
  // 若栈不空,则删除S的栈顶元素,用 e 返回其值,
  // 并返回 TRUE;否则返回 FALSE
  if ( !S.top )
   return FALSE;
  else
  {   e = S.top -> data;   // 返回栈顶元素
   q = S.top;
   S.top = S.top -> next; // 修改栈顶指针
   --S.length;       // 栈的长度减1
   delete q;       // 释放被删除的结点空间
   return TRUE;
  }
 } // Pop
队列的定义
            队列特殊性在于限定插入在线性表的一端进行,删除在线性表的另外一端进行。如图3-5所示:

 插入端和删除端都是浮动的。通常我们将插入端称为队尾,用一个“队尾指针”指示;而删除端被称为队头,用一个“队头指针”指示。
            结论:先进先出(First In First Out),简称为FIFO线性表。队列的链式存储
             在用链式存储结构表示队列时,需要设置队头指针和队尾指针,以便指示队头结点和队尾结点。











(2)入队
void EnQueue(Queue &Q,ElemType e)
  {
  // 在当前队列的尾元素之后,插入元素 e 为新的队列尾元素
   s = new LNode;
   if (!s) exit(1);  // 存储分配失败
   s->data=e;  s->next = NULL;
   Q.rear->next=s; // 修改尾结点的指针
   Q.rear=s;     // 移动队尾指针
   ++Q.length;    // 队列长度增1
  }

===============Q.front->next是队列中的第一个元素

===============Q.front->next = p->next队头指针指向第2个元素---出队


   队列的顺序存储(循环队列)

        和顺序栈相类似,在利用顺序分配存储结构实现队列时,除了用一维数组描述队列中数据元素的存储区域之外,尚需设立两个指针 front 和 rear 分别指示“队头”和“队尾”的位置。为了叙述方便,在此约定:初始化建空队列时,令 front=rear=0,每当插入一个新的队尾元素后,尾指针 rear 增1;每当删除一个队头元素之后,头指针front增1。因此,在非空队列中,头指针始终指向队头元素,而尾指针指向队尾元素的"下一个"位置。如下图所示。


            假设在这之后又有两个元素 f 和 g 相继入队列,而队列中的元素 b 和 c 又相继出队列。则队头指针指向元素 d,队尾指针则指到数组“之外”的位置上去了,致使下一个入队操作无法进行(请注意此时队列空间并未满)。为此,设想这个数组的存储空间是个“环”,认定“7”的下一个位置是“0”。如下图所示。我们将这种形式表示的队列称之为循环队列。 






假设为队列开辟的数组单元数目为MAX_QUEUE,在C语言中,它的下标在0~MAX_QUEUE-1之间,若增加队头或队尾指针,可以利用取模运算(一个整数数值整除以另一个整数数值的余数)实现。如下所示:
             front=(front+1)%MAX_QUEUE;
             rear=(rear+1)%MAX_QUEUE;
            当front或rear为MAXQUEUE-1时,上述两个公式计算的结果就为0。这样,就使得指针自动由后面转到前面,形成循环的效果。
            队空和队满的标志问题:
            队列变为空,队头和队尾指针相等。



队列变为满,队头和队尾指针也相等。




 解决方法:一是为队列另设一个标志,用来区分队列是“空”还是“满”;二是当数组只剩下一个单元时就认为队满,此时,队尾指针只差一步追上队头指针,即:(rear+1)%MAX_QUEUE==front。




(1)初始化队列Q    
void InitQueue (Queue &Q,int maxsize )
 {
 // 构造一个最大存储空间为 maxsize 的空队列 Q
  if (maxsize == 0)
   maxsize = MAXLISTSIZE;
  Q.elem = new ElemType[maxsize]; // 为循环队列分配存储空间
   if (!Q.elem) exit(1);    // 存储分配失败
  Q.queuesize = maxsize;
  Q.front = Q.rear = 0;
 } // InitQueue

(2)入队    
bool EnQueue (Queue &Q,ElemType e)
 {
 // 若当前队列不满,这在当前队列的尾元素之后,插入元素 e 为新的队列尾元素,并返回TRUE,否则返回FALSE
  if ((Q.rear + 1) % Q.queuesize == Q.front )
   return FALSE;
  Q.elem[Q.rear] = e;
  Q.rear = (Q.rear+1) % Q.queuesize;
  return TRUE;
 }

(3)出队  
bool DeQueue (Queue &Q, ElemType &e)
 {
 // 若队列不空,则删除当前队列Q中的头元素,用 e 返回其值
 // 并返回TRUE;否则返回 FALSE
  if (Q.front == Q.rear)
   return FALSE;
  e = Q.elem[Q.front];
  Q.front = (Q.front+1) % Q.queuesize;
  return TRUE;
 }

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

杨辉三角形



















































0 0
原创粉丝点击