数据结构和算法(2)单向循环链表的建立插入删除实现github
数据结构和算法(5)栈和队列的操做和实现shell
@[TOC]数据结构
本篇博客代码下载:数据结构和算法
队列是一种先进先出(First In First Out)的线性表,也就是FIFO。一般,称进数据的一端为 "队尾",出数据的一端为 "队头",数据元素进队列的过程称为 "入队",出队列的过程称为 "出队"。post
与栈结构不一样的是,队列的两端都"开口",要求数据只能从一端进,从另外一端出,以下图 所示:单元测试
若是对栈结构不熟悉能够参考我以前的博客:“数据结构和算法(五)栈和队列的操做和实现”
栈和队列不要混淆,栈结构是一端封口,特色是"先进后出";而队列的两端全是开口,特色是"先进先出"。
所以,数据从表的一端进,从另外一端出,且遵循 "先进先出" 原则的线性存储结构就是队列。
队列存储结构的实现有如下两种方式:
- 顺序队列:在顺序表的基础上实现的队列结构;
- 链队列:在链表的基础上实现的队列结构; 二者的区别仅是顺序表和链表的区别,即在实际的物理空间中,数据集中存储的队列是顺序队列,分散存储的队列是链队列。
实际生活中,队列的应用随处可见,好比排队买 XXX、医院的挂号系统等,采用的都是队列的结构。
拿排队买票来讲,全部的人排成一队,先到者排的就靠前,后到者只能从队尾排队等待,队中的每一个人都必须等到本身前面的全部人所有买票成功并从队头出队后,才轮到本身买票。这就不是典型的队列结构吗?
明白了什么是队列,接下来开始学习顺序队列和链队列的基本实现和注意事项。
因为顺序队列的底层使用的是数组,所以需预先申请一块足够大的内存空间初始化顺序队列。除此以外,为了知足顺序队列中数据从队尾进,队头出且先进先出的要求,咱们还须要定义两个指针(top 和 rear)分别用于指向顺序队列中的队头元素和队尾元素,以下图 所示:
top
指针和
rear
指针重合,且因为顺序队列底层实现靠的是数组,所以
top
和
rear
其实是两个变量,它的值分别是队头元素和队尾元素所在数组位置的下标。
当有数据元素进队列时,对应的实现操做是将其存储在指针 rear
指向的数组位置,而后 rear+1
;当须要队头元素出队时,仅需作 top+1
操做。
举个栗子:
若是咱们要将 {1,2,3,4} 用顺序队列存储的实现,
元素1 进队的过程以下:
元素4入队过程:
那么咱们接下来要将1,2,3,4这四个元素出队。出队过程以下:
元素4出队:
咱们先看一下一个简单的顺序队列操做代码:
#include <stdio.h>
int enQueue(int *a,int rear,int data){
a[rear]=data;
rear++;
return rear;
}
void deQueue(int *a,int front,int rear){
//若是 front==rear,表示队列为空
while (front!=rear) {
printf("出队元素:%d\n",a[front]);
front++;
}
}
int main() {
int a[100];
int front,rear;
//设置队头指针和队尾指针,当队列中没有元素时,队头和队尾指向同一块地址
front=rear=0;
//入队
rear=enQueue(a, rear, 1);
rear=enQueue(a, rear, 2);
rear=enQueue(a, rear, 3);
rear=enQueue(a, rear, 4);
//出队
deQueue(a, front, rear);
return 0;
}
复制代码
输出结构为:
出队元素:1
出队元素:2
出队元素:3
出队元素:4
复制代码
上面这种顺序存储队列会存在一些问题,如假溢出问题,以下图:
为了解决上面问题,咱们有一种比较优的解决办法,就是用循环队列来解决假溢出问题。
接下来将介绍循环队列。
循环队列就是,当队尾指针移动到数组末尾时,下次再有元素入队时,能够将队尾指针从新移到数组前面没有元素的位置。
循环队列操做以下图:
如上图: (a) 咱们用Q.front == Q.rear表示队列为空。 当咱们依次入队a,b,c三个元素后,如上图(b)所示。 接下来,咱们将元素a 从队头出队,如上图(c)所示。 而后,咱们又依次入队了d ,e ,f, g这个时候实际上队列已经存满了,单咱们发现 Q.front == Q.rear,如上图(d1)所示。可是咱们前面(a)中用Q.front == Q.rear表示队列为空,可是队列满了的时候咱们Q.front == Q.rear就没法区分究竟是队列为空仍是满了。为了解决这个问题,咱们通常采用牺牲一个存储空间的方式。也就如上图(d2) 咱们用Q.front = Q.rear + 1表示堆满,就是牺牲一个存储单元不存放数据。
在循环队列中咱们判断队列为空,队列为满的条件以下:
- 队列为满:
(Q.rear+1)%maxSize==Q.front
- 队列为空:
Q.rear==Q.front
- 队列中有效的数据的个数:
(Q.rear+maxSize-Q.front)%maxSize
/* 循环队列的顺序存储结构 */
typedef struct KQueue {
KQueueElementType data[MAXSIZE];
int front; /* 头指针 */
int rear; /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}KQueue;
复制代码
//1. 初始化一个队列
KStatus initQueue(KQueue *Q) {
Q->front = Q->rear = 0;
return OK;
}
复制代码
//2. 将队列清空
KStatus clearQueue(KQueue *Q) {
Q->front = Q->rear = 0;
return OK;
}
复制代码
//3. 队列判空
KStatus isEmpty(KQueue Q) {
return Q.front == Q.rear ;
}
复制代码
//4. 队列是否满了
KStatus isFull(KQueue Q) {
return Q.front == (Q.rear + 1 ) % MAXSIZE;
}
复制代码
//5. 查询队列长度
int getLength(KQueue Q) {
return (Q.rear - Q.front + MAXSIZE)%MAXSIZE;
}
复制代码
//6. 获取队头元素
//若队列不空,则用e返回Q的队头元素,并返回OK,不然返回ERROR;
KStatus getHead(KQueue Q, KQueueElementType *e) {
//判断是否队列为空
if (isEmpty(Q)) {
return ERROR;
}
//取出元素值
*e = Q.data[Q.front];
return OK;
}
复制代码
//7. 入队
// 若队列未满,则插入元素e为新队尾元素
KStatus enQueue(KQueue *Q, KQueueElementType e) {
//判断队列是否满了
if (isFull(*Q)) {
return ERROR;
}
//将元素e赋值给队尾
Q->data[Q->rear] = e;
//rear指针向后移动一位,若到最后则转到数组头部
Q->rear = (Q->rear + 1) % MAXSIZE;
return OK;
}
复制代码
//8. 出队
//若队列不空,则删除Q中队头的元素,用e返回值
KStatus deQueue(KQueue *Q, KQueueElementType *e) {
if (isEmpty(*Q)) return ERROR;
//从队头取出元素赋值给e
*e = Q->data[Q->front];
//front指针向后移动一位,删除对头元素
Q->front = (Q->front + 1) % MAXSIZE;
return OK;
}
复制代码
//9. 遍历队列
KStatus traverseQueue(KQueue Q) {
int i = Q.front;
while ((i + Q.front) != Q.rear) {
//从队头遍历到队尾,依次输出元素,i表示当前已经输出到第几个元素了
//(i + Q.front) != Q.rear 表示 已经遍历到了队尾了,
//因为咱们不能修改front和rear指向,因此须要一个临时变量记录当前位置
printf("%d ", Q.data[i]);
i = (i + 1) % MAXSIZE;
}
printf("\n");
return OK;
}
复制代码
//10. 单元测试
void test() {
printf("循环队列操做单元测试\n");
KStatus j;
int i=0;
KQueueElementType d;
KQueue Q;
initQueue(&Q);
printf("初始化队列后,队列空否?%u(1:空 0:否)\n",isEmpty(Q));
printf("入队:\n");
while (i < 10) {
enQueue(&Q, i);
i++;
}
traverseQueue(Q);
printf("队列长度为: %d\n",getLength(Q));
printf("如今队列空否?%u(1:空 0:否)\n",isEmpty(Q));
printf("出队:\n");
//出队
deQueue(&Q, &d);
printf("出队的元素:%d\n",d);
traverseQueue(Q);
//获取队头
j=getHead(Q,&d);
if(j)
printf("如今队头元素为: %d\n",d);
clearQueue(&Q);
printf("清空队列后, 队列空否?%u(1:空 0:否)\n",isEmpty(Q));
}
复制代码
//
// main.c
// 010_Queue
//
// Created by 孔雨露 on 2020/4/18.
// Copyright © 2020 Apple. All rights reserved.
//
#include <stdio.h>
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int KStatus;
typedef int KQueueElementType; /* KQueueElementType类型根据实际状况而定,这里假设为int */
/* 循环队列的顺序存储结构 */
typedef struct KQueue {
KQueueElementType data[MAXSIZE];
int front; /* 头指针 */
int rear; /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}KQueue;
//1. 初始化一个队列
KStatus initQueue(KQueue *Q) {
Q->front = Q->rear = 0;
return OK;
}
//2. 将队列清空
KStatus clearQueue(KQueue *Q) {
Q->front = Q->rear = 0;
return OK;
}
//3. 队列判空
KStatus isEmpty(KQueue Q) {
return Q.front == Q.rear ;
}
//4. 队列是否满了
KStatus isFull(KQueue Q) {
return Q.front == (Q.rear + 1 ) % MAXSIZE;
}
//5. 查询队列长度
int getLength(KQueue Q) {
return (Q.rear - Q.front + MAXSIZE)%MAXSIZE;
}
//6. 获取队头元素
//若队列不空,则用e返回Q的队头元素,并返回OK,不然返回ERROR;
KStatus getHead(KQueue Q, KQueueElementType *e) {
//判断是否队列为空
if (isEmpty(Q)) {
return ERROR;
}
//取出元素值
*e = Q.data[Q.front];
return OK;
}
//7. 入队
// 若队列未满,则插入元素e为新队尾元素
KStatus enQueue(KQueue *Q, KQueueElementType e) {
//判断队列是否满了
if (isFull(*Q)) {
return ERROR;
}
//将元素e赋值给队尾
Q->data[Q->rear] = e;
//rear指针向后移动一位,若到最后则转到数组头部
Q->rear = (Q->rear + 1) % MAXSIZE;
return OK;
}
//8. 出队
//若队列不空,则删除Q中队头的元素,用e返回值
KStatus deQueue(KQueue *Q, KQueueElementType *e) {
if (isEmpty(*Q)) return ERROR;
//从队头取出元素赋值给e
*e = Q->data[Q->front];
//front指针向后移动一位,删除对头元素
Q->front = (Q->front + 1) % MAXSIZE;
return OK;
}
//9. 遍历队列
KStatus traverseQueue(KQueue Q) {
int i = Q.front;
while ((i + Q.front) != Q.rear) {
//从队头遍历到队尾,依次输出元素,i表示当前已经输出到第几个元素了
//(i + Q.front) != Q.rear 表示 已经遍历到了队尾了,
//因为咱们不能修改front和rear指向,因此须要一个临时变量记录当前位置
printf("%d ", Q.data[i]);
i = (i + 1) % MAXSIZE;
}
printf("\n");
return OK;
}
//10. 单元测试
void test() {
printf("循环队列操做单元测试\n");
KStatus j;
int i=0;
KQueueElementType d;
KQueue Q;
initQueue(&Q);
printf("初始化队列后,队列空否?%u(1:空 0:否)\n",isEmpty(Q));
printf("入队:\n");
while (i < 10) {
enQueue(&Q, i);
i++;
}
traverseQueue(Q);
printf("队列长度为: %d\n",getLength(Q));
printf("如今队列空否?%u(1:空 0:否)\n",isEmpty(Q));
printf("出队:\n");
//出队
deQueue(&Q, &d);
printf("出队的元素:%d\n",d);
traverseQueue(Q);
//获取队头
j=getHead(Q,&d);
if(j)
printf("如今队头元素为: %d\n",d);
clearQueue(&Q);
printf("清空队列后, 队列空否?%u(1:空 0:否)\n",isEmpty(Q));
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
test();
return 0;
}
复制代码
Hello, World!
循环队列操做单元测试
初始化队列后,队列空否?1(1:空 0:否)
入队:
0 1 2 3 4 5 6 7 8 9
队列长度为: 10
如今队列空否?0(1:空 0:否)
出队:
出队的元素:0
1 2 3 4 5 6 7 8
如今队头元素为: 1
清空队列后, 队列空否?1(1:空 0:否)
Program ended with exit code: 0
复制代码
链式队列的实现思想同顺序队列相似,只需建立两个指针(命名为 top 和 rear)分别指向链表中队列的队头元素和队尾元素,如图下图1 所示:
图 1 所示为链式队列的初始状态,此时队列中没有存储任何数据元素,所以 top 和 rear 指针都同时指向头节点。
下面咱们来说解一下 链式队列入队,出队操做,链式队列入队,出队操做基本跟单链表类似,若是不熟悉链表的操做能够参考我以前的链表相关的博客:
基本就是入队就是在链表尾部结点插入一个元素,出队就是在链表头结点删除一个结点。
{1,2,3}
三个元素:如上图所示,在链队队列中,当有新的数据元素入队,只需进行如下 3 步操做:
- 将该数据元素用节点包裹,例如新节点名称为 elem;
- 与 rear 指针指向的节点创建逻辑关系,即执行 rear->next=elem;
- 最后移动 rear 指针指向该新节点,即 rear=elem;
1,2
两个元素出队,其操做过程 以下图3所示:如上图3所示,咱们能够知道,在链式队列中队头元素出队,须要作如下 3 步操做:
- 经过 top 指针直接找到队头节点,建立一个新指针 p 指向此即将出队的节点;
- 将 p 节点(即要出队的队头节点)从链表中摘除;
- 释放节点 p,回收其所占的内存空间;
复制代码
复制代码
复制代码
复制代码
复制代码
复制代码
复制代码
复制代码
复制代码
复制代码
复制代码
复制代码
复制代码