栈和队列——超详细

一、定义

1.栈的定义:

栈是只允许在一端进行插入或删除的线性表。首先栈是一种线性表,但限定这种线性表只能在某一端进行插入和删除操作。

  • 栈顶(Top):线性表允许进行插入删除的那一端;
  • 栈底 (Bottom) :固定的,不允许进行插入和删除的另一端;
  • 空栈 :不含任何元素;

栈又有两种书写形式:数组栈链式栈,两种都可以,非要选一种,数组栈更好,虽然会造成一定的内存空间浪费,但是链式栈太麻烦。

链式栈栈顶的选择:

  • 如果用尾做栈顶,尾插尾部删,要设计成双向链表,否则删除数据效率变低
  • 如果用头做栈顶,头插头删就可以设计成单链表。

自己理解了一下栈这个概念,就可以看做一个弹夹式手枪,先放进去的子弹要最后才能被打出来,最后放进去的子弹,是第一个被打出来。

栈的插入操作,叫做进栈,也称压栈、入栈。类似子弹入弹夹。

栈的删除操作,叫做出栈,有点也叫做弹栈。如同弹夹里的子弹出来。

2,进栈出栈的变化形式:

最先进栈的元素,是不是只能是最后出栈呢?我才开始认为这句话没问题,但是在看到书本解析时我才知道这句话是不对的。例如:若3、2、1进栈,那么出栈不止可以3、2、1,也可以是3进,3出,2进,2出,1进,1出,那么出栈就是1、2、3。这样不同的出栈次序约有5种。所以可以做出总结:最先进栈的元素不一定最后出栈。

二、栈的抽象数据类型:

对于栈来说,理论上线性表的操作特性它都具备,可是由于它的特殊性,所以针对它的操作上会有些许变化。特别是插入和删除操作,我们理解为弹和压,更容易理解。

InitStack(*S)//初始化操作,建立一个空栈
DestroyStack(*S)//若栈存在,就销毁它
ClearStack(*S)//将栈清空
StackEmpty(*S)//若栈为空,返回true,否则返回false
GetTop(S,*e)//若栈存在且非空,用e返回S的栈顶元素
Push(*S,*e)//若栈S存在,插入新元素e到栈S中,并成为栈顶元素
Pop(*S,*e)//删除栈S中栈顶元素,并用e返回其值
StackLength(S)//返回栈S的元素个数
/* 链栈结构 */
typedef struct StackNode
{
    SElemType data;
    struct StackNode* next;
}StackNode, * LinkStackPtr;


typedef struct
{
    LinkStackPtr top;
    int count;
}LinkStack;

Status visit(SElemType c)
{
    printf("%d ", c);
    return OK;
}

/*  构造一个空栈S */
Status InitStack(LinkStack* S)
{
    S->top = (LinkStackPtr)malloc(sizeof(StackNode));
    if (!S->top)
        return ERROR;
    S->top = NULL;
    S->count = 0;
    return OK;
}

/* 把S置为空栈 */
Status ClearStack(LinkStack* S)
{
    LinkStackPtr p, q;
    p = S->top;
    while (p)
    {
        q = p;
        p = p->next;
        free(q);
    }
    S->count = 0;
    return OK;
}

/* 若栈S为空栈,则返回TRUE,否则返回FALSE */
Status StackEmpty(LinkStack S)
{
    if (S.count == 0)
        return TRUE;
    else
        return FALSE;
}

/* 返回S的元素个数,即栈的长度 */
int StackLength(LinkStack S)
{
    return S.count;
}

/* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
Status GetTop(LinkStack S, SElemType* e)
{
    if (S.top == NULL)
        return ERROR;
    else
        *e = S.top->data;
    return OK;
}

/* 插入元素e为新的栈顶元素 */
Status Push(LinkStack* S, SElemType e)
{
    LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
    s->data = e;
    s->next = S->top;	/* 把当前的栈顶元素赋值给新结点的直接后继,见图中① */
    S->top = s;         /* 将新的结点s赋值给栈顶指针,见图中② */
    S->count++;
    return OK;
}

