数据结构——第二章:线性结构(数组、链表、栈、队列)

92 阅读19分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

线性结构

数据结构 = 个体的存储 + 个体的关系存储 算法 = 对存储数据的操作(增加、删除、遍历等等)

别人问:什么是线性结构? 把所有的结点用一根直线穿起来

在这里插入图片描述

1、连续存储数组

int a[10];
int *pArr = (int *)malloc(len);

自己定义一个数据类型,并且为它添加方法。

#include <stdio.h>
#include <malloc.h>
//定义一个数据类型,里面又三个变量
struct Arr
{
	int *pBase; // 存放数组第一个元素的地址
	int len; // 数组所能容纳的最大个数
	int cnt;// 当前数组的元素个数
}
// 定义一些算法
void init_arr(); // 初始化整个数组
bool append_arr(); //在数组最后,追加一个元素
bool insert_arr(); // 在数组当中插入一个元素
bool delete_arr(); // 删除一个数组元素
bool get_arr();
bool is_empty(); // 判断是否为空
boll is_full();	 // 判断是否为满

void sort_arr(); // 排序整个数组
void show_arr(); // 输出数组
void inversion_arr(); //导致整个数组


int main(void)
{
	struct Arr arr;
	init_arr(&arr,6);
	shouw_arr(&arr);
	return 0;
}

void init_arr(struct Arry *pArr , int length)
{
pArr -> pBase = (int *) malloc(sizeof(struct Arry) * length);
if(NULL == pArr->pBase)
	{
		printf("malloc error");
		exit(-1); // 终止整个程序
	}
else
	{
		pArr -> len = length;
		pArr -> cnt = 0;
	}
	return;
}


(1)init_arr

我们希望,一调用 init_arr 函数, arr 当中的 pBase 指向一块连续内存。len 表示这个内存的大小,cnt 有效元素的个数。 在这里插入图片描述

分析:
1、需不需要返回值
2、需不需要参数:肯定需要,没有的话,不知道操作谁
	(1)输入型参数?
	(2)输出型参数?

struct Arr arr;
init_arr(arr);
void init_arr(strcut Arr array) // 传值方式,根本不会改变实参的内容 
{
	array.len = 99; 
}



struct Arr arr; 
init_arr(&arr);  // 传入地址
void init_arr(strcut Arr *parr) // 传址方式,可以改变实参内容
{
	(*parr).len = 99; 
}

void init_arr(struct Arry *pArr , int length)
{
pArr -> pBase = (int *) malloc(sizeof(struct Arry) * length);
if(NULL == pArr->pBase)
	{
		printf("malloc error");
		exit(-1); // 终止整个程序
	}
else
	{
		pArr -> len = length;
		pArr -> cnt = 0;
	}
	return;
}

(2)show_arr

输出这个数组 传入指针:只传递 4 个字节的内容

void show_arr(struct Arry *pArr) // 输出数组,
{
	if(数组为空)
		提示用户数组为空
	else
		输出数组有效内容
}

bool is_empty(struct Arry *pArr)
{
	if(0 == pArr -> cnt)
		return ture;
	else
		return flase;
}

要注意:pArr 是一个结构体变量的地址
void show_arr(struct Arry *pArr) // 输出数组,
{
	if(is_empty(pArr))
		printf("数组为空!\n");
	else
		for(int i = 0; i < pArr->cnt , i++)
		{
			printf("%d\n",pArr->pBase[i]);
		}
}

(3)append_arr

bool append_arr(struct Arry *pArr, int val)
{
	if(数组满了)
		提示用户数组满了
	else
		追加
}

bool is_full(struct Arry *pArr)
{
	if(pArr->cnt == pArr->len)
		return ture;
	else
		return flase;
}

bool append_arr(struct Arry *pArr, int val)
{
	if(is_full(pArr))
		{
			printf("数组已满\n");
			return flase;
		}
	else
		pArr->pBase[pArr->cnt] = val;
		(pArr->cnt)++;
		return ture;
}

(4)insert_arr

思考函数要实现的功能: 向指定位置插入一个数字。 所以需要传入的参数: 1、结构体(数组、数组大小、当前元素个数) 2、准备插入的位置 3、要插入的值

在这里插入图片描述

// pos从1开始,假设pos = 3, val = 55; 则 a[2] = 55;
bool insert_arr(struct Arry *pArr, int pos,int val)
{
	int i = 0;

	if(is_full(pArr))  // 如果满了
		return flase;
	if(pos<1 || pos > pArr->cnt-1) //如果元素不够:一共有 3 个元素,就不可以在第 5 个位置插入
		return flase;

// 先进行后移
	for(i = cnt-1; i>=pos-1; i--)  // 这个循环,一步一步去试数字然后往出写
	{
		pArr->pBase[i+1] = pArr->pBase[i]
	}
// 再进行插入
	pArr->pBase[pos-1] = val;
	(pArr->cnt)++;
}

(5)delete_arr

思考函数要实现的功能: 向指定位置删除一个函数、并且返回成功、还是返回失败?并且想返回被删除的值? 所以需要传入的参数: 1、结构体(数组、数组大小、当前元素个数) 2、准备删除的位置 3、输出型参数 int *pVal (从而实现多个返回值)

主函数调用:
int val; // 定义一个变量来接收删除的数字
struct Arr arr;

delete(&arr,1,&val);

bool delete(struct Arry *pArr, int pos, int *pVal)
{
	int i = 0;
	if( is_empty(pArr) )
		return flase;
	if(pos<1 || pos > pArr->cnt) // 要删除,必须有这个值
		return flase;
	
	// 先拿出
	*pVal = pArr->pBase[pos-1];
	// 再移动
	for(i = pos; i<cnt ; i++)
	{
		pArr->pBase[i-1] = pArr->pBase[i];
		(pArr->cnt)--;
	}
	
	return ture;
}

(6)inversion_arr

函数功能:倒置 最后一个换到第一个

void inversion_arr(struct Arry *pArr)
{
	int i = 0; //第一个元素
	int j = (pArr->cnt)- 1// 最后一个元素
	int tmp = 0;
while(i<j)
	{
		tmp = pArr->pBase[i];
		pArr->pBase[i] = pArr->pBase[j];
		pArr->pBase[j] = tmp;
	} 
	return 0;
}

(7)sort_arr

void sort_arr(struct Arry *pArr)
{
	int i, j = 0;
	int tmp = 0;
	
	for(i=0; j < pArr->cnt-1; ++i) // cnt-1 说明最后一个不用比较
	{
		for(j = i+1 ; j <pArr->cnt; ++j)
			{
				if(pArr->pBase[i] > pArr->pBase[j]) // 把小的换到前面来
					{
						tmp = pArr->pBase[i];
						pArr->pBase[i] = pArr->pBase[j];
						pArr->pBase[j] = tmp;
					} 
			}

	}

}

2、离散存储链表

