数据结构入门3-1(栈与队列)

目录

栈和队列的定义和特点

栈的定义和特点

队列的定义和特点

栈的表示和实现

栈的类型定义

顺序栈的表示和实现

链栈的表示和实现

补充结构:双栈的表示和实现

栈与递归

采用递归算法解决问题

1. 定义是递归的

2. 数据结构是递归的

3. 问题的解法是递归的

递归过程与递归工作栈

递归算法的效率分析

1. 时间复杂度的分析

2. 空间复杂度的分析

利用栈将递归转换成非递归的方法

队列的表示和操作的实现

队列的类型定义

循环队列——队列的顺序表示和实现

链队——队列的链式表示和实现

 链队(补充)—— 只有尾指针


        本笔记参考:《数据结构(C语言版)》


        栈和队列是两种重要且存在限定性的数据结构。之所以说存在限定性,是因为栈和队列的基本操作是线性表存在的子集,它们的操作是受到限制的。

栈和队列的定义和特点

栈的定义和特点

||| 栈——先进后出(Last In Firtst out,LIFO)

        栈(stack)是限定仅在表尾进行插入或删除操作的线性表。不含元素的栈被称为空栈

        利用栈,可以按照保存数据时相反的顺序使用数据。

        栈可以在类似于 数制的转换 这类问题中被使用:在输入时我们需要将每次计算得到的数据压入栈内,在计算结束后再依次弹出栈中的数据,从而得到数制转换的结果。(除此之外还有:“期待的急迫程度”,表达式求值算法等)

队列的定义和特点

||| 队列——先进先出(First In First Out,FIFO)

        队列只允许在表的一端进行插入,而在另一端删除数据

        队列最经典的例子就是操作系统中的作业排队,凡是申请输出的作业都从队尾进入队列,传输完毕后,队头的作业先从队列中退出输出操作。

        不论是栈还是队列,它们最基本的操作都是“入”和“出”。类似于线性表,栈和队列的存储结构也包含顺序和链式两种。

栈的表示和实现

栈的类型定义

        栈的基本操作包括:

  • 入栈
  • 出栈
  • 栈的初始化
  • 栈空的判定
  • 取栈顶元素等
  • ……

顺序栈的表示和实现

        顺序栈,顾名思义,指利用顺序存储结构实现的栈(即利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素),顺序栈的定义如下:

#define MAXSIZE 100		//顺序栈存储空间的初始分配量
#define SElemType int

typedef struct
{
	SElemType* base;	//栈底指针
	SElemType* top;		//栈顶指针
	int stacksize;		//栈可用的最大容量
}SqStack;

其中:

1. 初始化

  目的:为顺序栈动态分配一个预定义大小的数组空间。

Status InitStack(SqStack& S)			//暂定 Status 的类型为 bool
{//构造一个空栈S
	S.base = new SElemType[MAXSIZE];	//为顺序栈动态分配一个最大容量为MAXSIZE的数组空间

	if (!S.base)				
		exit(OVERFLOW);					//存储分配失败(ps:exit()的使用需要调用头文件iostream)

	S.top = S.base;						//top初始为base,空栈
	S.stacksize = MAXSIZE;				//stacksize置为最大容量MAXSIZE
	return true;
}

2. 入栈

  目的:在栈顶插入一个新的元素。

Status Push(SqStack& S, SElemType e)
{//插入元素e为新的栈顶元素
	if (S.top - S.base == S.stacksize)	//栈满
		return false;

	*S.top++ = e;						//元素e压入栈顶,栈顶指针加1
	return true;
}

3. 出栈

  目的:将栈顶元素删除。(会改变栈顶指针,与下面的取出栈顶元素相区分)

Status Pop(SqStack& S, SElemType& e)
{//删除S的栈顶元素,用e返回其值
	if (S.top == S.base)				//栈空
		return false;

	e = *--S.top;						//栈顶指针减1,将栈顶元素赋给e
	return true;
}

【分析】

        [ e = *--S.top; ]:在这条语句中,= 的优先级低于 * 和 -- ,而 * 和 -- 的优先级是一致的,此时观察结合性,发现结合性是R-L(即从右向左),也就是说,这三个操作符的执行顺序是:

  1. --:将栈顶指针减1,找到栈顶元素;
  2. * :解引用栈顶元素的地址,找到栈顶元素的数据;
  3. = :将该数据赋值给 e 。

4. 取栈顶元素

  目的:当栈非空时,返回当前栈顶元素的值,栈顶指针保持不变

SElemType GetTop(SqStack S)             //此处没有使用 &操作符 ,因为不要求修改指针
{//返回S的栈顶元素,不修改栈顶指针
	if (S.top != S.base)				//栈非空
		return *(S.top - 1);			//返回栈顶元素的值,栈顶指针不变
}

5. 删除栈

  目的:返回栈申请的空间,置空两个指针。

Status DestoryStack(SqStack& S)
{//返回空间,置空栈顶、栈底指针
		delete[] S.base;            //返回空间
		S.base = S.top = NULL;

	return true;
}

        类似于顺序表,顺序栈也会受到最大空间容量的限制,尽管可以用再分配空间扩大容量,但不推荐。而弥补这一缺点的,就是接下来的链栈。

链栈的表示和实现

        链栈——采用链式存储结构实现的栈,通常用单链表表示。

定义如下:

#define ElemType int        //自定义SElemType的类型

typedef struct StackNode
{
	ElemType data;
	struct StackNode* next;
}StackNode, *LinkStack;

         此处不用像一些单链表一样附加一个头结点,原因:栈的主要操作是在栈顶插入和删除,以链表的头部作为栈顶是最方便的。

1. 初始化

  目的:构造一个空栈,将栈顶指针置为空(没有头结点)

Status InitStack(LinkStack& S)		//同上,将Status的类型暂定为bool类型
{//构造一个空栈S,栈顶指针置空
	S = NULL;
	return true;
}

2. 入栈

  目的:为入栈元素动态分配一个结点空间(不需要判断栈是否满)

Status Push(LinkStack& S, ElemType e)
{//在栈顶插入元素e
	LinkStack p = new StackNode;	//生成新的结点
	p->data = e;					//在新的数据域中存储数据e
	p->next = S;					//将新结点插入栈顶
	S = p;							//修改栈顶指针S为p
    
    return true; 
}

【分析】


3. 出栈

  目的:删除栈顶元素。

  不同点:

  1. 和顺序栈一样,需要判断栈是否为空;
  2. 链栈在出栈之后还要释放出栈元素的栈顶空间。
Status Pop(LinkStack& S, ElemType& e)
{//删除S的栈顶元素,用e返回其值
	if (S == NULL)					//栈空
		return false;

	e = S->data;					//将栈顶元素赋给e
	LinkStack p = S;				//临时保存元素空间地址,以备释放
	S = S->next;					//修改栈顶指针
	delete p;						//释放原本栈顶元素的空间

	return true;
}

【分析】


4. 取栈顶元素

  目的:返回当前栈顶元素的值,栈顶指针S保存不变。

ElemType GetTop(LinkStack S)
{//返回S的栈顶元素,不修改栈顶指针
	if (S != NULL)					//栈非空
		return S->data;				//返回栈顶元素的值,栈顶指针不变
}

5. 删除栈

  目的: 删除每一个元素所在空间,置空指针。

Status DestoryStack(LinkStack& S)
{//删除栈
	if (!S)					//栈空
		return false;

	while (S)				//当栈内还有元素时
	{
		LinkStack p = S;
		S = S->next;		//寻找下一个元素
		delete p;			//删除空间
	}
	S = NULL;

	return true;
}

补充结构:双栈的表示和实现

        双栈,即将两个栈存放在同一个数组空间内部,其栈底分别位于该数组的两端,如图:

  对于上述结构,我们这样约定:

  • top[0] == -1 时,认为0号栈为空;
  • top[1] == m 时,认为1号栈为空。

        双栈数据结构的定义如下:

#define SElemType int       //自定义SElemType的类型
typedef struct
{
	int top[2], bot[2];		//栈顶和栈底指针
	SElemType* V;			//栈数组
	int m;					//栈最大可容纳元素个数
}DblStack;

1. 初始化

Status InitStack(DblStack& S)
{
	using namespace std;
	S.m = MAXSSIZE;
	S.V = new SElemType[MAXSSIZE];	//为队列分配一个最大容量为MAXQSIZE的数组空间
	if (!S.V)						//如果存储分配失败
		exit(OVERFLOW);

	S.bot[0] = S.top[0] = -1;
	S.bot[1] = S.top[1] = MAXSSIZE;
	return true;
}

2. 入栈

 使用 div 区分两个栈。

Status Push(DblStack& S, SElemType e, int div)
{
	if (S.top[0] + 1 == S.top[1])
		return false;

	if (div)				//div为1
		S.top[div]--;
	else					//div为0
		S.top[div]++;
	S.V[S.top[div]] = e;	//赋值操作

	return true;
}

3. 出栈

Status Pop(DblStack& S, SElemType& e, int div)
{
	if (div)							//div是1
	{
		if (S.top[div] == MAXSSIZE)
			return false;
		e = S.V[S.top[div]];			//赋值
		S.top[div]++;					//移动指针
	}
	else								//div是0
	{
		if (S.top[div] == -1)
			return false;
		e = S.V[S.top[div]];
		S.top[div]--;
	}

	return true;
}

 4. 取栈顶元素

SElemType GetHead(DblStack& S, int div)
{
	if (!(S.top[div] == S.bot[div]))	//栈非空
		return S.V[S.top[div]];
}

5. 删除栈

Status DestoryStack(DblStack& S)
{
	delete[] S.V;
	S.bot[0] = S.top[0]
		= S.bot[1] = S.top[1]
		= S.m = 0;

	return true;
}

------

        在上述代码中,没有考虑变量div超出范围的情况,这就是代码健壮性。如果有需要,可以使用if语句补足这一点。

栈与递归

        递归作为算法设计中常见的手段,有时会因为其的缺点而不被推荐使用。但是依旧存在递归发挥优势的方面:

  • 在面对一个大型且复杂的问题时,递归的使用可以把问题的描述和求解变得简洁且清晰。
  • 当问题本身或者其所涉及的数据结构是递归定义时,递归往往更加适合;
  • 递归算法的正确性容易得到验证(由系统而不是用户管理递归工作栈)

采用递归算法解决问题

||| 若一个函数、过程或者数据结构定义的内部又直接(或间接)出现定义本身的应用,则称它们是递归的。

1. 定义是递归的

例子——数学公式及其实现

对应的函数实现:

long Fact(long n)
{
	if (n == 0)						//递归终止的条件
		return 1;
	else							//递归步骤
		return n * Fact(n - 1);
}

【分析】

  递归求解——将复杂的问题分解为几个相对简单且解法相同或类似的子问题。

        主程序调用函数Fact()的示例如下:

----------------

补充例子

对应的函数实现:

long Fib(long n)
{
	if (n == 1 || n == 2)					//递归终止的条件
		return 1;
	else
		return Fib(n - 1) + Fib(n - 2);		//递归步骤
}

        可采用上述例子中提到的 “分治法” 的条件:

  1. 一个问题能够转换成一个新问题;
  2. 转换的问题之间不同的只能是 处理的对象 ;
  3. 处理的对象变化有规律;
  4. 问题的转换能使问题简化;
  5. 必须有一个明确的 递归出口/递归边界 。

        “分治法” 求解的一般形式:

void p(参数表)
{
    if(递归结束条件成立)        //递归终止的条件
        可直接求解;
    else
        p(较小的参数);         //递归步骤
}

2. 数据结构是递归的

        某些数据结构本身具有递归的特性,因此它们的操作可以通过递归进行描述。譬如:

typedef struct LNode
{
	ElemType data;
	struct LNode* next;
}LNode, * LinkList;

        上述是一个常见的链表定义,其中 指针域next 是指向LNode类型的指针,即LNode的定义用到了自身,因此链表就是一种递归的数据结构。

例子:遍历输出链表中各个结点

void TraverseList(LinkList p)
{
	if (p == NULL)					//递归终止
		return;
	else
	{
		cout << p->data << endl;	//输出当前结点的数据域
		TraverseList(p->next);		//p指向下一个结点
	}
}

        上述算法递归结束时,只执行return操作,则算法的一般可写为:

void p(参数表)
{
    if(递归结束条件不成立)
        p(较小的参数);
}

        因此上述算法可以简化为:

void TraverseList(LinkList p)
{
	if(p)
	{
		cout << p->data << endl;
		TraverseList(p->next);
	}
}

3. 问题的解法是递归的

        有这么一类问题,它们使用递归求解会更加简单。

例子:n解Hanoi(汉诺)塔问题

【问题描述】

        假设有3个分别命名为A、B和C的塔座,在塔座A上插有n个直径大小各不相同,从小到大编号为1,2,……,n的圆盘。

        现在要求将塔座A上的n个圆盘移至塔座C上,并仍然按照同样顺序叠排,圆盘的移动必须遵守下列规则:

  1. 每次只能移动一个圆盘;
  2. 圆盘可以插在A、B和C中的任一塔座上;
  3. 任何时刻都不能将一个较大的圆盘压在较小的圆盘之上。

【要求】

        描述圆盘的移动操作(先考虑每次移动的结果,再考虑其余的细节过程。或者说,只描述移动的结果,其余交给递归实现)。

【代码】

        ① 将搬动操作定义为一个函数:

int m = 0;
void move(char A, int n, char C)
{
	using namespace std;
	cout << ++m << ". "
		<< n << "号盘: "
		<< A << "→"
		<< C << endl;
}

        ② 程序本体:

void Hanoi(int n, char A, char B, char C)
{//将塔座A上的n个圆盘按规则搬到C上,B作为辅助塔
	if (n == 1)						//将编号为1的圆盘从A移到C
		move(A, 1, C);
	else
	{
		Hanoi(n - 1, A, C, B);		//C是辅助塔,将A上编号为1到n-1的圆盘移到B
		move(A, n, C);				//将编号为n的圆盘从A移到C
		Hanoi(n - 1, B, A, C);		//A是辅助塔,将B上编号为1到n-1的圆盘移到C
	}
}

如果执行该函数,可得:

(执行结果格式有调整)

递归过程与递归工作栈

        一个递归函数,在函数执行的过程中需要进行多次自我调用。调用函数和被调用函数之间的链接及信息交换需通过来进行。

        通常,在函数调用另一个函数之前,系统需要完成3件事:

        而从被调用函数返回调用函数时,系统也要完成3件事:

        实际上,多函数嵌套调用的过程就类似于堆栈,系统会将所需的空间安排在一个栈内:

例如:

        一个递归函数的运行过程就类似于多个函数的嵌套调用,不同的是,调用函数和被调用函数是同一个函数,这时候就涉及一个重要概念——递归的“层次”

例子:递归工作栈和活动记录的使用

【代码】

void main()
{
	long n;							//调用 Fact(4) 时记录进栈
	n = Fact(4);
}

long Fact(long n)
{
	long temp;

	if (n == 0)
		return 1;					//活动记录退栈
	else
		temp = n * Fact(n - 1);		//活动记录进栈

	return temp;					//活动记录退栈
}

【分析】

        当主函数调用函数Fact()时会同时传递返回地址,设该返回地址是 RetLoc_1

        类似的,设递归函数Fact()内部执行时,传递的返回地址是 RetLoc_2

  注:此处暂时忽略局部变量temp的入栈和出栈情况。

        接下来,主函数执行,依次启动5个函数调用:

        递归的结束条件会出现在函数Fact(0)内部,执行Fact(0)会引起返回语句的执行:弹出栈顶的活动记录,返回地址返回到上一层Fact(1)的递归调用处(RetLoc2)。

递归算法的效率分析

1. 时间复杂度的分析

        在算法分析中,当一个算法包含了递归调用时,其时间复杂度的分析可以转换成一个递归方程的求解(求渐进阶)。

        常见的求解递归方程的方法是迭代法:迭代展开递归方程的右端,使之成为一个非递归的和式,通过估计这个和式来达到对方程右端(即方程的解)的估计。

以 Fact(n) 为例

   设:

  • Fact(n) 的执行时间是 T(n),则 Fact(n - 1) 的执行时间是 T(n - 1) ;
  • 两数相乘 和 赋值操作 的执行时间为 O(1) 。
long Fact(long n)
{
	long temp;

	if (n == 0)
		return 1;					//执行时间是O(1)
	else
		temp = n * Fact(n - 1);		//执行时间是O(1) + T(n - 1)

	return temp;
}

        由上述代码可得(其中C、D是常数):

        故T(n)是关于n的一次函数,可得递归方程的解:T(n) = O(n)

补充

  • Fibonacci数列递归算法的时间复杂度是O(2^n);
  • Hanoi塔递归算法的时间复杂度也是O(2^n)。

2. 空间复杂度的分析

        递归函数运行时,系统需要设立一个“递归工作栈”存储每一层递归所需信息,此工作栈是递归空间执行的辅助空间,因此,分析递归算法的空间复杂度时需要分析工作栈的大小。

        故对于递归算法,空间复杂度为:

其中,f(n)为“递归工作栈”内 工作记录的个数 和 问题规模n 的函数关系。

补充

        此前的 阶乘问题、Fibonacci数列问题 和 Hanoi塔问题 的递归算法的空间复杂度均为 O(n)。

利用栈将递归转换成非递归的方法

        对于一般的递归过程,可以仿照递归算法,利用栈来消除递归过程:

        这种写法结构的缺点:

  • 不够清晰;
  • 可读性差;
  • 需要优化。

队列的表示和操作的实现

队列的类型定义

        队列的操作与栈的操作类似,不同的是,删除是在表的头部(即队头)进行。

队列的抽象数据类型定义:

循环队列——队列的顺序表示和实现

        队列也有两种存储表示,顺序表示和链式表示。

        现在展示队列的顺序存储结构表示:

#define MAXQSIZE 100			//队列可能到达的最大长度
#define QElemType int			//设置QElemType的类型

typedef struct
{
	QElemType* base;			//存储空间的基地址
	int front;					//头指针
	int rear;					//尾指针
}SqQueue;

约定:

  1. 初始化创建空队列时,front = rear = 0
  2. 插入新的队列 尾元素 时,尾指针rear加1;
  3. 每当删除队列头元素时,头指针front加1。

        注意(4)所处的状态:

        为了解决这种“假溢出”现象,可以构建循环队列

        在循环队列下,头、尾指针的4种状态:

        根据上图中的(3)和(4)状态可知:对于循环队列,不能通过头、尾指针的值来判断队列空间是否处于“满”的状态。

        为了判断队列空间所处状态,可以使用两种不同的处理方法:

  • 方法1 —— 少使用一个元素空间

        接下来展示具体的实现操作:

1. 初始化

  操作:动态分配一个预定义大小位MAXQSIZE的数组空间。

Status InitQueue(SqQueue& Q)			//依旧暂定Status的类型是bool
{//创造一个空队列Q
	using namespace std;

	Q.base = new QElemType[MAXQSIZE];	//为队列分配一个最大容量为MAXQSIZE的数组空间
	if (!Q.base)						//如果存储分配失败
		exit(OVERFLOW);
	Q.front = Q.rear = 0;				//头、尾指针置为 0 ,队列为空

	return true;
}

