栈与队列
一 栈:
栈(Stack):是限制在表的一端进行插入和删除操作的线性表。又称为后进先出LIFO(Last In First Out)或先进后出FILO (First In Last Out)线性表。
栈顶(Top):允许进行插入、删除操作的一端,又称为表尾。用栈顶指针(top)来指示栈顶元素。
栈底(Bottom):是固定端,又称为表头。
空栈:当表中没有元素时称为空栈。
二 抽象数据类型
ADT Stack{
数据对象:D ={ ai|ai∈ElemSet, i=1,2,…,n,n≥0 }
数据关系:R ={<ai-1, ai>|ai-1,ai∈D, i=2,3,…,n }
基本操作:初始化、进栈、出栈、取栈顶元素等
} ADT Stack
typedef struct sqstack
{
ElemType *bottom; /* 栈不存在时值为NULL */
ElemType *top; /* 栈顶指针 */
int stacksize ; /* 当前已分配空间,以元素为单位 */
}SqStack ;
三 栈的顺序存储
动态顺序栈:指的是栈的大小可以根据需要增加。
结点进栈:首先将数据元素保存到栈顶(top所指的当前位置),然后执行top加1,使top指向栈顶的下一个存储位置;
结点出栈:首先执行top减1,使top指向栈顶元素的存储位置,然后将栈顶元素取出。
基本操作
一 初始化
Status Init_Stack(void)
{
SqStack S ;
S.bottom=(ElemType *)malloc(STACK_SIZE*sizeof(ElemType));
if (! S.bottom) return ERROR;
S.top=S.bottom ; /* 栈空时栈顶和栈底指针相同 */
S. stacksize=STACK_SIZE;
return OK ;
}
二 压栈
Status push(SqStack S , ElemType e)
{
if (S.top-S.bottom>=S. stacksize-1)
{
S.bottom=(ElemType*)realloc((S.STACKINCREMENT+STACK_SIZE)*sizeof(ElemType)); /* 栈满,追加存储空间 */
if (! S.bottom) return ERROR;
S.top=S.bottom+S.stacksize ;
S.stacksize+=STACKINCREMENT ;
}
*S.top=e; S.top++ ; /* 栈顶指针加1,e成为新的栈顶 */
return OK;
}
三 弹栈
Status pop( SqStack S, ElemType *e )
/*弹出栈顶元素*/
{
if ( S.top==S.bottom )
return ERROR ; /* 栈空,返回失败标志 */
S.top-- ;
e=*S. top ;
return OK ;
}
静态顺序栈
采用静态一维数组来存储栈。
栈底固定不变的,而栈顶则随着进栈和退栈操作变化的,
结点进栈:首先执行top加1,使top指向新的栈顶位置,然后将数据元素保存到栈顶(top所指的当前位置)。
结点出栈:首先把top指向的栈顶元素取出,然后执行top减1,使top指向新的栈顶位置。
若栈的数组有Maxsize个元素,则top=Maxsize-1时栈满
typedef struct sqstack
{
ElemType stack_array[MAX_STACK_SIZE] ;
int top;
}SqStack ;
基本操作
栈初始化
SqStack Init_Stack(void)
{
SqStack S ;
S.bottom=S.top=0 ;
return(S) ;
}
压栈
Status push(SqStack S , ElemType e)
/* 使数据元素e进栈成为新的栈顶 */
{
if (S.top==MAX_STACK_SIZE-1)
return ERROR; /* 栈满,返回错误标志 */
S.top++ ; /* 栈顶指针加1 */
S.stack_array[S.top]=e ; /* e成为新的栈顶 */
returnOK; /* 压栈成功 */
}
弹栈
Status pop( SqStack S, ElemType *e )/*弹出栈顶元素*/
{
if ( S.top==0 )
return ERROR; /* 栈空,返回错误标志 */
*e=S.stack_array[S.top];
S.top--;
return OK;
}
四 栈的链式存储
栈的链式存储结构称为链栈,是运算受限的单链表。其插入和删除操作只能在表头位置上进行。因此,链栈没有必要像单链表那样附加头结点,栈顶指针top就是链表的头指针。
typedef struct Stack_Node
{
ElemType data ;
structStack_Node *next ;
} Stack_Node ;
链栈的基本操作
初始化:
Stack_Node *Init_Link_Stack(void)
{
Stack_Node *top ;
top=(Stack_Node*)malloc(sizeof(Stack_Node )) ;
top->next=NULL;
return(top) ;
}
压栈:
Status push(Stack_Node *top ,ElemType e)
{
Stack_Node *p ;
p=(Stack_Node *)malloc(sizeof(Stack_Node)) ;
if (!p) return ERROR;
/* 申请新结点失败,返回错误标志 */
p->data=e;
p->next=top->next ;
top->next=p; /* 钩链 */
return OK;
}
弹栈:
Status pop(Stack_Node *top , ElemType *e)
/* 将栈顶元素出栈 */
{
Stack_Node *p ;
ElemType e ;
if (top->next==NULL)
return ERROR; /* 栈空,返回错误标志 */
p=top->next; e=p->data ; /* 取栈顶元素 */
top->next=p->next; /* 修改栈顶指针 */
free(p) ;
return OK ;
}
五 栈与递归
递归调用:一个函数(或过程)直接或间接地调用自己本身,简称递归(Recursive)。
递归是程序设计中的一个强有力的工具。因为递归函数结构清晰,程序易读,正确性很容易得到证明。
为了使递归调用不至于无终止地进行下去,实际上有效的递归调用函数(或过程)应包括两部分:递推规则(方法),终止条件。
六 顺序存储队列
队列(Queue):也是运算受限的线性表。是一种先进先出(First In First Out ,简称FIFO)的线性表。只允许在表的一端进行插入,而在另一端进行删除。
队首(front) :允许进行删除的一端称为队首。
队尾(rear) :允许进行插入的一端称为队尾。
队列中没有元素时称为空队列。在空队列中依次加入元素a1, a2, …, an之后,a1是队首元素,an是队尾元素。显然退出队列的次序也只能是a1, a2, …, an ,即队列的修改是依先进先出的原则进行的
抽象数据类型:
ADT Queue{
数据对象:D ={ ai|ai∈ElemSet, i=1, 2, …, n, n >= 0 }
数据关系:R = {<ai-1, ai>| ai-1, ai∈D, i=2,3,…,n }
约定a1端为队首,an端为队尾。
基本操作:
Create():创建一个空队列;
EmptyQue():若队列为空,则返回true ,否则返回flase ;
InsertQue(x) :向队尾插入元素x;
DeleteQue(x) :删除队首元素x;
} ADT Queue
//队列的数据结构
typedef struct queue
{
ElemType Queue_array[MAX_QUEUE_SIZE] ;
int front ;
int rear ;
}SqQueue;
利用一组连续的存储单元(一维数组)依次存放从队首到队尾的各个元素,称为顺序队列.
队列的示意图。
七 循环队列
为充分利用向量空间,克服上述“假溢出”现象的方法是:将为队列分配的向量空间看成为一个首尾相接的圆环,并称这种队列为循环队列(CircularQueue)。
在循环队列中进行出队、入队操作时,队首、队尾指针仍要加1,朝前移动。只不过当队首、队尾指针指向向量上界(MAX_QUEUE_SIZE-1)时,其加1操作的结果是指向向量的下界0。
溢出的情况下标的计算方式是用模运算来进行计算的i=(i+1)%MAX_QUEUE_SIZE ;
◆ 循环队列为空:front=rear 。
◆ 循环队列满:(rear+1)%MAX_QUEUE_SIZE =front。
基本操作
入队
Status Insert_CirQueue(SqQueue Q , ElemType e)
/* 将数据元素e插入到循环队列Q的队尾 */
{
if ((Q.rear+1)%MAX_QUEUE_SIZE== Q.front)
return ERROR; /* 队满,返回错误标志 */
Q.Queue_array[Q.rear]=e ; /* 元素e入队 */
Q.rear=(Q.rear+1)% MAX_QUEUE_SIZE ;
/* 队尾指针向前移动 */
return OK; /* 入队成功 */
}
出队
Status Delete_CirQueue(SqQueue Q, ElemType *x )
/* 将循环队列Q的队首元素出队 */
{
if (
Q.front+1== Q.rear)
return error; /* 队空,返回错误标志 */
*x=Q.Queue_array[Q.front] ; /* 取队首元素 */
Q.front=(Q.front+1)% MAX_QUEUE_SIZE ;
/* 队首指针向前移动 */
return OK ;
}
七 链队列
队列的链式存储结构简称为链队列,它是限制仅在表头进行删除操作和表尾进行插入操作的单链表。
需要两类不同的结点:数据元素结点,队列的队首指针和队尾指针的结点
typedef struct Qnode
{
ElemType data ;
struct Qnode *next ;
}QNode ;
链对列的初始化
LinkQueue *Init_LinkQueue(void)
{ LinkQueue *Q ; QNode *p ;
p=(QNode *)malloc(sizeof(QNode)) ; /* 开辟头结点 */
p->next=NULL ;
Q=(LinkQueue *)malloc(sizeof(LinkQueue)) ;
/* 开辟链队的指针结点 */
Q.front=Q.rear=p ;
return(Q) ;
}
链对列的入队
Status Insert_CirQueue(LinkQueue *Q ,ElemType e)
/* 将数据元素e插入到链队列Q的队尾 */
{
p=(QNode*)malloc(sizeof(QNode)) ;
if (!p) return ERROR;
/* 申请新结点失败,返回错误标志 */
p->data=e; p->next=NULL ; /* 形成新结点 */
Q.rear->next=p; Q.rear=p ; /* 新结点插入到队尾 */
return OK;
}
链对列的出队
Status Delete_LinkQueue(LinkQueue *Q,ElemType *x)
{
QNode *p ;
if (Q.front==Q.rear) return ERROR ; /* 队空 */
p=Q.front->next; /* 取队首结点 */
*x=p->data;
Q.front->next=p->next; /* 修改队首指针 */
if (p==Q.rear) Q.rear=Q.front ;
/* 当队列只有一个结点时应防止丢失队尾指针 */
ree(p) ;
return OK ;
}
链队列的撤销
void Destroy_LinkQueue(LinkQueue *Q )
/* 将链队列Q的队首元素出队 */
{ while (Q.front!=NULL)
{ Q.rear=Q.front->next;
/* 令尾指针指向队列的第一个结点 */
free(Q.front); /* 每次释放一个结点 */
/* 第一次是头结点,以后是元素结点 */
Q.ront=Q.rear;
}
}