您的位置:首页 > 理论基础 > 数据结构算法

数据结构与算法&&线性表的变异体——栈与队列

2019-05-15 22:22 211 查看

本文由个人笔记整理得出,材料主要来自《大话数据结构》和网络各博主

栈的定义:限定仅在表尾进行插入和删除操作的线性表。

栈的插入操作叫作进栈,也称压栈、入栈。
栈的删除操作,也叫出栈,弹栈。
栈底:下标为0的一端作为栈底。

ADT栈(stack)
Data
Operation
InitStack(*S):初始化操作,建立一个空栈S;
DestroyStack(*S):若栈存在,则销毁它;
ClearStack(*S):清空栈
StackEmpty(S):若栈为空,返回ture,否则返回false
GetTop(S,*e):若栈存在,用e返回S的栈顶元素
Push(*S,e):若栈存在,插入新的元素e到栈S中并成为栈顶元素
Pop(*S,*e):删除栈S中的栈顶元素,并用e返回其值
StackLength(S):返回栈S的元素个数
endADT

栈的结构定义:

typedef int SElemType;
typedef struct
{
SElemType data[MAXSIZE];//定义数组
int top;//设置栈顶
}SqStack;

Status Push(SqStack *S,SElemType)//进栈
{
if(S->top == MAXSIZE-1)//栈已满
return ERROR;
S->top++;
S->data[S->top] = e;
return OK;
}
Status Pop(SqStack *S,SElemType *e)//出栈
{
if(S->top == -1)
return ERROR;
e= S->data[S->top];
S->top--;
return OK;
}
typedef strcut//定义两个栈,占用同一个数组;由两边向中间靠拢
{
SElemType data[MAXSIZE];
int top1;
int top2;
}SqDoubleStack;

Status Push(SaDoubleStack *S,SElemType e,int stackNumber)//进栈,先判断要进哪一个栈;然后再进
{
if(S->top+1==S->top2)//栈满
return ERROR;
if(stackNumber==1)//判断是栈1
S->data[++S->top1] = e;
else if(stackNumber == 2)//判断是栈2
S->data[--S->top2] = e;
return OK;

}
Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber)
{
if(stackNumber == 1)
{
if(S->top == -1)
return ERROR;
*e = S->data[S->top1--];
else if(stackNumber == 2)
{
if(S->top2 == MAXSIZE)
return ERROR;
*e = S->data[S->top2++];
}
return OK;
}
}

栈的链式存储结构及实现
上篇文章我们可以知道,线性表有顺序表和链表;那么对于栈也是一样的;上面介绍的栈只有数组,没有指针,属于顺序栈。一般情况下,栈只是用来做插入和删除操作,但是上面我们谈到的栈是有空间限制的;对比于上篇文章中写到的线性表,那么我们应该就不难理解链栈存在的意义了:最大的好处就是没有空间的限制,通过指针指向将结点像一个链子一样把结点链接。

先来个定义:
typdef struct StackNode
{
SElemType data;//数据
struct StackNode *next;//指针
}StackNode,*LinkStackPtr;

typdef struct LinkStack
{
LinkStackPtr top;//栈顶
int count;
}LinkStack;

Stauct Push(LinkStack *S,SElemType e)//插入元素e为新的栈顶元素
{
LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));//创建新结点
s->data = e;//数据
s->next = s->top;//把当前的栈顶数据赋值给新结点的后继
s->top = s;//新结点s赋值给栈顶指针
s->count++;
return OK;

}

Stauct Pop(LinkStack *S,SElemType *e)//若栈不空,则删除S的栈顶元素,用e返回其值;
{
LinkStackPtr p;
if(StackEmpty(*S))
return ERROR;
*e = S->top->data;//数据
p = S->top;//把当前的栈顶数据赋值给新结点的后继
S->top = S->top->next;//新结点s赋值给栈顶指针
S->count++;
return OK;
}

其实我们很容易就可以看出,栈的时间复杂度为O(1);对比顺序栈和链栈,顺序栈需要事先确定一个固定的长度,可能会存在内存空间浪费的问题,但它的优势是存取定位很方便,而链栈则要求每一个元素都有指针域,坏处是增加了一些内存的开销,但对于栈的长度,我们就可以不跟顺序栈一样死板定义了。