/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(LinkStack* S, SElemType* e)
{
    LinkStackPtr p;
    if (StackEmpty(*S))
        return ERROR;
    *e = S->top->data;
    p = S->top;					/* 将栈顶结点赋值给p,见图中③ */
    S->top = S->top->next;    /* 使得栈顶指针下移一位,指向后一结点,见图中④ */
    free(p);                    /* 释放结点p */
    S->count--;
    return OK;
}

Status StackTraverse(LinkStack S)
{
    LinkStackPtr p;
    p = S.top;
    while (p)
    {
        visit(p->data);
        p = p->next;
    }
    printf("\n");
    return OK;
}

三、栈的顺序储存结构及实现:

1.栈的顺序储存结构:

既然栈是线性表的特例,那么栈的顺序储存其实也是线性表顺序储存的简化,我们简称为顺序栈

顺序表是用数组来实现的,对于栈这种只能一头插入删除的线性表来说,用数组下标为0的一端作为栈底比较好应为首元素都存在栈底,变化最小,所以让它做栈底。

我们定义一个top变量来指示栈顶元素在数组中的位置,它可以变大变小。若储存栈的长度为StackSize,则栈顶位置top必须小于StackSize当栈存在一个元素时,top等于0,因此通常把空栈的判定条件定为top等于-1

栈的结构定义:

typedef int SElemType;
//顺序栈结构
typedef struct
{
	SElemType data[MAXSIZE];
	int top;//用于栈顶指针
}SqStack;

若有一个栈,StackSize是5,则栈普通情况、空栈和满栈的情况如下图:

2.栈的顺序储存结构——进栈操作

操作如图所示:

因此对于进栈操作push,其代码如下:

Status Push(SqStack* S, SElemType e)
//Status 是函数的类型,其值是函数结果状态代码,如OK等
{
	if (S->top == MAXSIZE - 1)
	{
		return ERROR;//这个说明栈满,无法再进栈
	}
	S->top++;//栈顶指针增加1
	S->data[S->top] = e; //赋值
	return OK;
}

Status 是函数的类型,其值是函数结果状态代码,如OK等。函数返回值只不过是时函数处理的状态,返回类型Status是一个整型,返回OK代表1,ERROR代表0。

typedef int Status;

3.栈的顺序储存结构——出栈操作

出栈操作pop,代码如下:

Status Pop(SqStack* S, SElemType* e)
{
	if (S->top == -1)
	{
		return ERROR;//空栈
	}
	*e = S->data[S->top];//将要删除的栈顶元素赋值给e
	S->top--;
	return OK;
}

四、两栈共享空间: 

栈的顺序储存还是很方便的,因为它只准栈顶进出元素,所以不存在线性表插入删除时需要移动元素的问题,不过它有一个很大的缺陷,就是必须事先确定数组存储空间大小,万一不够用了,就需要用编程手段来扩展数组的容量,非常麻烦。对于一个栈,我们也只能尽量考虑周全,设计出合适大小的数组来处理,但对于两个相同类型的栈,我们却可以做到最大限度地利用其事先开辟的储存空间来进行操作。

如果我们有两个类型相同的栈,我们为它们各自开辟了数组空间,极有可能是第一个已经满了,再进栈就溢出了,而另一个栈还有很多储存空间空闲,这又是何必呢?我们完全可以用一个数组来储存两个栈,充分利用这个数组占用的内存空间,只不过实现需要一些小技巧。

做法如下图:数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为零处,另一个栈为数组的末端,即下标为数组长度n-1处。这样,两个栈如果增加元素,就是两端点向中间延伸。

其实关键思路是:它们是在数组的两端,向中间靠拢。top1和top2是栈1和栈2的栈顶指针,可以想象,只要它们两不见面,两个栈就可以一直使用。

从这里分析出来,栈1为空时,就是top1等于-1时;而当top2等于n时,即是栈2为空时,那什么时候栈满呢?                              

