数据结构与算法碎片积累(二)

背景:非科班梳理基础知识ing

前言:数据结构与算法碎片积累(一)有60个小题(20节课),感觉看起来太多了,所以,接下来根据课程安排,采用课程内容分类方式来总结。这样子,或许回头看时候,更人性化一些。

20201124
1、啥叫双向链表?
答:简单理解,就是一个节点,由数据域data,有前指针prior,后指针next组成。

typedef char ElemType;
typedef struct DualNode {
    
    
	ElemType data;
	struct DualNode* prior;//前驱结点
	struct DualNode* next;//后继结点
};

在这里插入图片描述
2、双向链表插入和删除操作是怎么样的?
答:
1)插入:
在这里插入图片描述
假设节点s插到节点p的前方,核心语句:

s->next=p;//s的后指针指向p
s->prior=p->prior;//s的前指针指向p的前一个结点
p->prior->next=s;//p的前一个结点的后指针指向s
p->prior=s;//p的前指针指向s

插入操作时候,一定要注意指针的指向以及先后顺序

2)删除:
在这里插入图片描述

假设节点p为待删除节点,关键语句:

   p->prior->next=p->next;
   p->next->prior=p->prior;
   free(p);//释放删除节点所占的内存

关键就是把节点p的前节点的后指针指向p的后节点;p的后节点的前指针指向p的前结点,释放节点p内容。

3、双向链表相对于单链表来说,有啥特色?
答:
1)双向链表相对单链表而言,更复杂一些,每个节点多一个prior指针,对于插入和删除操作的顺序一定要注意;
2)双向链表可以有效提升算法的时间性能,也就是利用空间换取时间。
3)简单理解,双向链表有前后指向指针,指向更加灵活。

4、双向链表的应用实例演示。
答:
–要求实现用户输入一个数使得26个字母的排序发生变化,例如用户输入3,输出结果:
DEFGHIJKLMNOPQRSTUVWXYZABC
–同时需要支持负数,例如用户输入-3,输出结果:
XYZABCDEFGHIJKLMNOPQRSTUVW

//26字母输出,练习双向链表
#include<stdio.h>
#include<stdlib.h>

#define OK 1
#define ERROR 0

typedef char ElemType;
typedef int Status;

//双向链表
typedef struct DualNode {
    
    
	ElemType data;
	struct DualNode* prior;//前驱结点
	struct DualNode* next;//后继结点
}DualNode, * DuLinkList;

//初始化一个双向链表,以及插入数据到双向链表中
Status InitList(DuLinkList* L)
{
    
    
	DualNode* p, * q;//节点指针
	int i;

	*L = (DuLinkList)malloc(sizeof(DualNode));
	if (!(*L))
	{
    
    
		return ERROR;
	}//开辟失败,退出函数

	(*L)->next = (*L)->prior = NULL;
	p = *L;

	for (i = 0; i < 26; i++)//依次将26个字母插入到链表中
	{
    
    
		q = (DualNode*)malloc(sizeof(DualNode));//开辟新结点

		if (!q)
		{
    
    
			return ERROR;//开辟失败,退出
		}

		q->data = 'A' + i;//这种方式保存26个字母到双向链表里面

		
		q->prior = p;
		q->next = p->next;//注意,最开始的p->next都是NULL的,这一步
						  //保持了链表的最后一个结点的后指针都是指向NULL
						  //所以,这一步一定要注意,顺序不能和下一步调换
		p->next = q;
		p = q;//移动p到链表的最后面

		if (i == 25)//下面注释步骤作用和这里是一样的
		{
    
    
			p->next = (*L)->next;
			(*L)->next->prior = p;
		}//这一步实现双向循环链表(注意,这个循环双向链表,不连接头结点*L的)
		
	}
	//p->next = (*L)->next;
	//(*L)->next->prior = p;//实现最后结点和字符A结点链接(注意不是头结点*L)

	return OK;
}

//这函数作用是控制表头*L指针的移动步数,从而达到输出字母的顺序要求
void Caesar(DuLinkList* L, int i)
{
    
    
	if (i > 0)
	{
    
    
		do {
    
    
			(*L) = (*L)->next;//这里决定了后移
		} while (--i);//由于i大于0,所以要减少来控制后移步数
	}

	if (i < 0)
	{
    
    
		do {
    
    
			(*L) = (*L)->prior;//这里决定了前移
		} while (++i);//由于i小于0,所以要增加来控制后移步数
	}
}


int main()
{
    
    
	DuLinkList L;
	int i,n;

	InitList(&L);

	printf("请输入一个整数:");
	scanf_s("%d", &n);//这里由于在vs上面运行的,提示了scanf不安全
					  //需要写成scanf_s类型

	Caesar(&L, n);

	for (i = 0; i < 26; i++)
	{
    
    
		L = L->next;

		printf("%c", L->data);
	}
	return 0;
}

5、栈是啥?
答:可以这么理解:
1)栈是一种线性表;
2)栈是一种先进后出(如子弹入弹夹)的数据结构;
3)栈的删除或插入操作只是在表尾(又称栈顶top)进行;
4)栈的表尾,又称栈顶(top);表头,又称为栈底(bottom)。

5)栈的插入操作(push),称为进栈,也称为压栈;删除操作(pop),叫出栈,又称为弹栈;
6)栈有两种存储方式,顺序存储结构和链式存储结构。

6、栈的常规操作,结构定义,入栈,出栈,清空栈,销毁栈是哈?
答:案例展示,将一个二进制数转为十进制数:

//利用栈的数据结构特点,将二进制转换为十进制数
#include<stdio.h>
#include<stdlib.h>
#include<math.h>

#define STACK_INIT_SIZE 20
#define STACKINCREMENT  10

//栈结构定义
typedef char ElemType;
typedef struct {
    
    
	ElemType* top;//栈顶
	ElemType* base;//栈底
	int stackSize;//栈空间大小
}sqStack;

void InitStack(sqStack* s) {
    
    
	s->base = (ElemType*)malloc(STACK_INIT_SIZE * sizeof(ElemType));//开辟空间
	if (!s->base)
		return;//开辟失败则退出函数

	s->top = s->base;//刚开始栈顶栈底指向相同
	s->stackSize = STACK_INIT_SIZE;//初始化栈空间大小
}//初始化一个栈

//入栈操作
void Push(sqStack* s, ElemType e)
{
    
    
	if (s->top - s->base >= s->stackSize)//检测栈是否已经满了
	{
    
    
		s->base = (ElemType*)realloc(s->base, (s->stackSize + STACKINCREMENT)*sizeof(ElemType));
		//复制原来的数据,并开辟到更大的内存中
		if (!s->base)
			return;//开辟失败的话,退出函数

		s->stackSize = s->stackSize + STACKINCREMENT;//栈容量不跟新,会导致程序崩溃(qt运行倒不会)
	}
	*(s->top) = e;//插入元素入栈
	s->top++;//栈顶上移
}

//出栈操作
void Pop(sqStack* s, ElemType* e)
{
    
    
	if (s->top == s->base)
		return;//判断是否出现下溢

	*e = *--(s->top);//先栈顶元素下移,然后,返回出栈元素
}

//清空栈操作
void ClearStack(sqStack* s)
{
    
    
	s->top = s->base;
	
}//将栈顶指针指向栈底,就不再读取栈数据,达到清空目的(清空不表示数据删除;销毁才是将数据彻底销毁)

//销毁一个栈
void DestoryStack(sqStack* s)
{
    
    
	int i, len;

	len = s->stackSize;

	for (i = 0; i < len; i++)
	{
    
    
		//ElemType* p;
		//p = s->base;
		//s->base++;
		//free(p);
		free(s->base);
		s->base++;
	}//这里存在一个bug,还没有解决20201124
	s->base = s->top = NULL;
	s->stackSize = 0;
}//从栈底不断上移,并释放内存

int StackLen(sqStack s)//对比上面的可以发现,对应需要修改的,传指针;
					   //不需要修改数据的,只是传值
{
    
    
	return(s.top - s.base);
}

int main()
{
    
    
	ElemType c;
	sqStack s;
	InitStack(&s);//初始化栈

	int len, i, sum = 0;

	printf("请输入二进制数,输入#符号表示结束!\n");

	scanf_s("%c", &c);

	while (c != '#')
	{
    
    
		Push(&s, c);
		scanf_s("%c", &c);//这里的c是下一个入栈的字符
		//printf("%c\n", c);
	}
	getchar();//回车键,产生的字符'\n'(==10),该函数可避免
			  //该字符一直在缓冲区(下次输入操作时候,10会造成程序混乱)
			  //其作用就是将'\n'从缓冲区去掉
	len = StackLen(s);

	printf("栈的当前容量是:%d\n", len);

	for (i = 0; i < len; i++)
	{
    
    
		Pop(&s,&c);
		sum = sum+(c - 48) * pow(2, i);//pow就是被调用的数学次方函数
	}

	printf("转换为十进制数是:%d\n", sum);
	ClearStack(&s);
	int k = StackLen(s);
	if (k == 0) {
    
    
		printf("\n清空成功!\n");
	}
	//DestoryStack(&s);//销毁栈还有bug,后面修正了再更新,销毁思路应该没问题的

	return 0;
}

7、栈的链式存储结构插入和删除的操作注意点?
答:
栈因为只是栈顶用来做插入和删除操作,所以比较好的方法就是将栈顶放在单链表的头部,栈顶指针和单链表指针合二为一。栈的链式存储结构图示可如:
在这里插入图片描述

typedef struct StackNode {
    
    
	ElemType data;//存放栈的数据
	struct StackNode* next;

}StackNode,*LinkStackPtr;//栈节点,链栈节点指针

typedef struct LinkStack {
    
    
	LinkStackPtr top;//top指针
	int count;   //栈元素计算器
};

//进栈操作
//对于栈链的Push操作,假设元素值为e的新节点是s,top为栈顶指针,那么
Status Push(LinkStack* s, ElemType e)
{
    
    
	LinkStackPtr p = (LinkStackPtr)malloc(sizeof(StackNode));//开辟栈节点
	p->data = e;//保存需要插入的元素
	p->next = s->top;//把栈顶指针保存
	s->top = p;//把栈点放到原来栈顶位置,同时栈顶指针上移
	s->count++;//栈的容量加1
	return OK;
}
//出栈操作
Status Pop(LinkStack* s, ElemType* e) {
    
    
	LinkStackPtr p;

	if (StackEmpty(*s))//StackEmpty判断是否为空栈,空的话返回1
		return ERROR;

	*e = s->top->data;//保存删除的元素
	p = s->top;//记录栈顶地址

	s->top = s->top->next;//栈顶指针下移
	free(p);//释放原来栈顶内存
	s->count--;//栈的容量减1
	return OK;
}

8、链式栈的应用,逆波兰计算器怎么实现?
答:
1)正常表达式->逆波兰表达式的感性认识:
a+b - > a b +
a+(b-c) -> a b c - +
a+(b-c)d -> a b c – d * +
a+d
(b-c) -> a d b c - * +
2)代码实现,就是通过逆波兰表达式来计算结果:

//逆波兰计算器
#include<stdio.h>
#include<ctype.h>
#include<stdlib.h>

#define STACK_INIT_SIZE 20
#define STACKINCREMENT 10
#define MAXBUFFER 10

typedef double Elemtype;
typedef struct {
    
    
	Elemtype* base;
	Elemtype* top;
	int stackSize;
}aqStack;