总结一下,从上篇文章我们知道,线性表的插入和删除是可以在表中、表头和表尾插入的;但栈不一样,只能删除和加入元素只能栈顶上操作;这个的本质意义上是简化了程序设计的问题,将数据的处理划分了不同的关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题。

补充:
递归(函数)
所谓递归就是一个直接或间接调用自己的函数。但是写递归最怕的就是陷入永不结束的无穷递归中,所以,每一个递归至少有一个条件,就是当满足条件再进行时,不再引用自身而是利用返回值退出。
如:

int Fbi(int i)
{
if(i<2)
return i == 0?0:1;//问号运算符的标准格式:表达式1?表达式2:表达式3
若表达式1为真,则执行表达式2,为假,则执行表达式3
对于本题,若i=0,则返回值0,否则返回值1
return Fbi(i-1)-Fbi(i-2);
}
int main()
{
int i;
for(int i = 0;i<40;i++)
printf("%d",Fbi(i));
return 0;
}

队列定义:值允许在一端进行插入,而在另外一端进行删除操作的线性表。

队列,其实就跟排队一样,讲究先来后到,先进先出,简称FIFO。允许插入的一端成为队尾,允许删除的一端称为队头。

实际应用典例:电脑键盘的字母输入

ADT Queue
Data
Operation
InitQueue(*Q):建立一个空队列
DestroyQueue(*Q):如果队列存在,则销毁它
ClearQueue(*Q):清空队列
QueueEmpty(Q):判断队列是否为空,若为空,则返回true;否则返回false
GetHead(Q,*e):若队列Q存在且位非空,用e返回队列Q的队头元素
EnQueue(*Q,e):删除队列Q中队头元素,并用e返回其值
QueueLength(Q):返回队列Q的元素的个数
endADT

循环队列:队列中头尾相接的顺序存储结构。

typedef int QElemType;
typedef struct
{
QElemType data[MAXSIZE];
int front;//头
int rear;//尾
}SqQueue;

Status InitQueue()//初始化一个队列
{
Q->front;
Q->rear = 0;
return OK;
}
int QueueLength(SqQueue Q)//求队列的长度
{
return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}
Status EnQueue(SqQueue *Q,QElemType e)//若队列未满,则插入元素e为Q的新的队尾元素
{
if((Q-rear+1)%MAXSIZE== Q->front)//已满
return ERROR;
Q->data[Q->rear] = e;//把元素e赋值给队尾
Q->rear = (Q->rear+1)%MAXSIZE;//rear向后移一位置
return OK;
}
Status DeQueue(SqQueue *Q,QElemType *e)//若队列不空,则删除元素e,用e返回其值
{
if(Q->front == Q->rear)
return ERROR;
*e = Q-data[Q-front];
Q-front = (Q-front+1)%MAXSIZE;

return OK;
}

队列的链式存储结构及实现
写到队列这里其实我们都不用讲了,链式?呵呵,不就是在结点再加指针吗?不就是方便删除和插入吗?这谁不会?对吧

typedef int QElemType;
typedef struct QNode
{
QElemType data;//数据
struct QNode *next;//指针
}QNode,*QueuePtr;

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

Status EnQueue(LinkQueue *Q,QElemtype e)//插入元素e为Q的新的队列元素
{
QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
if(!s)//保险一点,加了防止分配内存不成功
{
exit(0);

}
s->data =  e;
s-next =  NULL;
Q->rear->next = s;//把拥有元素e新结点s赋值给原队尾结点的后继,就是让队尾的后继指向s咯
Q-rear = s;//当前的s设置为队尾结点,rear指向s
return OK;
}

Status DeQueue(LinkQueue *Q,QElemtype *e)//删除结点
{
QueuePtr p;
if(Q->front == Q->rear)
return ERROR;
p = Q-front->next;//把欲删除的队头结点暂存给p,
*e = p->data;//把欲删除的队友结点的值赋值给e
Q->front->next = p-next;//把原队头结点后继p->next赋值给头结点后继
if(Q-rear == p)//判断改链表是否只剩下一个元素,是的话则将rear指向头结点
Q-rear =
1cca8
Q-front;
free(p);
return OK;
}

再总结一下:
栈:仅限定在表尾插入和删除操作的线性表;
队列:只允许在一端进行插入操作,而在另一端进行删除操作的线性表;
对于循环队列,相对于队列最大好处就是队头和队尾可以在数组中循环变化,解决了移动数据的时间损耗。使得本来的时间复杂度从O(n)变为了O(1)。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: