第八周周报

来源:互联网 发布:mysql 存储手机号 编辑:程序博客网 时间:2024/06/06 14:22

第八周周报

这周继续学习数据结构。

一、栈与队列

栈与队列:
栈是限定仅在表尾进行插入和删除操作的线性表。
队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。

1. 栈

1.1 栈的定义
栈(stack)是限定仅在表尾进行插入和删除操作的线性表。
把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素 的栈称为空栈。栈又称为后进先出(LastIn First Out)的线性表,简称LIFO结构。
它是一个线性表,也就是说,栈元素具有线性关系,即前驱后继关系。只不过它是一种特殊的 线性表而已。定义中说是在线性表的表尾进行插入和删除操作,这里表尾是指栈顶,而不是栈底。它的特殊之处就在于限制了这个线性表的插入和删除位置,它始终只在栈顶进行。
栈的插入操作,叫作进栈,也称压栈、入栈。
栈的删除操作,叫作出栈,也有的叫作弹栈。

1.2 栈的抽象数据类型
ADT
栈(stack) Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。 Operation
InitStack(*S):
初始化操作,建立一个空栈S。
DestroyStack(*S): 若栈存在,则销毁它。
ClearStack(*S): 将栈清空。
GetTop(S, *e): 若栈存在且非空,用e返回S的栈顶元素。
Push(*S, e): 若栈S存在,插入新元素e到栈S中并成为栈顶元素。
Pop(*S, *e): 删除栈S中栈顶元素,并用e返回其值。
StackLength(S): 返回栈S的元素个数。
endADT

1.3 栈的顺序存储结构及实现
1.3.1栈的顺序存储结构
栈是线性表的特例,栈的顺序存储其实也是线性表顺序存储的简化,简称为顺序栈。 线性表是用数组来实现的。用下标为0的一端作为栈底,因为首元素都存在栈底,变化最小,所以让它作栈底。

栈的结构定义
/* SElemType类型根据实际情况而定,这里假设为int */
typedef int SElemType;
typedef struct {
SElemType data[MAXSIZE];
/* 用于栈顶指针 */
int top;
}SqStack;

栈普通情况、空栈和栈满的情况、空栈和栈满的情况示意图:
栈普通情况、空栈和栈满的情况、空栈和栈满的情况示意图

1.3.2 栈的顺序存储结构——进栈操作
对于进栈操作push,其代码如下:
/* 插入元素e为新的栈顶元素 */
Status Push(SqStack *S, SElemType e)
{
/* 栈满 */
if (S->top == MAXSIZE - 1)
{
return ERROR;
}
/* 栈顶指针增加一 */
S->top++;
/* 将新插入元素赋值给栈顶空间 */
S->data[S->top] = e;
return OK;
}

1.3.3 栈的顺序存储结构——出栈
出栈操作pop,代码如下:
/* 若栈不空,则删除S的栈顶元素,用e返回其值,
并返回OK;否则返回ERROR
*/ Status Pop(SqStack *S, SElemType *e)
{
if (S->top == -1)
return ERROR;
/* 将要删除的栈顶元素赋值给e */
*e = S->data[S->top];
/* 栈顶指针减一 */
S->top–;
return OK;
}

1.3.4两栈共享空间
数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即 下标为0处,另一个栈为数组的末端,即下标为数组长度n-1处。这样,两个栈如果增加元素,就是两端点向中间延伸。
两栈共享空间
其实关键思路是:它们是在数组的两端,向中间靠拢。top1和top2是栈1和栈2的栈顶指针,可以 想象,只要它们俩不见面,两个栈就可以一直使用。

对于两栈共享空间的push方法,我们除了要插入元素值参数外,还需要有一个判断是栈1还是栈2 的栈号参数stackNumber。插入元素的代码如下:
/* 插入元素e为新的栈顶元素 */
Status Push(SqDoubleStack *S, SElemType e,
int stackNumber)
{
/* 栈已满,不能再push新元素了 */
if (S->top1 + 1 == S->top2)
return ERROR;
/* 栈1有元素进栈 */
if (stackNumber == 1)
/* 若栈1则先top1+1后给数组元素赋值 */
S->data[++S->top1] = e;
/* 栈2有元素进栈 */
else if (stackNumber == 2)
/* 若栈2则先top2-1后给数组元素赋值 */
S->data[–S->top2] = e;
return OK;
}

