IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【数据结构笔记】数据结构基础—队列 -> 正文阅读

[数据结构与算法]【数据结构笔记】数据结构基础—队列

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;
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-08-28 09:36:03  更:2021-08-28 09:37:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 22:43:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码