栈
栈的定义:栈是一种特殊(操作受限)的线性表,只允许在线性表的同一端(表尾)进行插入和删除。允许插入和删除的一端称为栈顶,另一端称为栈底。
栈的特点:后进先出(LIFO)或先进后出(FILO)。
出栈顺序判断:若输入序列是...,P j...Pk...Pi ...(j<k<i) ,一定不存在这样的输出序列...,Pi...Pj...Pk...
与一般线性表的区别:仅在于运算规则不同。
一般线性表 | 栈 |
---|---|
逻辑结构: 1:1 | 逻辑结构:1:1 |
存储结构:顺序表、链表 | 存储结构:顺序栈、链栈 |
运算规则:任意合法位置插入、删除 | 运算规则:只熊在栈顶插入、删除 |
栈的操作:
空栈 base==top是栈空标志 stacksize=6 | 栈满: top-base==stacksize (若为下标,top==stacksize) 此时入栈,则上溢(overflow) | 栈空:top==base (若为下标,top==0) 此时出栈,则下溢(underflow) |
顺序栈
利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,并用起始端作为栈底。
//方法一:
typedef struct{
SElemType *base;//栈底指针
SElemType *top;//栈顶指针
int stacksize;//栈空间
}Sqstack;
//方法二:
typedef struct{
SElemType base[stacksize];
int top;
}SqStack;
栈的基本操作:
[初始化]
void InitStack(Sqstack &S)
{
S.base=(SElemType *)malloc(M*sizeof(SElemType));
S.top=S.base;
S.StackSize=M;
}
[入栈]
Status Push(Sqstack &S, SElemType x)
{
if(S.top-S.base==S.stacksize)
{
S.base=(SElemType *)realloc(S.base,(S.stacksize+3)*sizeof(SElemType));
S.top=S.base+S.stacksize;
S.stacksize+=3;
}
S.top= X;
S.top++;
return OK;
}
[出栈]
Status Pop(Sqstack &S, SElemType &e)
{
if(S.base==S.top)
return ERROR;
S.top--;
e=*S.top;
return OK;
}
顺序栈的优点:由于栈的插入和删除操作具有它的特殊性,所以用顺序存储结构表示的栈并不存在插入删除数据元素时需要移动的问题。
顺序栈的缺点:栈容量难以扩充的弱点仍就没有摆脱。
若是栈中元素的数目变化范围较大或不清楚栈元素的数目,就不宜使用顺序栈。
链栈
typedef struct StackNode {
SElemType data;
struct StackNode *next;
}StackNode,*LinkStack;
LinkStack S;
[初始化]
void InitStack(LinkStack &S)
{
S=NULL;
}
[入栈]
Status Push(LinkStack &S , SElemType e)
{
p=(LinkStack)malloc(sizeof(StackNode));//生成新结点p
if(!p) exit(OVERFLOW);
p->data=e;
p->next=S;
S=p;
return OK;
}
[出栈]
Status Pop(LinkStack &S,SElemType &e)
{
if(S==NULL) return ERROR;
e=S->data;
p=S;
S=S->next;
free(p);
return OK;
}
顺序栈和链栈的比较:
时间性能:相同,都是常数时间。
空间性能:顺序栈有存储元素个数的限制。链栈没有栈满的问题,但每个元素都需要一个指针域,从而产生了结构性开销。当栈的使用过程中元素个数变化较大时,用链栈是适宜的,反之,应该采用顺序栈。
双栈共享空间:利用顺序栈单向延伸的特性,使用一个数组来存储两个栈,让一个栈的栈底为该数组的始端,另一个栈的栈底为该数组的末端,每个栈从各自的端点向中间延伸。
栈空 | 栈满 |
top[0]==-1,top[1]==m或 top[0]==0 ,top[1]==m-1 | top[0]+1==top[1]或 top[0]-1==top[1] |
队列
队列的定义:队列是一种特殊的线性表。只允许在线性表的一端(队尾)进行插入,另一端(队头)进行删除
队列的特点:先进先出(FIFO)
队头指针:指向链队列中队头元素。
首结点:存放队列中第一个数据元素的结点。
队尾指针:指向链队列中队尾元素。
头结点:在链表的首结点之前附设的一个结点,该结点的数据域可以为空,也可存放表长度等附加信息。
链队列
链队列结构定义:
//链队列结构
typedef struct Queue{
QucuePtr front; //队首指针
QueuePtr rear; //队尾指针
}LinkQueue;
//结点
typedef Struct QNode{
QElemType data;//元素
Struct QNode *next;//指向下一结点的指针
}QNode, *QueuePtr;
链队列的基本操作:
[初始化]
Status InitQueue (LinkQueue &Q)
{
Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode));
if(!Q.front) exit(OVERFLOW);
Q.front->next=NULL;
return OK;
}
[入队]
Status EnLinkQueue(LinkQueue &Q,QElemType e)
{
QNode *newNode;
newNode=(QNode *)malloc(sizeof(QNode));
if(newNode!=NULL)
{
newNode->data=e;
newNode->next=NULL;
Q.rear->next=newNode;
Q.rear=newNode;
return OK;
}
else return ERROR;
}
[出队]
Status DeLinkQueue(LinkQueue &Q,QElemType &e)
{
QNode *p;
if(Q.front==Q.rear) return ERROR;
p=Q.front->next;
Q.front->next=p->next;
if(Q.rear==p) Q.rear=Q.front;
e=p->data;
free(p);
return OK;
}
[队判空]
Status QueueEmpty(LinkQueue Q)
{
if(Q.front==Q.rear) return TRUE;
else return FALSE;
}
循环队列
rear=(rear+1)%N
front=(front+1) %N
循环队列队满和队空二义性判断:
1.使用计数器记录队列中元素个数(即队列长度)。
2.加设标志位,识别当前front=rear属于何种情况。
3.浪费一个单元,则队满特征可改为front==(rear+1)%N,其中N为队列的空间大小。
实际应用中我们通常采用第三种方法:
front和rear二者之一指向实元素,另一个指向空闲元素。
队空条件:front ==rear(初始化时:front=rear)
队满条件:front==(rear+1)%N(N为队列的空间大小)
循环队列基本操作:
[初始化]
void InitQ(SqQueue &Q,int N)
{
Q.base= (int*)malloc(sizeof(int)*N);
Q.front=Q.rear=0;
}
[入队]
void AddQ(SqQueue &Q, int x )
{
if((Q.rear+1)%N==Q.front) printf("Queue Full\n");
else
{
Q.base[Q.rear]=x;
Q.rear=(Q.rear+1) % N;
}
}
[出队]
Status DeleteQ(SqQueue &Q,int &e)
{
if(Q.front==Q.rear)
{
printf("Queue Empty\n");
return ERROR;
}
else
{
e=Q.base[Q.front];
Q.front=(Q.front+1)%N;
return OK;
}
}