对于两栈共享空间的pop方法,参数就只是判断栈1栈2的参数stackNumber,代码如下:
/* 若栈不空,则删除S的栈顶元素,用e返回其值, 并返回OK;否则返回ERROR */
Status Pop(SqDoubleStack *S, SElemType *e,
int stackNumber)
{
if (stackNumber == 1)
{
/* 说明栈1已经是空栈,溢出 */
if (S->top1 == -1)
return ERROR;
/* 将栈1的栈顶元素出栈 */
*e = S->data[S->top1–];
}
else if (stackNumber == 2)
{
/* 说明栈2已经是空栈,溢出 */
if (S->top2 == MAXSIZE)
return ERROR;
/* 将栈2的栈顶元素出栈 */
*e = S->data[S->top2++];
}
return OK;
}
使用这样的数据结构,通常都是当两个栈的空间需求有相反关系时,也就是一个栈增长时 另一个栈在缩短的情况。

1.4 栈的链式存储结构及实现
1.4.1 栈的链式存储结构
栈的链式存储结构,简称为链栈,通常对于链栈来说,是不需要头结点的。对于链栈来说,基本不存在栈满的情况,除非内存已经没有可以使用的空间,如果真的发生,那此 时的计算机操作系统已经面临死机崩溃的情况,而不是这个链栈是否溢出的问题。

1.4.2 栈的链式存储结构——进栈操作
对于链栈的进栈push操作,假设元素值为e的新结点是s,top为栈顶指针。
/* 插入元素e为新的栈顶元素 */
Status Push(LinkStack *S, SElemType e)
{
LinkStackPtr s
= (LinkStackPtr)malloc(sizeof(StackNode));
s->data = e;
/* 把当前的栈顶元素赋值给新结点的直接后继,如图中① */
/* 将新的结点s赋值给栈顶指针,如图中② */
S->top = s;
return OK;
}

1.4.3 栈的链式存储结构——出栈操作
至于链栈的出栈pop操作,也是很简单的三句操作。假设变量p用来存储要删除的栈顶结点,将栈 顶指针下移一位,最后释放p即可.
/* 若栈不空,则删除S的栈顶元素,用e返回其值, 并返回OK;否则返回ERROR */
Status Pop(LinkStack *S, SElemType *e)
{
LinkStackPtr p;
if (StackEmpty(*S))
return ERROR;
*e = S->top->data;
/* 将栈顶结点赋值给p,如图③ */
p = S->top;
/* 使得栈顶指针下移一位,指向后一结点,如图④ */
S->top = S->top->next;
/* 释放结点p */
free(p);
S->count–;
return OK;
}

1.5 栈的应用——递归
在高级语言中,调用自己和其他函数并没有本质的不同。我们把一个直接调用自己或通过一系列的 调用语句间接地调用自己的函数,称做递归函数。
写递归程序最怕的就是陷入永不结束的无穷递归中,所以,每个递归定义必须至少有一个条 件,满足时递归不再进行,即不再引用自身而是返回值退出。
栈还有更多应用,继续发掘吧。

2. 队列

2.1 队列的定义
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许 删除的一端称为队头。假设队列是q=(a1,a2,……,an),那么a1就是队头元素,而an是队尾元素。这 样我们就可以删除时,总是从a1开始,而插入时,列在最后。
队列

2.2 队列的抽象数据类型
ADT 队列(Queue)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation
InitQueue(*Q): 初始化操作,建立一个空队列Q。
DestroyQueue(*Q): 若队列Q存在,则销毁它。
ClearQueue(*Q): 将队列Q清空。
QueueEmpty(Q): 若队列Q为空,返回true,否则返回false。
GetHead(Q, *e): 若队列Q存在且非空,用e返回队列Q的队头元素。
EnQueue(*Q, e): 若队列Q存在,插入新元素e到队列Q中并成为队尾元素。
DeQueue(*Q, *e): 删除队列Q中队头元素,并用e返回其值。
QueueLength(Q): 返回队列Q的元素个数
endADT