咱们根据这个图,简单推理一下,就可以发现当栈2为空,也就是top1=n-1时也就是栈一满了;反之,当栈1为空栈时,top2等于0时,栈二满。但跟多情况,其实就是两栈见面之时,也就是两个指针之间相差1时,即top1+1==top2为栈满。(这个式子表达的是指针位置之间的关系)

两栈共享结构代码:

//两栈共享空间结构
typedef struct
{
	SElemType data[MAXSIZE];
	int top1;
	int top2;
}SqDoubleStack;

对于两栈共享push方法,我们除了要插入元素值参数外,还需要有一个判断是栈1还是栈2的栈号参数stackNumber。这个栈号参数我们可以理解为,准备向那个栈里加东西,方便后续操作。

//插入元素e为新的栈顶元素
Status Push(SqDoubleStack* S, SElemType e, int stackNumber)
{
	if (S->top1 + 1 == S->top2)//栈满
	{
		return ERROR;
	}
	else if (stackNumber == 1)//若栈1有元素进栈
	{
		S->data[++S->top1] = e;//先+1再赋值
		return OK;
	}
	else if (stackNumber == 2)//若栈2有元素进栈
	{
		S->data[--S->top2] = e;//先-1再赋值
		return OK;
	}
}

因为在代码开始时已经判断了是否有栈满的情况,所以后面的top1+1或top2-1是不用担心溢出的问题,

对于两栈共享空间pop方法,参数就只是判断栈1,栈2的参数stackNumber,代码如下:

//若栈不空,则删除栈顶元素,用e返回其值,并返回OK;否则返回ERROR
Status Pop(SqDoubleStack* S, SElemType* e, int stackNumber)
{
	if (stackNumber == 1)
	{
		if (S->top1 == -1)//栈1空栈,溢出
		{
			return ERROR;
		}
		*e = S->data[S->top1--];//先赋值,再--
	}
	else if (stackNumber == 2)
	{
		if (S->top2 == MAXSIZE)//说明栈2空栈,溢出
		{
			return ERROR;
		}
		*e = S->data[S->top2++];//先赋值,再++
	}
	return OK;
}

其实,我在学这个两栈共享空间这个概念的时候,我并不能理解它所存在的意义是什么?两个栈都在不停的增长很快就会因为栈满而溢出了。但是当我看过讲解之后,才知道,使用这样的数据结构,通常都是当两个栈的空间需求有相反关系时,也就是一个栈增长时另一个栈在缩短的情况。这样使用两栈共享空间储存方法才有较大意义。所以解决问题要选择适当的方法,不然会让问题复杂化。

五、栈的链式储存结构及实现

1.栈的链式储存结构:

栈的链式储存结构,简称为链栈。想想看栈只是栈顶来做插入和删除工作,那么栈顶放在链表的头部还是尾部呢?而单链表刚好具有头指针,而栈顶指针也是必须,我们可以将其和二为一,所以比较好的方法是把栈顶放在单链表的头部,另外已经有了栈顶在头部了,单链表常用的头结点也就失去了意义,通常对于链栈来说,是不需要头结点的

对于链栈来说基本上不存在栈满的情况,除非内存已经没有可以使用的空间,如果真的发生,那么此时计算机操作系统已经面临死机崩溃的情况,而不是这个链栈是否溢出的问题。

对于空栈来说,链表原定义是头指针指向空,那么链栈的空其实就是top=NULL的时候

链栈的结构代码如下:

/* 链栈结构 */
typedef struct StackNode
{
        SElemType data;
        struct StackNode *next;
}StackNode,*LinkStackPtr;


typedef struct
{
        LinkStackPtr top;
        int count;
}LinkStack;

 链栈的绝大部分操作都和单链表相似,只是在插入和删除上,特殊了一些。

2.栈的链式储存结构——进栈操作

对于链栈的进栈push操作,假设元素值为e的新节点是s,top为栈顶指针,代码如下,

//插入元素e为新的栈顶元素
Status Push(LinkStack* S, SElemType e)
{
	LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
	s->data = e;
	s->next = S->top;
	S->top = s;
	S->count++;
	return OK;
}

这一串代码还是需要慢慢理解一下的,我才开始并不能够明白思路,我们先结合图来看一下,首先s是一个新的节点,我们需要给它适当的空间,类型就是LinkStackPtr,大小就是StackNode(一个节点)的大小,这个用法不熟悉的话,需要去了解一下malloc的用法,接着就是给新节点赋值为e,把当前的栈顶元素赋值给新节点的直接后继再将新的节点s赋值给栈顶指针,这个单单的是看着,有点想不出来,看图!!!将栈顶元素赋值给新节点的直接后继是为了入栈操作,将它接上去,但是这样一操作栈顶处于第二个节点,所以我们需要将刚刚的新节点赋值给栈顶指针,让新节点成为新的栈顶,这样可以理解出,栈顶已经不再是之前的那个栈顶了,最后count++就不用说了,就这样入栈操作完成。

3.栈的链式储存结构——出栈操作

至于链栈的出栈pop操作,也是很简单的,看代码:

StackEmpty(*S)//若栈为空,返回true,否则返回false
//若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK,否则返回ERROK
Status Pop(LinkStack* S, SElemType* e)
{
	LinkStackPtr p;
	if (StackEmpty(*S))//判断是否为空
	{
		return ERROR;
	}
	*e = S->top->data;//用e储存将要删除的节点数据
	p = S->top;//将栈顶节点赋值给p
	S->top = S->top->next; //使得栈顶指针下移一位,指向后一节点
	free(p);//释放节点p
	S->count--;
	return OK;
}

这个操作还是很容易理解的,首先我们创建一个新节点p,这个p不需要提前给它空间,然后再判断链栈S是否为空,这之后我直接使用了之前写的函数,判断其是否为空,如果为空则不能继续进行删除操作,之后就是将要删除的数据储存在e中,要使用指针来进行赋值。接下来将栈顶节点赋值给p,然后将栈顶后移一位,指向后一节点,也就意味着原先的第二个节点变成了栈顶节点。最后释放p节点即可,count--就不用多说什么了。

六、栈的作用

我才学完栈这个概念的时候,感觉其实这些问题用链表和数组都能够轻易解决,那为什么要引入这样的数据结构呢?栈的引入简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦于我们所要解决的问题。

七、队列的定义

  1. 队列是只允许在一段进行插入操作,而在另一端进行删除操作的线性表。
  2. 队列是一种先进先出的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一段称为队头。假设队列是q=(a,b,c.....h,i,j,k),那么a就是队头元素,而k就是队尾元素,这样我们就可以删除时总是从a开始,而插入时,列在最后。这也比较符合我们通常的生活习惯,排在第一个的优先出列,最后来的当然排在队伍最后,如下图所示:

八、队列的抽象数据类型

同样是线性表,队列也有类似线性表的各种操作,不同的就是插入数据只能在队尾进行,删除数据只能在队头进行。