2. 求队列长度(循环数列)

  注意:对于循环数列,头、尾指针之间的差值可能是负数,为了处理这种情况,一般这样计算:差值 = (差值 + MAXQSIZE) % MAXQSIZE

int QueueLength(SqQueue Q)
{//返回Q的元素个数,即队列的长度
	return(Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;
}

3. 入队(循环数列)

  操作:在队尾插入一个新元素。

Status EnQueue(SqQueue& Q, QElemType e)
{//插入元素e为Q的新的队尾元素
	if ((Q.rear + 1) % MAXQSIZE == Q.front)	//判断是否队满
		return false;

	Q.base[Q.rear] = e;						//新元素插入队尾
	Q.rear = (Q.rear + 1) % MAXQSIZE;		//队尾指针加1

	return true;
}

4. 出队(循环数列)

  操作:删除表头元素。

Status DeQueue(SqQueue& Q, QElemType& e)
{//删除Q的队头元素,用e返回其值
	if (Q.front == Q.rear)					//队空
		return false;

	e = Q.base[Q.front];					//保存队头元素	
	Q.front = (Q.front + 1) % MAXQSIZE;		//队头指针加1

	return  true;
}

5. 取队头元素

  操作:当队列非空时,返回队头元素的值,队头指针不变。

QElemType GetHead(SqQueue Q)
{//返回队头元素,不修改队头指针
	if (Q.front != Q.rear)					//队列非空
		return Q.base[Q.front];
}

6. 删除队列

  操作:释放空间,队头、队尾指针归零。

Status DestoryQueue(SqQueue& Q)
{//删除队列
	delete[] Q.base;
	Q.front = Q.rear = 0;

	return true;
}

  • 方法2 —— 设 标志位 区别队列是“空”或者“满”

        该方法和方法1的区别并不大,最大的改变就是判断队列状态的条件变为了判断标志的值。

1. 初始化

  约定tag的值:

  • true:队列未满;
  • false:队列已满。


2. 求队列长度(循环数列)

  该步与方法1无区别,可自行参考上文。


3. 入队(循环数列)

        需要注意的是,此处进行了两次判断:


4. 出队(循环数列)

  此处函数结尾没有判断,因为删除元素一定会有剩余空间存在。


5. 取队头元素

  该步与方法1无区别,可自行参考上文。


 6. 删除队列

链队——队列的链式表示和实现

        链队,即采用链式存储结构实现的队列,通常也用单链表表示。不同的是,为了操作方便,会给链队添加一个头结点。队列的链式存储结构如下:

对应的代码如下:

#define QElemType int					//定义QElemType的类型

typedef struct QNode
{
	QElemType data;
	struct QNode* next;
}QNode, *QueuePtr;
typedef struct
{
	QueuePtr front;						//队头指针
	QueuePtr rear;						//队尾指针
}LinkQueue;

        因此,链队的操作实际上就是单链表插入和删除操作的特殊情况,只需要进一步修改头、尾指针。

1. 初始化

  操作:构造一个只有头结点的空队。

Status InitQueue(LinkQueue& Q)			//Status类型: bool
{//构造一个空队列Q
	using namespace std;

	Q.front = Q.rear = new QNode;		//生成一个新节点作为 头结点 ,队头、队尾指针指向此结点
	Q.front->next = NULL;				//头结点的指针域置空

	return true;
}

2. 入队

  链队的入队操作不需要判断队列是否已满,取而代之的,需要尾入队元素分配一个结点空间。

Status EnQueue(LinkQueue& Q, QElemType e)
{//插入元素e作为Q的新的队尾元素
	QueuePtr p = new QNode;				//为入队元素分配结点
	p->data = e;						//将新节点的数据域置为e
	p->next = NULL;						
	Q.rear->next = p;					//将新解结点插入到队尾
	Q.rear = p;							//修改队尾指针

	return true;
}

【分析】


3. 出队

  注意:链队的出队操作的最后要释放出队元素所占的空间。

Status DeQueue(LinkQueue& Q, QElemType& e)
{//删除Q的队头元素,用e返回其值
	if (Q.front == Q.rear)				//如果队列为空的情况
		return false;

	QueuePtr p = Q.front->next;			//p指向队头元素
	e = p->data;
	Q.front->next = p->next;			//修改头结点的指针域
	if (Q.rear == p)					//被删除的是最后一个元素
		Q.rear = Q.front;				//在这种if情况下,让队尾指针指向头结点
	delete p;							//释放空间

	return true;
}

【分析】

注意:

        当队列当前的最后一个元素被删后,队尾指针也会丢失目标,需要重新对队尾指针进行赋值(即重新指向头节点)。


4. 取队头元素

QElemType GetHead(LinkQueue Q)
{//返回Q的队头元素,不修改队头指针
	if (Q.front != Q.rear)				//队列非空
		return Q.front->next->data;		//返回队头元素的值
}

5. 删除队列

Status DestoryQueue(LinkQueue& Q)
{
	while (Q.front != Q.rear)		//删除队尾以外的全部元素
	{
		QueuePtr p = Q.front;
		Q.front = Q.front->next;
		delete p;	
	}
	delete Q.rear;					//删除队尾
	Q.rear = Q.front = NULL;		//指针置空		

	return true;
}

 链队(补充)—— 只有尾指针

        该链队要求:

  1. 带有头结点,是循环链表;
  2. 只有一个尾指针指向队尾元素节点。 

        其的存储结构如下:

        对应该结构队列的代码:

#define QElemType int					//定义QElemType的类型

typedef struct QNode
{
	QElemType data;
	struct QNode* next;
}QNode, * QueuePtr;
typedef struct
{
	QueuePtr rear;				 //队尾指针
}LinkQueue;

        所以,该种队列的增删操作不仅需要改变尾指针,同时也要改变头结点的指针指向。

1. 初始化

  此时头结点的next指针需要指向自身。

Status InitQueue(LinkQueue& Q)
{
	Q.rear = new QNode;
	Q.rear->next = Q.rear;		//头结点指针指向自身

	return true;
}

2. 入队

Status Push(LinkQueue& Q, QElemType e)
{//增加节点
	QueuePtr p = new QNode;
	QueuePtr q = Q.rear;
	p->data = e;				//赋值
	p->next = Q.rear;			//节点与其前一个节点进行链接
	while (q->next != Q.rear)	//寻找头结点
	{
		q = q->next;
	}
	q->next = p;				//改变头结点的指针
	Q.rear = p;					//改变尾指针的指向

	return true;
}

【分析】


3. 出队

Status Pop(LinkQueue& Q, QElemType& e)
{//出队,要求返回出队元素信息,并且删除当前出队节点
	if (Q.rear == Q.rear->next)		//要求队列内存在元素
		return false;

	QueuePtr p = Q.rear;
	QueuePtr q = NULL;
	if (p->next->next == Q.rear)	//如果队列内只有一个元素
		e = p->data;				//赋值
	else							//如果队列内不止一个元素
	{
		while (p->next->next->next != Q.rear)	//寻找队头元素
		{
			p = p->next;
		}
		e = p->next->data;			//赋值
		q = p;
		p = p->next;				//p指向队头元素
		q->next = q->next->next;	//改变队头元素的下一个元素的指针指向
	}
	delete p;						//释放空间

	return true;
}

【分析】


4. 取队头元素

QElemType GetHead(LinkQueue Q)
{
	QueuePtr p = Q.rear;
	while (p->next->next != Q.rear)
	{
		p = p->next;
	}
	return p->data;
}

5. 删除队列

Status DestoryQueue(LinkQueue& Q)
{
	QueuePtr p = Q.rear;
	while (p->next != Q.rear)
	{
		p = p->next;
	}

	while (Q.rear != p)		//删除队尾以外的全部元素
	{
		QueuePtr q = Q.rear;
		Q.rear = Q.rear->next;
		delete q;
	}
	delete Q.rear;					//删除队尾
	Q.rear = p = NULL;		//指针置空		

	return true;
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值