【数据结构笔记】数据结构基础—队列

1.顺序队列的原理

        队列是限制在两端进行插入操作和删除操作的线性表,允许进行存入操作的一端称为“队尾”,允许进行删除操作的一端称为“队头”,当线性表中没有元素时,称为“空队”,特点 :先进先出(FIFO)。

typedef int datatype;
#define N 128

// 当front和rear的值相同时,表示队列为空,但对于循环队列来讲,满队时,front和rear的值也相同
// 所以对于队列来说,当队列只剩下一个空位置时,即视为满队,即,当(rear+1)%N 等于front时,视为满队
typedef struct {
	datatype data[N];
	int front; // 队头元素的下标
	int rear;  // 队尾元素的下一个位置的下标(待存入值的位置)
}sequeue;

规定:

        1.front指向队头元素的位置; rear指向队尾元素的下一个位置。
        2.在队列操作过程中,为了提高效率,以调整指针代替队列元素的移动,并将数组作为循环队列的操作空间。
        3.为区别空队和满队,满队元素个数比数组实际能容纳的个数少1。

2.顺序队列的实现

创建队列与释放队列

sequeue * queue_create() {
	sequeue *sq;

	if ((sq = (sequeue *)malloc(sizeof(sequeue))) == NULL) {
		printf("malloc failed\n");
		return NULL;
	}

	memset(sq->data, 0, sizeof(sq->data));
	sq->front = sq->rear = 0; // rear指向待插入位置,front指向当前首位置
	return sq;
}
sequeue * queue_free(sequeue *sq) {
	if (sq == NULL) {
		printf("sq is NULL\n");
		return NULL;
	}

	free(sq); // 如果sq中的data用的是指针,那么data需要在创建时申请内存,在此时释放内存
	sq = NULL;

	return NULL;
}

入队与出队

int enqueue(sequeue *sq, datatype x) {
	if (sq == NULL) {
		printf("sq is NULL\n");
		return -1;
	}
	
// 当队列只剩下一个空位置时,即视为满队,此时rear指向最后一个空位置,
// 若队尾在内存的末端,则此时(rear+1)%N 等于front,若队尾在内存的中间(循环队列),则rear+1等于front
// 即,当(rear+1)%N 等于front时,视为满队
	if ((sq->rear + 1) % N == sq->front) { 
		printf("sequeue is full\n");
		return -1;
	}

	sq->data[sq->rear] = x;
	// 这里对N取余是为了应对这种情况:循环队列,rear指向内存末端
	// 同时front指向内存中间,内存的前端还有空余,此时rear+1就超出内存范围了
	// 对N取余后,就会利用上内存前端的空间,构成循环队列
	sq->rear = (sq->rear + 1) % N; 

	return  0;
}
datatype dequeue(sequeue *sq) {
	datatype ret; // 用于返回出队的值

	ret = sq->data[sq->front]; 
  // 队首到队尾索引从低到高,所以这里是+1不是-1
	sq->front = (sq->front + 1) % N;  // 同时,对N取余也是为了适应循环队列

	return ret;
}

判断空队与满队

int queue_empty(sequeue *sq) {
	if (sq == NULL) {
		printf("sq is NULL\n");
		return -1;
	}

	return (sq->front == sq->rear ? 1 : 0);
}
int queue_full(sequeue *sq) {
	if (sq == NULL) {
		printf("sq is NULL\n");
		return -1;
	}

	if ((sq->rear + 1) % N == sq->front) {
		return 1;
	}
	else {
		return 0;
	}
}

清空队列

int queue_clear(sequeue *sq) {
	if (sq == NULL) {
		printf("sq is NULL\n");
		return -1;
	}

	sq->front = sq->rear = 0;

	return 0;
}

3.链式队列的原理与实现

        插入操作在队尾进行,删除操作在队头进行,由队头指针和队尾指针控制队列的操作。

typedef int data_t;     

typedef struct node {   
    data_t data;		
    struct node_t *next; 	
} listnode, *linklist;    
              
typedef struct {  
    linklist_t front, rear;  // 这里front和rear还是对应位置的索引,不过是以指针的形式,指向节点类型的变量
} linkqueue; 	

创建和释放队列

linkqueue * queue_create() {
	linkqueue *lq;

	if ((lq = (linkqueue *)malloc(sizeof(linkqueue))) == NULL) {
		printf("malloc linkqueue failed\n");
		return NULL;
	}
	// 创建时,front和rear都指向同一个节点,即头节点,而不是队头节点
	lq->front = lq->rear = (linklist)malloc(sizeof(listnode)); 
	if (lq->front == NULL) {
		printf("malloc node failed\n");
		return NULL;
	}
	lq->front->data = 0;
	lq->front->next = NULL; //此时rear也指向头节点,而不是头节点中的next,这两句话用lq->rear代替lq->front依然正确

	return lq;
}
linkqueue * queue_free(linkqueue *lq) {
	linklist p;

	if (lq == NULL) {
		printf("lq is NULL\n");
		return NULL;
	}

	while (lq->front) { // 和清空队列的不同之处在于,头指针也释放
		p = lq->front;
		lq->front = p->next;
		printf("free:%d\n", p->data);
		free(p);
	}

	free(lq);  // 和清空队列的不同之处在于,用于描述队列信息的结构体也被释放
	lq = NULL;

	return NULL;
}

入队和出队

int enqueue(linkqueue *lq, datatype x) {
	linklist p;

	if (lq == NULL) {
		printf("lq is NULL\n");
		return -1;
	}
	// 先新建一个节点:1.申请内存 2.初始化data和*next
	if ((p = (linklist)malloc(sizeof(listnode))) == NULL) {
		printf("malloc node failed\n");
		return -1;
	}
	p->data = x;
	p->next = NULL;

	lq->rear->next = p; // 连上
	lq->rear = p;       // 移动指针

	return 0;
}
datatype dequeue(linkqueue *lq) {
	linklist p;

	if (lq == NULL) {
		printf("lq is NULL\n");
		return -1;
	}
 // 这里涉及到一个思想,因为链式队列涉及到头节点,和队头可能冲突
 // 出队其实出的应该是队头,不应该出头节点,这里出队时,出的是头节点
 // 思想就是:出了头节点后,认为队头就是新的头节点,队列中的第二个元素成为新的队头
 // 如此一来,就相当于把队头出掉了,真实情况是队头被当作了新的头节点,它的data值也就每人在乎了
	p = lq->front;
	lq->front = p->next;  // 队头成为新的头节点
	free(p);
	p = NULL;

	return (lq->front->data);
}

队列是否为空

int queue_empty(linkqueue *lq) {
	if (lq == NULL) {
		printf("lq is NULL\n");
		return -1;
	}

	return (lq->front == lq->rear ? 1 : 0); // front和rear指向的节点相同,视为空
}

清空队列

// 由于这是链式队列,所以清空队列应该是把头节点外的所有节点都释放
int queue_clear(linkqueue *lq) {
	linklist p;

	if (lq == NULL) {
		printf("lq is NULL\n");
		return -1;
	}

	while (lq->front->next) { // 这里保留了头节点,从队头开始释放
		p = lq->front;
		lq->front = p->next;
		printf("clear free:%d\n", p->data);
		free(p);
		p = NULL;
	}
	return 0;
}

4.栈和队列的应用

球钟问题

工作原理

问题

 为什么队列中球数为27:小时容器11个 + 5分钟容器11个 + 1分钟容器4个 = 26个,第27个球的作用很特殊,作用是把这26个清空,实现11:59到00:00的转变。

 容器:是栈,有最大容量,故选择顺序栈。

 由于循环队列涉及到取余操作,链式队列用起来相对简单,故选择链式队列。

#include <stdio.h>
#include "linkqueue.h"
#include "sqstack.h"

int check(linkqueue * lq);

int main(int argc, const char *argv[])
{
	linkqueue *lq; // 描述链式队列的结构体,里面有front,rear
	sqstack *s_hour, *s_five, *s_min; // 定义了3个结构体指针,指向顺序栈对象
	int value;
	int i, min = 0;

	if ((lq = queue_create()) == NULL) {  // 创建链式队列
		return -1;
	}

	for (i = 1; i <= 27; i++) {  // 将27个球入队
		enqueue(lq, i);
	}

	if ((s_hour = stack_create(11)) == NULL) { // 创建特定容量的顺序栈
		return -1;
	}

	if ((s_five = stack_create(11)) == NULL) {
		return -1;
	}

	if ((s_min = stack_create(4)) == NULL) {
		return -1;
	}

	while (1) {
		min++; // 这里记录次数,因为题目问的就是min有多少次
		if (!queue_empty(lq)) {  // 如果队列还有数
			value = dequeue(lq);   // 出队一个数给value,利用value将这个数送入栈
			if (!stack_full(s_min)) {  
				stack_push(s_min, value);   // 后面代码不再执行
			} else {   // 此时value中的球还没入栈,else执行,说明min栈满了,这个球会在后面给到5min栈
				while (!stack_empty(s_min)) {   
					enqueue(lq, stack_pop(s_min)); //*将min栈出栈 同时入队到队列,直到栈空为止
				}
				if (!stack_full(s_five)) {   // 这里注意括号,这个if是在else里面的,即此时min栈满了,但又被清空了
					stack_push(s_five, value); // min栈满了,清空min栈后,5min栈入栈一元素,后面代码不再执行
				} else {   // else执行,说明min栈、5min栈都满了
					while (!stack_empty(s_five)) { // while循环,开始清理5min栈
						enqueue(lq, stack_pop(s_five)); // 出栈后入队
					}
					if (!stack_full(s_hour)) {
						stack_push(s_hour, value); // 之前的5min栈满了,小时栈自然要push一个值,后面不再执行
					} else {  // 此时的value是第27个球,else执行,说明min栈、5min栈、小时栈都满了
						while (!stack_empty(s_hour)) {
							enqueue(lq, stack_pop(s_hour)); // 清空小时栈后入队
						}
						enqueue(lq, value); // 这个第27个球,不会入栈,在外边溜达一圈后直接归队
						//上面语句执行完后,27个球全部归队,时间置为00:00
						if (check(lq) == 1) {
							break; // 这个break跳出的是while(1)循环,其他while循环均不在break外层
						}
					}
				}

			}
		}
	} // while(1)的回括号
	printf("total:%d\n", min);

	printf("dequeue:");
	while (!queue_empty(lq)) 
		printf("%d ", dequeue(lq)); 
	puts(""); 

	return 0;
} // main的回括号

// 检查传入的链式队列,是否为升序,若是返回1,不是返回0
int check(linkqueue * lq) { 
	linklist p;

	if (lq == NULL) {
		printf("lq is NULL\n");
		return -1;
	}

	p = lq->front->next;  // p指向队头

	while (p && p->next) {  // 队头和队头后面一个元素均非空时
		if (p->data < p->next->data) {
			p = p->next;
		} else {
			return 0;
		}
	}
	return 1;
}

相关题目

1、链式队列有假溢出么?链式队列需要判空判满么?为什么?
2、代码实现链式队列,输入数字入队,输入字符出队。

1、链式队列没有假溢出,链式队列可以判空,不需要判满,链式队列每次入队会新建节点,每次出队会释放节点,没有明确的长度限制,故不涉及溢出和满队列,当链式队列的front和rear指向同一个节点时,视链式队列为空队列。

2、可以尝试下实现输入多位数。用%d,判断scanf的返回值就知道输入的是字符还是数字了。

int main()
{
	int value;
	//char ch;
	linkqueue lp  = listqueue_create();
	printf("Please enter a character or a number from 0 to 9:\nEnter '?' to stop.\n");
	while (1) {
		char ch;
		scanf("%c", &ch);

		if (isalpha(ch)) 
			printf("%d has dequeued.\n", dequeue(lp));

		if (isdigit(ch)) {
			ch = (int)(ch - '0');
			printf("%d has enqueued.\n", ch);
			enqueue(lp, ch);
		}
		if (ch == '?')
			break;
	}
	while (lp->front->next!= NULL) {
		printf("%d ", dequeue(lp));
	}
	puts("");
	return 0;
}
  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
高分笔记系列书籍简介高分笔记系列书籍包括《数据结构高分笔记》《组成原理高分笔记》《操作系统高分笔记》《计算机网络高分笔记》等,是一套针对计算机考研的辅导书。它们2010 年夏天诞生于一群考生之手,其写作风格突出表现为:以学生的视角剖析知识难点;以通俗易懂的语言取代晦涩难懂的专业术语;以成功考生的亲身经历指引复习方向;以风趣幽默的笔触缓解考研压力。相信高分笔记系列书籍带给考生的将是更高效、更明确、更轻松、更愉快的复习过程。 数据结构高分笔记简介众所周知,在计算机统考的四门专业课中,最难拿高分的就是数据结构。但是这门课本身的难度并不是考生最大的障碍,真正的障碍在于考生不能独自把握复习方向和考试范围。也许有学生要问,我们不是有大纲吗?照着大纲去复习不就可以了吗?表面上看是这样的,但是当你真正开始复习的时候就会发现,其实大纲只给了考生一个大致范围,有很多地方是模糊的,这些模糊的地方可能就是你纠结的地方。比如大纲里对于栈和队列的考查中有这么一条:“栈和队列的应用”。这个知识点就说得很模糊,因为只要涉及栈和队列的地方,都是其应用的范畴,这时考生该怎么办呢?于是把所有的希望寄托于参考书,希望参考书能帮助我们理解大纲的意图。参考书分为两种:一是课本,二是与课本配套的辅导书。对于课本,考生用得最多的就是严蔚敏老师编写的“严版”《数据结构》。因为这本书的内容非常丰富,如果能把这本书中考试大纲要求的章节理解透彻,参加考研就没有任何问题,但是这个过程是漫长的,除非本科阶段就学得非常好。计算机统考后,专业课四门加上公共课三门,一共是七门,绝大多数考生复习的时间一般也就六个月,而数据结构的复习需要占用多少时间,这点大家都很清楚。要在这么短的时间内掌握“严版”《数据结构》中考纲要求的知识点,基本上是不可能的,这就需要一本辅导书来依照大纲从课本中总结出考纲要求的知识点,才能使得考生在短时间内达到研究生考试的要求。市面上的参考书有两种:一种是四合一的辅导书,另一种是分册的。比如网上流行的《1800 题》及其第2 版,此书中题目极多,并且有很多老式的考研题,有些算法设计题的答案是用Pascal语言写的。这本书中的题目一般考生全做基本上是不可能的,挑着做又会把时间浪费在选题上。不可否认,这本书确实是一本非常好的题库,但是考生直接拿来用作考研辅导书却不太合适。在这种情况下,就需要有一本优质的完全针对新大纲的辅导书出现,这就是高分笔记产生的原因。 接下来详细介绍一下这本辅导书的写作过程,请看下图: 前 言VII图中所涉及的书都是大家很熟悉的。当年这些书编者都买了,花了很大心思才从中找出在考研战场上真正有用的东西。比如《1800 题》,里边既有好题,又有废题,相信很多人都希望有人能从中去掉重复的题目,选出大纲要求的题目,并能把解答写得更通俗易懂些,而现在编者所做的工作就是从这1800 道题中选出大纲要求的题目,并且修正部分解答,使其更容易理解。其次是“严版”《数据结构》,此书写得很严谨,语言表述非常专业,但对于基础稍差的学生来说读起来十分费力,要很长时间才能适应这本书的写作风格。如果有一本辅导书能把那些复杂程序的执行过程、拗口的专业术语、令人头大的符号翻译成容易理解的语言,就可以节省考生很多时间,因此,编者所做的事情就是根据自己复习的经验,以及对这本书的理解,把其中考试不需要的内容删掉,把需要的内容改造成一般考生容易接受的形式。对于李春葆老师的《数据结构习题与解析》,也做了类似的处理,并且在这本书中穿插讲解了一些考试大纲中没有明文规定,但是很多算法题目中大量用到的算法设计思想,来帮助大家提高解算法设计题的能力,比如搜索(打印图中两结点之间的所有路径)、分治法(二分法排序、求树的深度等)等算法思想。因此,相信本书会给读者的考研复习带来很大的帮助。 另外,本书配有微信公众号来收集读者的反馈,这也是本书不断更新完善的重要途径,即根据考生最需要的内容来作为调整讲解的依据。 本书特点: (1)精心挑选出适合考研的习题,并配上通俗易懂的答案,供读者自测和练习。 (2)总结出考研必备知识点,并且帮读者把其中过于专业、过于严谨的表述翻译成通俗易懂的语言。 (3)针对近年数据结构大题的出题风格(比如算法设计题目中的三段式题目:①表述算法思想;②写出算法描述;③计算算法的时间和空间复杂度),设计了独特的真题仿造部分,让读者在复习的过程中逐渐适应不同类型的题目。 参加本书编写的人员还有:章露捷,刘建萍,施伟,刘炳瑞,刘菁,孙琪,金苍宏,2019 版数据结构高分笔记VIII蔡明婉,吴雪霞,孙建兴,张继建,胡素素,邱纪虎,率方杰,李玉兰,率秀颂,刘忠艳,赵建,张兆红,张来恩,张险峰,殷凤岭,于雪友,周桂芝,张玉奎,李亚静,周莉,李娅,刘梅,殷晓红,李艳红,王中静,张洪英,王艳红
数据结构学习资料分享 内容概览: 本次分享包涵了大学计算机相关专业必学的“数据结构”课程的一系列学习资料。主要包括: 算法代码:我们提供了多种数据结构实现代码,包括数组、链表、栈、队列、树、图等。这些代码不仅能帮助你理解数据结构的基本概念,而且能让你明白如何在实际情况中应用这些数据结构笔记:详细且系统的笔记,涵盖了数据结构的各个方面,从基础概念到复杂的数据结构如堆、B树等。这些笔记有助于你系统地复习和学习数据结构。 相关书籍推荐:为了更深入地理解数据结构,我们推荐了几本经典的教材和参考书籍。这些书籍将帮助你建立完整的数据结构知识体系。 适用人群: 这份学习资料适用于所有大学计算机相关专业的学生,无论你是初学者还是已经有一定的数据结构基础。同时,对于对数据结构感兴趣的非专业人士,这份资料也是一个很好的起点。 使用建议: 结合理论和实践:在学习的过程中,请结合算法代码和理论知识。尝试自己编写代码实现数据结构,并在遇到问题时参考提供的代码。 由浅入深:建议先从基础数据结构开始学习,如数组和链表,然后再学习更复杂的数据结构如树和图。 多做练习:数据结构是实践性很强的学科。通过多做练习,你可以更好地理解数据结构的基本概念和原理,并提高编程能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

DUANDAUNNN

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

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

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

打赏作者

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

抵扣说明:

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

余额充值