2.3 循环队列
把队列的这种 头尾相接的顺序存储结构称为循环队列。
循环队列的顺序存储结构代码如下:
/* QElemType类型根据实际情况而定,这里假设为int */
typedef int QElemType;
/* 循环队列的顺序存储结构 */
typedef struct
{
QElemType data[MAXSIZE];
/* 头指针 */
int front;
/* 尾指针,若队列不空, 指向队列尾元素的下一个位置 */
int rear;
}
SqQueue;

循环队列的初始化代码如下:
Status InitQueue(SqQueue *Q)
Q->front = 0;
return OK;
}

循环队列求队列长度代码如下:
/* 返回Q的元素个数,也就是队列的当前长度 */
int QueueLength(SqQueue Q)
{
return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}

循环队列的入队列操作代码如下:
Status EnQueue(SqQueue *Q, QElemType e)
{
if ((Q->rear + 1) % MAXSIZE == Q->front)
return ERROR;
Q->data[Q->rear] = e;
/* rear指针向后移一位置, */
return OK;
}

循环队列的出队列操作代码如下:
/* 若队列不空,则删除Q中队头元素,用e返回其值 */
Status DeQueue(SqQueue *Q, QElemType *e)
{
/* 队列空的判断 */
return ERROR;
/* 将队头元素赋值给e */
*e = Q->data[Q->front];
/* front指针向后移一位置, */
Q->front = (Q->front + 1) % MAXSIZE;
/* 若到最后则转到数组头部 */
return OK;
}
2.4 队列的链式存储结构及实现
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。
链队列
链队列的结构为:
/* QElemType类型根据实际情况而定,这里假设为int */
typedef int QElemType;
/* 结点结构 */ typedef struct QNode
{
QElemType data;
struct QNode *next;
}
QNode, *QueuePtr;
typedef struct
{
/* 队头、队尾指针 */
QueuePtr front, rear;
} LinkQueue;

2.4.1 队列的链式存储结构——入队操作
入队操作时,其实就是在链表尾部插入结点:
其代码如下:
/* 插入元素e为Q的新的队尾元素 */
Status EnQueue(LinkQueue *Q, QElemType e)
{
QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
/* 存储分配失败 */
if (!s)
exit(OVERFLOW);
s->data = e; s->next = NULL;
/* 把拥有元素e新结点s赋值给原队尾结点的后继, */
Q->rear->next = s;
/* 见上图中① */
/* 把当前的s设置为队尾结点,rear指向s,见上图中② */
Q->rear = s;
return OK;
}

2.4.2 队列的链式存储结构——出队操作
出队操作时,就是头结点的后继结点出队,将头结点的后继改为它后面的结点,若链表除头结点外 只剩一个元素时,则需将rear指向头结点。
代码如下:
/* 若队列不空,删除Q的队头元素,用e返回其值, 并返回OK,否则返回ERROR */
Status DeQueue(LinkQueue *Q, QElemType *e)
{
QueuePtr p; if (Q->front == Q->rear)
/* 将欲删除的队头结点暂存给p,见上图中① */
p = Q->front->next;
/* 将欲删除的队头结点的值赋值给e */
*e = p->data;
/* 将原队头结点后继p->next赋值给头结点后继, */
Q->front->next = p->next;
/* 见上图中② */
/* 若队头是队尾,则删除后将rear指向头结点,见上图中③ */
if (Q->rear == p)
Q->rear = Q->front;
free(p);
return OK;
}

3.栈与队列总结回顾

栈(stack)是限定仅在表尾进行插入和删除操作的线性表。
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
它们均可以用线性表的顺序存储结构来实现,但都存在着顺序存储的一些弊端。因此它们各自有各 自的技巧来解决这个问题。
对于栈来说,如果是两个相同数据类型的栈,则可以用数组的两端作栈底的方法来让两个栈共享数 据,这就可以最大化地利用数组的空间。
对于队列来说,为了避免数组插入和删除时需要移动数据,于是就引入了循环队列,使得队头和队 尾可以在数组中循环变化。解决了移动数据的时间损耗,使得本来插入和删除是O(n)的时间复杂 度变成了O(1)。
它们也都可以通过链式存储结构来实现,实现原则上与线性表基本相同如图所示。
这里写图片描述

哎哟太多了,还有串和树没写…写好了以后交吧

二、串

三、树

0 0
原创粉丝点击