void InitStack(aqStack *s) {
    
    
	s->base = (Elemtype*)malloc(STACK_INIT_SIZE * sizeof(Elemtype));
	if (!s->base)
		return;

	s->top = s->base;
	s->stackSize = STACK_INIT_SIZE;
}

void Push(aqStack* s, Elemtype e)
{
    
    
	//栈满,追加空间
	if (s->top - s->base >= s->stackSize) {
    
    
		s->base = (Elemtype*)realloc(s->base, (s->stackSize + STACKINCREMENT) * sizeof(Elemtype));
		if (!s->base)
			return;

		s->top = s->base + s->stackSize;
		s->stackSize = s->stackSize + STACKINCREMENT;
	}
	*(s->top) = e;
	s->top++;
}

void Pop(aqStack* s, Elemtype* e)
{
    
    
	if (s->top == s->base)
		return;

	*e = *--(s->top);//将栈顶元素弹出并修改栈顶指针
}//出栈

int StackLen(aqStack s)
{
    
    
	return(s.top - s.base);
}

int main() {
    
    
	aqStack s;//实例化一个栈对象
	char c;//保存输入的字符
	double d, e;//用于接收出栈入栈元素
	char str[MAXBUFFER];//小数的缓冲器
	int i=0;

	InitStack(&s);//初始化栈

	printf("请按逆波兰表达式输入待计算数据,数据间、数据与运算符间,运算符间之间用空格隔开,以#号作为结束标志:\n");
	scanf_s("%c", &c);

	while (c!='#') {
    
    //这里注意的是,这里的c还是一个一个字符读取进来的
		
		while (isdigit(c)||c=='.') {
    
    //用于过滤数字
			//该while循环,在不碰到空格前,会继续遍历c下一个字符
			//isdigit()属于ctype库函数中,检测是十进制数字符(0~9)返回非零,否则返回0
			str[i++] = c;//保存加载进来的字符
			str[i] = '\0';//'\0'在字符串中表示结束意思,必须加上这个,否则调试会错误
						  //通过测试发现,如果不加这个结束符,会导致上一个字符段的保
						  //存的字符会继续存在并使用,这里原因是,i=0的作用是重新从数组的
						  //第一个开始重新赋值保存,所以每次都要添加结束符,保证不会读取到
						  //原来遗留在数组中的数据

			if (i >= 10) {
    
    
				printf("出错:输入单个数据过大!\n");
				return -1;
			}
			scanf_s("%c", &c);//驱使继续读取c下一个字符

			if (c == ' ')//如果碰到空格
			{
    
    
				d = atof(str);//atof()函数是将保存在str数组里面的字符串转换为浮点型数据
				Push(&s, d);//转化的浮点数入栈
				i = 0;//为下一次c字符组成的串转换为浮点数做准备

				printf("%f\n", d);//测试获取得到的浮点数
				break;//第一个参数入栈成功就跳出该循环,进入下一个字符c的读取
			}
		}
		switch (c) {
    
    
		case '+':
			Pop(&s,&e);
			Pop(&s, &d);
			Push(&s, d + e);//为啥这里可以使用d,e呢?很简单,因为,有了出栈带来的参数
			break;
		case'-':
			Pop(&s, &e);
			Pop(&s, &d);
			Push(&s, d - e);
			break;
		case'*':
			Pop(&s, &e);
			Pop(&s, &d);
			Push(&s, d * e);
			break;
		case'/':
			Pop(&s, &e);
			Pop(&s, &d);
			if (e != 0) {
    
    
				Push(&s, d / e);
			}
			else {
    
    
				printf("\n出错:除数为零!");
				return -1;
			}
			break;
		}
		scanf_s("%c", &c);//驱使继续读取c下一个字符,而且在上一个内在的while循环内读取过的c字符是不会再重复读取的,
						  //而是继续读取,这里感觉好像使得c在循环里面成为全局变量,遍历过的就接着下一个遍历
	}
	Pop(&s,&d);
	printf("\n最终的计算结果为:%f\n", d);

	return 0;
}

9、如何实现从中缀表达式转换为后缀表达式?
答:
1)感性认识,中缀表达式->后缀表达式
(1-2)*(4+5)->1 2 – 4 5 + *

2)逆波兰表达式(RPN),是不需要括号的后缀表达式

3)相对于中缀表达式(人比较容易理解),后缀表达式更适合计算机,因为,后缀运算时候,计算机不需不断判断。

4)中缀转后缀代码实现:

//将中缀表达式转为后缀表达式
#include<stdio.h>
#include<stdlib.h>
#include<math.h>

#define STACK_INIT_SIZE 20
#define STACKINCREMENT  10

typedef char ElemType;
typedef struct {
    
    
	ElemType* top;
	ElemType* base;
	int stackSize;
}sqStack;//栈

void InitStack(sqStack* s) {
    
    
	s->base = (ElemType*)malloc(STACK_INIT_SIZE * sizeof(ElemType));
	if (!s->base)
		return;

	s->top = s->base;
	s->stackSize = STACK_INIT_SIZE;
}//初始化一个栈

void Push(sqStack* s, ElemType e)
{
    
    
	if (s->top - s->base >= s->stackSize)//检测栈是否已经满了
	{
    
    
		s->base = (ElemType*)realloc(s->base, (s->stackSize + STACKINCREMENT) * sizeof(ElemType));
		//复制原来的数据,并开辟到更到的内存中
		if (!s->base)
			return;
		s->stackSize = s->stackSize + STACKINCREMENT;//栈容量不跟新,会导致程序崩溃(qt运行倒不会)
	}
	*(s->top) = e;
	s->top++;
}//入栈操作

void Pop(sqStack* s, ElemType* e)
{
    
    
	if (s->top == s->base)
		return;//判断是否出现下溢

	*e = *--(s->top);
}//出栈操作

int StackLen(sqStack s)//对比上面的可以发现,对应需要修改的,传指针;
					   //不需要修改数据的,只是传值
{
    
    
	return(s.top - s.base);
}

int main() {
    
    
	sqStack s;
	char c, e;

	InitStack(&s);

	printf("请输入中缀表达式,以#作为结束标志:");
	scanf_s("%c", &c);

	while (c!='#')
	{
    
    
		while(c >= '0' && c <= '9')
		{
    
    
			printf("%c", c);
			scanf_s("%c", &c);//这里使用这个,可以会出现有#情况,但是,还有
							  //没有回到最外面的while判断#,所以需要在下面的
							  //输入类型中添加#判断,如果有,那么跳出循环
			if (c < '0' || c>'9') {
    
    
				printf(" ");
			}//这样处理是为了,大于9的数值输入的,如12,显示时候,不分开
		}
		//数字判断,如果是数字就直接打印

		if(']'==c)//这里和  c==']'  意义一样的,不过,这里写少一个= ,
					   //使得变成了赋值语句,编译器是检测不出错误的;而写成
					   //变量在右侧,少写=会提醒报错的。(常量在左侧,赋值
					   //操作会报错)
		{
    
    
			Pop(&s, &e);//出栈
			while ('['!=e)
			{
    
    
				printf("%c ", e);//打印出栈元素
				Pop(&s, &e);//继续出栈出栈
			}//如果没有遇到左括号,一直打印
		}
		//当输入元素为右括号时候,需要把括号中运算符号出栈,并且把里面+、-、*、/  打印出来
		//这模块的意思,就是把括号优先级提升最高,把里面的运算符打印出来,但是括号只是出栈,不用打印

		else if ('+'==c||c=='-')
		{
    
    
			if (!StackLen(s))//如果栈为空
			{
    
    
				Push(&s, c);//入栈
			}//如果栈为空,把符号元素保存到栈中

			else {
    
    
				do {
    
    
					Pop(&s, &e);//出栈,检测栈顶元素
					if ('[' == e) {
    
    
						Push(&s, e);
					}//如果是左括号,则左括号回栈
					else
					{
    
    
						printf("%c ",e);
					}//出栈的元素不是左括号,则打印
				} while (StackLen(s)&&e!='[');//栈不为空并且出栈元素不为左括号都要进行
				Push(&s, c);
			}//栈不为空时,输入的+或者-则跟栈顶元素比较,没遇到左括号,都要出栈打印;
			 //遇到左括号,就把运算符入栈
		}
		//这模块就是判断输入+或者-时,分两种情况,首先判断栈是否为空,不为空则入栈;栈不为空,那么输入元素
		//需要和栈顶元素比较,这时候,也分两种情况,如果此时栈顶元素是左括号,那么,就把元素入栈;否则,就
		//一直打印出栈
		//这里,相当于把之前入栈的运算符+、-(不包含括号内的,上一模块已打印完毕),全部打印出栈

		else if (c == '*' || c == '/' || c == '[') {
    
    
			Push(&s, c);
		}
		//如果是*、/、[ 时候,那么就把这几个符号入栈

		else if (c == '#') {
    
    
			break;
		}//这里判断第一个while有可能出现#情况,然后跳出循环
		//当进行多数字(如,1000,就是4个字符组成的)判断时候,存在一个内循环,如果此时已经碰到了#,不加处理
		//,下面又有一个scanf_s(继续读取下一个),那么就会使得读取到一些未知区域,会导致出错。
	

		else
		{
    
    
			printf("\n输入错误!\n");
			return -1;
		}
		//如果输入的是其他信息,那么就提示输入错误

		scanf_s("%c", &c);//继续读取下一个字符
	}//当未读取到#符号时候,就一直循环判断,看到底是打印还是入栈,还是出栈

	while (StackLen(s))
	{
    
    
		Pop(&s, &e);
		printf("%c ", e);
	}//当上面的大循环结束了,栈中还保留运算符的话,那么依次从栈顶出栈并打印
	return 0;
}
//注意,原来采用的是()括号的,后面发现,调试窗口自动为中文输入法,导致
//容易即使输入方式为中文输入也看不出来,所以,采用[]代替()
//虽然,通过分步调试理清楚了每一步的作用,但是,内在的算法思路还是没有很清晰的层次样式

10、队列有啥特性?
答:
1)队列,只能一端插入,另一端删除的线性表;
2)队列,是一种先进先出的线性表(注意与先进后出的栈区分)

3)队列,可用顺序表实现,也可以用链表实现;但,一般情况下,使用链表来实现,所以,又可简称为链队列。
4)队列的结构特性:
在这里插入图片描述
空队列时,front和rear都指向头结点
在这里插入图片描述
11、队列全家桶(结构、入队、出队、销毁操作)是啥?
答:案例展示:

#include <malloc.h>
#include<stdio.h>
#include <stdlib.h>

//链队列的结构代码
#define ElemType char
typedef struct QNode {
    
    
	ElemType data;
	struct QNode* next;
}QNode,*QueuePrt;//节点结构

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

//创建或者初始化一个队列
void initQueue(LinkQueue* q) {
    
    
	q->front = q->rear = (QueuePrt)malloc(sizeof(QNode));
	if (!q->front)
		exit(0);//这是一个函数,而return是一个关键字
	q->front->next = NULL;
}

//入队操作
void InsertQueue(LinkQueue* q, ElemType e) {
    
    
	QueuePrt p;
	p = (QueuePrt)malloc(sizeof(QNode));
	if (p == NULL)
		exit(0);
	p->data=e;
	p->next = NULL;
	q->rear->next = p;//q的队尾由原来指向null,重新指向新加入的p
	q->rear = p;//将队列q的队尾指针重新指向最后一个节点
}