InitQueue(*Q);//初始化操作,建立一个空队列Q
DestoryQueue(*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的元素个数

九、循环队列

1.队列顺序储存不足: 

我们假设一个队列有n个元素,则顺序储存的对垒需要建立一个大于n的数组,并把队列的所有元素储存在数组的前n个单元,数组下标为0的一端即是队头。所谓的入队操作,其实就是在队尾追加一个元素。如下图所示:

与栈不同的是,队列元素的出列是在队头,即下标为0的位置,那也就意味着,队列中的元素都得向前移动,以保证队列的队头,也就是下表为0的位置不为空。如下图所示:

可是想想,为什么出队列时一定要全部移动呢?如果不去限制队列的元素必须存储在数组的前n个单元这一条件,出队的可能性就会大大增加。也就是说,队头不需要一定在下标为0的位置如下图所示:

为了避免当只有一个元素时,队头和队尾重合使处理变得麻烦,所以引入两个指针,front指针指向队头元素,rear指针指向队尾元素的下一个位置,这样当front等于rear时,此队列不是还剩一个元素,而是空队列。

假设长度为5的数组,初始状态,如下图,front和rear指针均指向下标为0位置,而rear指针指向下标为4的位置,如下图所示:

出队a1、a2,则front指针指向下标为2的位置,rear不变,如下图所示,再入队a5,此时front不变,rear指针移动到数组之外,如下图所示:

数组之外又是哪里?问题不止于此,假设这个队列的总个数不超过5个,但目前如果接着入队的话,因数组,,末尾元素已经被占用,再向后加,就会产生数组越界的错误,可实际上,我们的队列在下标为0和1的地方还是空闲的。我们把这种现象叫做“假溢出”。

2.循环队列的定义:

所以解决假溢出的办法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把这队列的这种头尾相接的顺序储存结构称为循环队列

刚才的例子继续,上图的rear可以改为指向下标为零的位置,这样就不会造成指针指向不明的问题了,如下图所示:

接着入队a6,将它放置于下标为0处,rear指针指向下标为1处,如下图所示,若再有入队a7,则rear指针就与front指针重合,同时指向下标为2的位置,如下图所示:

  • 此时问题又出来了,我们刚才说,空队列时front等于rear,现在当队列满时,也是front等于rear,那么如何判断此时的队列是空还是满呢?
  • 方法一:设置一个标志变量flag,当front=rear,且flag=0时为队列空;当ront=rear且flag=0时为队列满。
  • 方法二:当队列空时条件为front=rear,当队列满时,我们修改其中的条件,保留一个元素空间。也就是说队列,满时,数组中还有一个空闲单元。例如下图所示,我们就认为此队列已经满了,也就是说,我们不允许上图的情况出现。

我们重点来讨论第二种方法,由于rear可能比front大,也可能比它小,所以尽管它们只相差一个位置就是满的情况,但也可能是相差整整一圈。所以若队列的最大尺寸为QueueSize,那么队列满的条件是(rear+1)%QueueSize==front(取模“%”的目的就是为了整合rear与front大小为一个问题)。自己尝试着算算就行了,可以理解的。

另外当rear>front时,此时队列的长度为rear-front,如下图所示。所以直接给一个总结性的公式:

(rear-front+QueueSize)%QueueSize,有了这些知识,实现循环队列代码就不难了

直接写代码:

typedef int Status;
typedef int QElemType;

//循环队列的顺序储存结构
typedef struct
{
	QElemType data[MAXSIZE];
	int front;//头指针
	int rear;//尾指针,若列队不空,指向列队尾元素的下一个
}SqQueue;

Status visit(QElemType e)
{
	printf("%d", e);
	return OK;
}


//初始化一个空队列
Status InitQueue(SqQueue* Q)
{
	Q->front = 0;
	Q->rear = 0;
	return OK;
}

//将Q清为空队列
Status ClearQueue(SqQueue* Q)
{
	Q->front = Q->rear = 0;
	return OK;
}

//若队列Q为空队列,则返回TRUE,否则返回FALSE
Status QueueEmpty(SqQueue Q)
{
	if (Q.front == Q.rear)//空队列的标志
	{
		return ERROR;
	}
	else
	{
		return OK;
	}
}

//返回Q的元素个数,也就是队列当前长度
int QueueLength(SqQueue Q)
{
	return (Q.rear - Q. front + MAXSIZE) % MAXSIZE;
}

//若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR
Status GetHead(SqQueue Q, QElemType* e)
{
	if (Q.front == Q.rear) /* 队列空 */
	{
		return ERROR;
	}
	*e = Q.data[Q.front];
	return OK;
}

//若队列不空,则删除Q中队头元素,用e返回其值
Status DeQueue(SqQueue* Q, QElemType* e)
{
	if (Q->front == Q->rear)			/* 队列空的判断 */
	{
		return ERROR;
	}
	*e = Q->data[Q->front];				/* 将队头元素赋值给e */
	Q->front = (Q->front + 1) % MAXSIZE;	/* front指针向后移一位置, */
	/* 若到最后则转到数组头部 */
	return  OK;
}

//从队头到队尾依次对队列Q中每个元素输出
Status QueueTraverse(SqQueue Q)
{
	int i;
	i = Q.front;
	while ((i + Q.front) != Q.rear)
	{
		visit(Q.data[i]);
		i = (i + 1) % MAXSIZE;//后移一位
	}
	printf("\n");
	return OK;
}

十、队列的链式储存结构及实现:

队列的链式储存结构,其实就是线性表单链表,只不过它只能尾进头出而已,我们把它简称为链队列,为了操作上的方便,我们将队头指针指向链队列的头结点,而队尾指针指向终端节点,如下图所示:

链队列的结构为:

typedef int Status; 

typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */

typedef struct QNode	/* 结点结构 */
{
   QElemType data;
   struct QNode *next;
}QNode,*QueuePtr;

typedef struct			/* 队列的链表结构 */
{
   QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;
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;
	Q->rear->next=s;	/* 把拥有元素e的新结点s赋值给原队尾结点的后继,见图中① */
	Q->rear=s;		/* 把当前的s设置为队尾结点,rear指向s,见图中② */
	return OK;
}

2.队列链式储存结构——出队操作

出队操作时,就是头结点的后继节点出队,将头结点的后继改为它后面的节点,若链表除头节点外只剩一个元素,则需要将rear指向头结点,如下图所示:

代码如下:

/* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */
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=Q->front;
	free(p);
	return OK;
}
3.链队列的操作代码:
typedef int Status; 

typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */

typedef struct QNode	/* 结点结构 */
{
   QElemType data;
   struct QNode *next;
}QNode,*QueuePtr;

typedef struct			/* 队列的链表结构 */
{
   QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;

Status visit(QElemType c)
{
	printf("%d ",c);
	return OK;
}

/* 构造一个空队列Q */
Status InitQueue(LinkQueue *Q)
{ 
	Q->front=Q->rear=(QueuePtr)malloc(sizeof(QNode));
	if(!Q->front)
		exit(OVERFLOW);
	Q->front->next=NULL;
	return OK;
}

/* 销毁队列Q */
Status DestroyQueue(LinkQueue *Q)
{
	while(Q->front)
	{
		 Q->rear=Q->front->next;
		 free(Q->front);
		 Q->front=Q->rear;
	}
	return OK;
}

/* 将Q清为空队列 */
Status ClearQueue(LinkQueue *Q)
{
	QueuePtr p,q;
	Q->rear=Q->front;
	p=Q->front->next;
	Q->front->next=NULL;
	while(p)
	{
		 q=p;
		 p=p->next;
		 free(q);
	}
	return OK;
}

/* 若Q为空队列,则返回TRUE,否则返回FALSE */
Status QueueEmpty(LinkQueue Q)
{ 
	if(Q.front==Q.rear)
		return TRUE;
	else
		return FALSE;
}

/* 求队列的长度 */
int QueueLength(LinkQueue Q)
{ 
	int i=0;
	QueuePtr p;
	p=Q.front;
	while(Q.rear!=p)
	{
		 i++;
		 p=p->next;
	}
	return i;
}

/* 若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR */
Status GetHead(LinkQueue Q,QElemType *e)
{ 
	QueuePtr p;
	if(Q.front==Q.rear)
		return ERROR;
	p=Q.front->next;
	*e=p->data;
	return OK;
}


/* 插入元素e为Q的新的队尾元素 */
Status EnQueue(LinkQueue *Q,QElemType e)
{ 
	QueuePtr s=(QueuePtr)malloc(sizeof(QNode));
	if(!s) /* 存储分配失败 */
		exit(OVERFLOW);
	s->data=e;
	s->next=NULL;
	Q->rear->next=s;	/* 把拥有元素e的新结点s赋值给原队尾结点的后继,见图中① */
	Q->rear=s;		/* 把当前的s设置为队尾结点,rear指向s,见图中② */
	return OK;
}

/* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */
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=Q->front;
	free(p);
	return OK;
}

/* 从队头到队尾依次对队列Q中每个元素输出 */
Status QueueTraverse(LinkQueue Q)
{
	QueuePtr p;
	p=Q.front->next;
	while(p)
	{
		 visit(p->data);
		 p=p->next;
	}
	printf("\n");
	return OK;
}

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qsl&F

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值