链表的重要性:是我们学习数据结构的基础, 如下面的树(一个结点指向下面多个结点在这里插入图片描述

图(任何一个结点可以保存其他结点的地址在这里插入图片描述

(1)专业术语

定义

(1)n个节点离散分配 (2)彼此通过指针相连接,上一结点保存了下一个结点的地址
(3)每个节点只有一个前驱节点,一个后续节点。首节点没有前驱节点,尾结点没有后续节点。 (树下面可以有很多节点) 在这里插入图片描述 在这里插入图片描述

专业术语:

首节点:第一个有效结点 尾结点:最后一个有效结点

头结点: (1)第一个有效结点,之前的结点 (2)头结点,不存放有效数据 (3)加头结点的目的是可以方便我们对链表的操作 (4)头结点与其他结点,数据类型完全一样

头指针:第一个节点的地址 指向头结点的指针变量(可能不对,是指向第一个有效结点的指针)

尾指针:最后一个节点的地址 指向尾结点的指针变量。

如果我们希望,通过函数来对链表进行处理,我们至少需要接受链表的几个参数

数组:(3个参数)首地址、长度、当前元素个数。

链表:(1个参数)首地址(头指针) 注: (1)头节点的数据结构,和其他的节点一样。 (2)所以我们知道 头指针,可以一个一个推算出下面节点的信息。

(2)节点的数据类型,该如何表示

每一个节点,都是一个数据类型。 每一个节点当中,应该包含什么?应该有几个成员?

  • 有效数据
  • 指针(地址):指向下一个节点。

问题: (1)通过指针,我们要找到后面一个节点,而不是仅仅是首地址。

int *p = (int *)0x10002000;
*p = 2; // 这样的解引用,是将整数 2 ,存放到了 0x10002000 地址开头的四个字节当中。

char *p = (char *)0x10002000;
*p = 2; // 这样的解引用,是将整数 2 ,存放到了 0x10002000 地址开头的 1 个字节当中。

我们每个节点的数据类型一样。 所以我们指针,相当于指向了和自己数据类型一样的变量。

struct Node
{
	int data; // 数据域(本身可以特别复杂)

	struct Node *pNext; // 指针域
}

typedef struct Node
{
	int data; // 数据域(本身可以特别复杂)

	struct Node *pNext; // 指针域
}NODE, *PNODE;

(3)链表的分类

  • 单链表 在这里插入图片描述

  • 双链表(每一个节点有两个指针域

在这里插入图片描述

  • 循环链表 能通过一个节点,找到任何一个节点 在这里插入图片描述

  • 非循环链表

(4)链表的伪算法

遍历(找到节点,之后就可以,增加、删除、改变、等等) 查找 清空 销毁 求长度 排序 删除结点 插入节点

插入非循环节点的伪算法

把 q 指向的节点,插入 p 指向节点的后面 在这里插入图片描述

注意:指针和结构体的知识: (1)指针 p :本身并没有指针域, p 指向的结构体才有指针域

p -> pNext ; // p 指向结构体,这样调用该结构体当中的成员

(2)实现的效果:p中的指针域,指向q。 q当中指针域指向下一个节点

p -> pNext = q; // p指向的结构体当中的指针域,指向q
q -> pNext =    // 发现已经丢失了一下个节点的地址
r = p -> pNext; // 先将下一个地址,存起来
p -> pNext = q;
q -> pNext = r; 
q -> pNext = p -> pNext; // 先将 q 的指针域,指向下一个节点
p -> pNext = q;

删除肺循环节点的伪算法

在这里插入图片描述

问题:导致内存泄漏(找不到第2个节点的地址,所以无法释放)

p -> pNext = p -> pNext -> pNext ;
free(p -> pNext) ; //错误:p -> pNext 已经发生改变

解决:

r = p -> pNext; // 先将第二个节点地址保存
p -> pNext = p -> pNext -> pNext ;
free(r); // 然后再释放

(5)基本算法实现

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

typedef struct Node
{
	int data; // 数据域

	struct Node *pNode; // 指针域
}NODE, *PNODE;

int main(void)
{
	//跨函数使用内存(动态内存)
	PNODE pHead = NULL;
	pHead = create_list(); // 创建一个非循环单链表,并将该链表的头节点地址赋值给 pHead 



	return 0;
}

(1)create_list

1、返回值:(是一个地址,头节点地址) PNODE
2、参数:不需要

流程:

1、首先生成一个头节点 2、由用户决定生成的节点个数 3、创建这些节点

不循环生成节点:非常麻烦,想要申请100个,就得写100次。

//获取用户信息
printf("请输入第 1 个节点的值: val= ");
scanf("%d",&val);
//生成第一个节点的地址为 pNew 
PNODE pNew1 = (PNODE)malloc(sizeof(NODE));
//将用户信息存放
pNew1 -> data = val;
//将这个节点的首地址,挂到上一个节点指针域
pHead -> pNext = pNew1;
// 每次创建,将尾结点的指针域指向 NUll
pNew1 -> pNext = NULL;

// 第二个节点
//获取用户信息
printf("请输入第 2 个节点的值: val= ");
scanf("%d",&val);
//生成第二个节点的地址为 pNew2 
PNODE pNew2 = (PNODE)malloc(sizeof(NODE));
//将用户信息存放
pNew2 -> data = val;
//将这个节点的首地址,挂到上一个节点指针域
pNew1 -> pNext = pNew2 ;
// 每次创建,将尾结点的指针域指向 NUll
pNew2 -> pNext = NULL;

循环生成节点:

for (i = 0; i < len; i++)
{
		//获取用户信息
printf("请输入第 1 个节点的值: val= ");
scanf("%d",&val);
//生成第一个节点的地址为 pNew 
PNODE pNew = (PNODE)malloc(sizeof(NODE));
//将用户信息存放
pNew -> data = val;
//将这个节点的首地址,挂到上一个节点指针域
pHead -> pNext = pNew;
// 每次创建,将尾结点的指针域指向 NUll
pNew -> pNext = NULL;
}

出现的问题:只有第一次可以创建成功,后面都失败 在这里插入图片描述 解决办法:自己构造一个,多余的指针,让这个指针始终指向尾结点

for (i = 0; i < len; i++)
{
		//获取用户信息
printf("请输入第 1 个节点的值: val= ");
scanf("%d",&val);
//生成第一个节点的地址为 pNew 
PNODE pNew = (PNODE)malloc(sizeof(NODE));
//将用户信息存放
pNew -> data = val;
//将这个节点的首地址,挂到上一个节点指针域
pTail -> pNext = pNew;
// 每次创建,将尾结点的指针域指向 NUll
pNew -> pNext = NULL;
// 将pTail 后移
pTail = pNew;
}

整体的创建实现

PNODE create_list()
{
	int len = 0;
	int i = 0;
	int val = 0;
	
	//生成一个头节点
	PNODE pHead = (PNODE) malloc(sizeof(NODE));
	if(NULL = pHead)
	{
	printf("分配失败\n");
	exit(-1);
	}
	PNODE pTail = pHead ;
	pHead -> pNext = NULL; // 尾结点的指针域永远为NULL
 	printf("请输入您需要生成的链表节点个数:len = ");
	scanf("%d",len);
	
	//数组的内存是连续的,所以可以直接 malloc
	//链表的内存是离散的,所以需要一个循环
	for (i = 0; i < len; i++)
	{
	//获取用户信息
	printf("请输入第 1 个节点的值: val= ");
	scanf("%d",&val);
	//生成第一个节点的地址为 pNew 
	PNODE pNew = (PNODE)malloc(sizeof(NODE));
	//将用户信息存放
	pNew -> data = val;
	//将这个节点的首地址,挂到上一个节点指针域
	pTail -> pNext = pNew;
	// 每次创建,将尾结点的指针域指向 NUll
	pNew -> pNext = NULL;
	// 将pTail 后移
	pTail = pNew;
	}

	return pHead; // 返回头指针
}

(2)traverse_list

1、返回值:不需要 2、参数:必须要,指定对哪一个链表进行遍历

void traverse_list(PNODE pHead)
{
	PNODE p = pHead -> pNext;
	
	while(NUll != p) //如果 p 不为空(下一个节点存在)
	{
	printf("%d\n", p-> data);
	// p++ ,移动到下一个节点
	p = p->pNext;
	}
	printf("\n");
}

(3)is_empty

参数: PNODE pHead 判断是哪一个链表 返回值:bool,判断是否成功

bool is_empty(PNODE pHead)
{
	if(NULL == pHead->pNext) //头节点的指针域为空
		return true;
	else
		return false;
}

(4)length_list

参数: PNODE pHead 判断是哪一个链表 返回值:int 返回长度

思路:遍历的时候计数即可

int length_list(PNODE pHead)
{
	PNODE p = pHead -> pNext;
	int cnt = 0;
	while(NUll != p) //如果 p 不为空(下一个节点存在)
	{
	cnt++; // 计数
	p = p->pNext;
	}
	return cnt;
}

(5)insert_list

参数:PNODE pHead 判断是哪一个链表 int pos:插入的位置 int val:插入的值 (输入型参数) 返回值:是否成功

bool insert_list(PNODE pHead,int pos,int val)
{
//  pos 的值必须合法,一共有 5 个节点,那么插入第 10 个就错误
int i = 0;
PNODE p = pHead;

//移动节点位置
// 当前节点不是尾结点 NULL!=p :说明如果移动到尾节点就停止移动

// 并且移动到第 pos-1 个节点之前
// 代数:pos=1时,向第一个节点之前插入,当前p指向头节点,不需要执行循环
// pos=2时,向第二个节点之前插入,所以需要执行一次。
while(NULL!=p && i<pos-1)
{
	i++;
	p = p->pNext;
}

// 如果pos的值不合法的时候
// 或者移动到尾结点之后,我们在此处进行返回
if(i>pos-1 || NULL==p)
	return false;
// 分配动态内存
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if(NULL == pNew)
{
printf("insert 动态内存分配错误\n");
exit(-1);
}
// 保存用户数据
pNew -> data = val;
// 插入节点
	// 保存后面节点的地址
PNODE q = p->pNext;
	// 然后将前面的节点指向新申请的节点
p->pNext = pNew;
	// 将插入的节点指向后面的节点
pNew->pNext = q;
	return true;
}

(6)delete_list

参数:PNODE pHead 判断是哪一个链表 int pos:删除的位置 int *val:插入的值 (输出型参数)

bool delete_list(PNODE pHead,int pos,int val)
{
int i = 0;
PNODE p = pHead;
//移动节点位置
// 当前节点不是尾结点 NULL!=p :说明如果移动到尾节点就停止移动

// 并且移动到第 pos-1 个节点之前
// 代数:pos=1时,向第一个节点之前插入,当前p指向头节点,不需要执行循环
// pos=2时,向第二个节点之前插入,所以需要执行一次。
while(NULL!=p->Next && i<pos-1)
{
	i++;
	p = p->pNext;
}

// 如果pos的值不合法的时候
// 或者移动到尾结点之后,我们在此处进行返回
if(i>pos-1 || NULL==p->Next)
	return false;

//删除节点
	//保留当前节点地址
PNODE q = p->pNext;
	//输出删除节点的内容
*val = q->data;
	//更换指针域
p->pNext = p->pNext->pNext;
	//释放内存
free(q);
q = NULL;
	return true;
}

(7)sort_list

参数:PNODE pHead 判断是哪一个链表 返回值: 无

数组和链表的探讨

不同点:一个是连续的,另一个是离散的。 相同点:都是线性结构,算法严格来说是一样的(逻辑上) 比如:都可以使用冒泡来进行排序。(第一个依次和后面比较)

在这里插入图片描述

void sort_list(PNODE pHead)
{
	int i, j = 0;
	int tmp = 0;
	int len = length_list(pHead);
	PNODE p,q; // p相当于i, q相当于j
	
	for(i=0,p=pHead->pNext; i < len-1; ++i, p=p->pNext) 
	{
		for(j=i+1, q = p->pNext ; j <len ; ++j , q= q->pNext)
			{
				if(p->data > q->data ) // 把小的换到前面来
					{
						tmp = p->data;
						p->data = q->data;
						q->data = tmp;
					} 
			}
}

补充泛型的初步定义:

算法:(数组和链表的算法一样吗?)

狭义的算法:与数据的存储方式,密切相关 广义的算法:与数据的存储方式,不相关

泛型: 利用某种技术:达到不同的存储方式,执行的操作是一样的。

举例:运算符的重载

p++:我们可以将 ++ 运算符进行重载。(为 ++ 重写一个函数) 所以广义上来说就实现了泛型

3、线性结构的应用 —— 栈

(1)序言

静态内存:分配在上 局部变量 栈序:先进后出

动态内存:分配在malloc 函数 堆序:堆排序

栈的定义:事先 “先进后出” 的数据结构。(类似于一个杯子)

栈的分类:

静态栈:是用数组来实现 注意:静态栈必须提前确定栈的大小(有限的),并且都是连续的.

动态栈:是用链表来实现. 动态栈可以无限大小(内存够的情况下),并且是不连续的.

分析栈和链表的区别: 栈只能在栈顶进入,或者是栈顶删除。

栈的算法:

出栈: 入栈:

(2)栈的算法实现

在这里插入图片描述 pBottom:指向头节点(里面并不存放有效数据),栈底元素的下一个 pTop :指向尾结点

删除元素:pTop 向上移动 插入元素:pTop 向下移动

判断空栈:pTop == pBottom

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

// 定义每个节点的数据类型
typedef struct Node
{
	int data; // 数据域
	struct Node *pNext; // 指针域
}NODE, *PNODE;

typedef struct Stack
{
	PNODE pTop;
	PNODE pBottom;
}STACK, *STACK;

int main(void)
{
	STACK S; // 等价于 struct Stack  S
// 初始化栈指针
	initStack(&S); // 一定要思考传入什么参数
// 压栈
	push(&S,1);
	push(&S,2);
// 遍历输出
	traverse(&S);
}

(1)init

// 数入之前:栈指针都是垃圾值
// 1、如果我们的 栈顶指针和栈底指针 都指向一个头节点(无用),才能说明我们构造了一个空栈
// 
void init(PSTACK PS)
{
	PS -> pTop = (PNODE)malloc(sizeof(NODE));
	if(NULL == PS -> pTop)
	{
		printf("init 动态分配出错\n");
		exit(-1);
	}
	else
	{
		PS -> pBottom = PS -> pTop;
		// 将头节点的指针域清空
		PS -> pTop ->pNext = NULL;
	}
}

(2)push

在这里插入图片描述

// 1、新申请一个节点
// 2、入栈的指针域,指向 上一个节点
// 3、栈顶指针,指向新malloc 的节点
// 4、
void push(PSTACK PS, int val)
{
	PNODE pNew = (PNODE) malloc(sizeof(NODE));
	pNew -> pdata = val;
	// 新申请的节点的指针域指向上一个节点,也就是 pTop 指向的节点
	pNew -> pNext = PS->pTop; 
	// 栈顶指针,指向新malloc 的节点
	PS->pTop = pNew;
}

(3)traverse

在这里插入图片描述

// 1、先输出 88 

// 思考:
// 1、我们不能改变栈顶指针,和栈底指针
// 2、所以我们定义一个临时的指针p ,指向栈顶元素
// 3、p 指针一个一个向下移动
// 4、p == pBottom 的时候,遍历完成
void traverse(PSTACK PS)
{
	PSTACK p = PS->pTop;
	
	while(p != PS->pBottom)
	{
		printf("%d ",p->data);
		p = p->pNext;
	}
return ;
}

(4)pop

// 1、保存栈顶节点的地址,然后释放
// 2、pTop 向下移动
// 3、将下一个节点的指针域,指向NULL


bool pop(PSTACK PS,int *val)
{
//栈为空
	if(PS -> pTop == PS -> pBottom)
	{
		return false;
	}
// 1、保存栈顶节点的地址,然后释放
	PNODE p =  PS ->pTop;
	*val = p -> data;
	free(p);
// 2、pTop 向下移动
	PS ->pTop = PS ->pTop -> pNext;
// 将下一个节点的指针域,指向NULL
	PS ->pTop -> pNext = NULL;
}

(5)clear

// 将栈中元素清零,然后节点还在
// 遍历一次,然后清除元素
void clear(PSTACK PS)
{
	if (empty(PS)) // 如果为空栈
{
	return ;
}
	PSTACK p = PS->pTop;
	
	while(p != PS->pBottom)
	{
		p->data = 0;
		p = p->pNext;
	}
return ;
}

// 将栈当中的节点释放
// 遍历一次元素,将其释放
// 但是栈顶指针,和栈底指针都必须留下,留下框架
void clear(PSTACK PS)
{
	if (empty(PS)) // 如果为空栈
	{
		return ;
	}
	else
	{
		PNODE p = PS->pTop;
		PNODE q = NULL;
		while(p !=  PS->pBottom) 
		{
			q = p->pNext;
			free(p);
			p=q;
		}
	}
	return ;
}

在这里插入图片描述

(3)栈的应用

1、函数的调用

int f ()
{
	int a,b = 0;
	g(&a;&b);
	
	printf("hello world\n");
}

分析:在 f() 函数当中调用g() 函数的时候,参与栈的使用。

将指令 printf("hello world\n"); 的地址压入栈 将参数所用的局部变量 int a,b = 0; 也压栈

2、中断

3、表达式求值 在这里插入图片描述 4、内存分配 5、缓冲处理 6、迷宫

4、线性结构的应用—— 队列

(1)序言

栈:我们讲的是动态队列,(本质还是链表)

什么是队列? 一种可以实现 “先进先出” 的存储结构。

链表的分类?

链式队列:本质是链表 静态队列:本质是数组

(2)静态队列的分析:

1、静态队列为什么必须是循环队列? 2、循环队列,需要几个参数来确定? 3、循环队列,各个参数的含义? 4、循环队列,入队伪算法讲解 5、循环队列,出队伪算法讲解 6、如何判断循环队列为空? 7、如何判断循环队列为满?

1、静态队列为什么必须是循环队列? 在这里插入图片描述 分析:

rear:用来添加元素的指针(向上移动) front:用来删除元素的指针(向上移动)

发现一个问题:指针都是向上移动,内存总有一天会崩溃。(而且使用数组的时候,数组的大小是固定的)

解决办法:循环队列

2、循环队列,需要几个参数来确定?

  • 需要两个参数:front 指针和 rear 指针。

3、循环队列,各个参数的含义?

  • 2个参数不同的场合有着不同的含义 在这里插入图片描述

(1)队列初始化:front 和 rear 的值都是

(2)队列空时:front 和 rear 的值相等,但不一定是

(3)队列为空:front代表第一个元素,rear代表最后一个元素的下一个元素。

4、循环队列,入队伪算法讲解

入队:在队尾加入 出队:在队头弹出

在这里插入图片描述

5、循环队列,出队伪算法讲解

在这里插入图片描述 6、如何判断循环队列为空?

如果,front 和 rear值相等,则该队列就一定为空。

7、如何判断循环队列已满? 在这里插入图片描述 1、可以发现,f 和 r 的值可以是任意值

2、可以看出当我们队列已经满了的时候,指针 p 和 指针 r 是相等的,所以和我们的,队列已空发生了冲突。

解决办法:

1、少使用一个元素 则:当 指针p指针r 互相临近,则队列已经满了。 因为:f 和 r 的值可以是任意值,所以当 r = 4 的时候,f 可以变为 f = 3,f = 5。(其中有一种情况是队列只有一个元素) 但是,入队的时候是,r + 1,所以说当 (r+1) % 数组的长度 == f 的时候,队列就是满的,另外一种情况排除。

2、添加一个元素 原理和少用一个元素是相同的

在这里插入图片描述

(3)循环队列程序演示

#include <stdio.h>

typedef struct queue
{
	int *pBase; // 数组的基地址
	int front; // 作为数组元素的下标
	int rear;  

}QUEUE,*QUEUE;


int main(void)
{
	QUEUE Q;
	
	init(&Q);
	
	return 0;
}

(1)init

//目的:
//1、创建一个数组
//2、下标进行初始化

void init(QUEUE *pQ)
{	
	// 6 个元素大小
	pQ -> pBase = (int*)malloc(sizeof(int)*6 );
	pQ -> front = 0;
	pQ -> rear = 0;
}

(2)en_queue

注意:判断是否已满就为少用一个元素,创造了条件


// 入队:如果队列不满
// 1、将值放在 r 当前的位置
// 2、将 r 移动到下一个位置

bool en_queue(QUEUE *pQ , int val)
{
	if( full_queue(pQ) )
	{
		return false;
	}
	else 
	{
		// 1、将值放在 r 当前的位置
		pQ->pBase[pQ->rear] = val;
		// 2、将 r 移动到下一个位置
		pQ->rear = (pQ->rear+1) % 6;
	}
}

(3)full_queue

bool full_queue(QUEUE *pQ)
{
	// 回顾伪算法
	if( (pQ->rear + 1)%6 == pQ->front )
		return true;
	else
		return false;
}

(4)traverse_queue


void traverse_queue(QUEUE *pQ)
{
	int i = pQ -> front;

// i != pQ->rear  时候,有东西要输出
	while(i != pQ->rear)
	{
		printf("%d ",pQ->pBase[i]);
		i = (i+1) % 6; // 开始循环
	}
}

(5)out_queue

// 出队:删除一个元素
// 1、先将值进行输出
// 2、指针 f 向上移动
bool out_queue(QUEUE *pQ , int *val)
{
	if( empty_queue() )
		return false;
	else
	{
		// 1、先将值进行输出
		*pVal = pQ->pBase[pQ->front];
		// 2、指针 f 向上移动
		pQ->front = (pQ->front + 1) %6;
	}	
}

(6)empty_queue

bool empty_queue( QUEUE *pQ )
{
	if(pQ->rear == pQ->front )
		return true;
	else
		return false;
}

(4)队列的具体的应用

1、所以和时间有关的操作,都与队列相关。

比如:任务,先进先执行