//出队操作
void DeleteQueue(LinkQueue* q, ElemType* e) {
    
    
	QueuePrt p;

	if (q->front == q->rear)//空队列情况
		return;
	
	//非空队列情况
	p = q->front->next;
	*e = p->data;
	q->front->next = p->next;

	if (q->rear == p)//队列只一个元素,而元素被出队了;在释放该空间前
					 //将该尾指针移动回来,和头指针指向同一节点头结点
		q->rear = q->front;
	free(p);//释放删除节点所占内存
}

//销毁队列
int DestoryQueue(LinkQueue* q) {
    
    
	if (1) {
    
    
		while (q->front) {
    
    
			q->rear = q->front->next;
			free(q->front);
			q->front = q->rear;
		}
		return 1;
	}
}

//创建一个链表并初始化,入队操作,出队操作并销毁
int main() {
    
    
	LinkQueue s;

	initQueue(&s);

	char e,t;
	int i = 0;
	printf("请输入字符并以#结尾:\n");
	scanf_s("%c", &e);

	while (e != '#') {
    
    
		InsertQueue(&s, e);
		i++;
		scanf_s("%c", &e);
	}

	while (i--)
	{
    
    
		DeleteQueue(&s, &t);
		printf("%c ", t);
	}

	int a=DestoryQueue(&s);
	if (a == 1) {
    
    
		printf("\n销毁完毕\n");
	}

	system("pause");
	return 0;
}

20201210
12、队列的顺序存储结构之循环队列
答:
1)队列只是采用顺序存储结构时,会导致出队时候,时间复杂度达到O(n)【队头不动情况】,或者假溢出的情况【队头移动情况】出现,所以为了解决这个问题,提出一种循环队列的顺序存储队列。
2)循环队列定义:
队列容量是固定的,并且队头和队尾指针都是可以随着元素入队而发生改变,逻辑上,循环队列如同一个环形存储空间(只是用顺序表模拟出的逻辑上的循环,实际上是不存在的)

3)但是,为避免循环时,队头队尾指针重新相遇情况,我们在入队或者出队时候,对其进行取模运算:
(rear+1)%QueueSize
(front+1)%QueueSize

因此,队列的顺序存储结构存在各种麻烦,一般情况下,队列采用的是链式存储结构的。

但是,循环队列基本操作还是要会的:

//定义一个循环队列
#define MAXSIZE 100
#define ElemType char
typedef struct {
    
    
	ElemType* base;//用于存放内存分配基地址(我理解就是,指向一个循环顺序队列,然后,
	               //又可以指向里面的任意位置,根据[]来访问具体位置),也可以用数组存放基地址
	int front;
	int rear;
}cycleQueue;

//初始化一个循环队列
void initQueue(cycleQueue* q) {
    
    
	q->base = (ElemType*)malloc(MAXSIZE * sizeof(ElemType));//开辟的是一个循环顺序队列整体内容空间,并q->base指向它
	if (!q->base)
		exit(0);
	q->front = q->rear = 0;//队头队尾指向初始化都在0号位置
}

//入队操作(队尾插入)
void InsertQueue(cycleQueue* q, ElemType e) {
    
    
	if ((q->rear + 1) % MAXSIZE == q->front)//这里判断元素是否已经满了
		return;//队列已满
	q->base[q->rear] = e;//数组和指针相互引用;把插入的元素放到队列相应的位置,根据[]来确定
	q->rear = (q->rear + 1) % MAXSIZE;//指向下一个顺序表元素编号
}

//出队操作(队头出队)
void DeleteQueue(cycleQueue* q, ElemType* e) {
    
    
	if (q->front == q->rear)
		return;//队列为空
	*e = q->base[q->front];//返回出队元素
	q->front = (q->front + 1) % MAXSIZE;//队头指向下一个顺序表元素编号
}

#########################
不积硅步,无以至千里
好记性不如烂笔头
盗图归授课老师所有,致谢

猜你喜欢

转载自blog.csdn.net/qq_45701501/article/details/110074733