数据结构(五)-队列(Java、C语言)

今天内容比较简单~

一、队列的定义

队列:队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表

二、队列的抽象数据类型

ADT Queue{

数据对象:D={ai|ai∈ElemSet, i=1,2,,n, n≥0}

数据关系:R1={<ai-1,ai>|ai-1,ai∈D, i=1,2,,n }

           约定a1为队列头,an为队列尾。
基本操作:
   InitQueue( &Q )
     操作结果:构造一个空队列Q。
   DestroyQueue ( &Q )
     初始条件:队列Q已存在。
     操作结果:销毁队列Q。
   ClearQueue ( &Q )
     初始条件:队列Q已存在。
     操作结果:将Q清为空队列。
   QueueEmpty( Q )
     初始条件:队列Q已存在。
     操作结果:若Q为空队列,则返回TRUE,否则返回FALSE。
   QueueLength( Q )
     初始条件:队列Q已存在。
     操作结果:返回Q的数据元素个数,即队列的长度。
   GetHead( Q, &e )
     初始条件:队列Q已存在且非空。
     操作结果:用e返回Q的队头元素。
   EnQueue( &Q, e )
     初始条件:队列Q已存在。
     操作结果:插入元素e为Q的新的队尾元素。
   DeQueue( &Q, &e )
     初始条件:队列Q已存在且非空。
     操作结果:删除Q的队头元素,并用e返回其值。
QueueTraverse( Q, visit() )
     初始条件:队列Q已存在且非空。
     操作结果:从队头到队尾依次对Q的每个数据元素调用函数visit()。一旦visit()失败,则操作失败。
}ADT Queue

三、循环队列(C语言)

如果使用队列的顺序存储结构,我们来想想有什么不足?

由于这是一个线性表,我们在队尾进行添加并没有什么问题,但是当我们在队首出队一个元素之后,后面的元素都需要向前挪动一个位置,此时的时间复杂度是n,因此,我们需要寻找一种方法来弥补这种不足。

队首元素不一定存储在0下标的位置可以解决上述问题吗?会不会产生新的问题?

我们如果让出队的元素出队后,后面的元素不移动,可以解决上述问题,但是,这会产生一个新的问题,当最后一个元素的位置已经位于数组的最后,再插入一个新的元素,那么这个新的元素将无法插入进去,会报数组越界的错误。

循环队列:
上述问题,都可以用循环队列来进行解决,让存储的时候,存储到末尾的时候又从头开始添加
在这里插入图片描述
由上面的图我们可以知道,循环队列需要一个头指针和尾指针进行标识,front为头指针,rear为尾指针。下面来讨论几种方法的实现

如何判断队列为空:
若头指针和尾指针相同,则判断为空,即front==rear

如何判断队列为满:
若队列为满,也会出现front=rear的情况,为了与队列为空的时候区别开来,我们可以保留一个空位,这个空位不存放元素,如果队列内除了这个元素其他元素都满了,则说明队列已经满了。可以看上面那个图。
队列满的条件:
(rear+2)%QueueSize==front

如何计算队列长度:
(rear-front+QueueSize)%QueueSize

#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 Status; 
typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */

/* 循环队列的顺序存储结构 */
typedef struct
{
	QElemType data[MAXSIZE];
	int front;    	/* 头指针 */
	int rear;		/* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}SqQueue;

Status visit(QElemType c)
{
	printf("%d ",c);
	return OK;
}

/* 初始化一个空队列Q */
Status InitQueue(SqQueue *Q)
{
	Q->front=0;
	Q->rear=0;
	return  OK;
}

/* 将Q清为空队列 */
Status ClearQueue(SqQueue *Q)
{
	Q->front=Q->rear=0;
	return OK;
}

/* 若队列Q为空队列,则返回TRUE,否则返回FALSE */
Status QueueEmpty(SqQueue Q)
{ 
	if(Q.front==Q.rear) /* 队列空的标志 */
		return TRUE;
	else
		return FALSE;
}

/* 返回Q的元素个数,也就是队列的当前长度 */
int QueueLength(SqQueue Q)
{
	return  (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}

/* 若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR */
Status GetHead(SqQueue Q,QElemType *e)
{
	if(Q.front==Q.rear) /* 队列空 */
		return ERROR;
	*e=Q.data[Q.front];
	return OK;
}

/* 若队列未满,则插入元素e为Q新的队尾元素 */
Status EnQueue(SqQueue *Q,QElemType e)
{
	if ((Q->rear+1)%MAXSIZE == Q->front)	/* 队列满的判断 */
		return ERROR;
	Q->data[Q->rear]=e;			/* 将元素e赋值给队尾 */
	Q->rear=(Q->rear+1)%MAXSIZE;/* rear指针向后移一位置, */
								/* 若到最后则转到数组头部 */
	return  OK;
}

/* 若队列不空,则删除Q中队头元素,用e返回其值 */
Status DeQueue(SqQueue *Q,QElemType *e)
{
	if (Q->front == Q->rear)			/* 队列空的判断 */
		return ERROR;
	*e=Q->data[Q->front];				/* 将队头元素赋值给e */
	Q->front=(Q->front+1)%MAXSIZE;	/* front指针向后移一位置, */
									/* 若到最后则转到数组头部 */
	return  OK;
}

/* 从队头到队尾依次对队列Q中每个元素输出 */
Status QueueTraverse(SqQueue Q)
{ 
	int i;
	i=Q.front;
	while((i+Q.front)!=Q.rear)
	{
		visit(Q.data[i]);
		i=(i+1)%MAXSIZE;
	}
	printf("\n");
	return OK;
}

int main()
{
	Status j;
	int i=0,l;
	QElemType d;
	SqQueue Q;
	InitQueue(&Q);
	printf("初始化队列后,队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));

	printf("请输入整型队列元素(不超过%d个),-1为提前结束符: ",MAXSIZE-1);
	do
	{
		/* scanf("%d",&d); */
		d=i+100;
		if(d==-1)
			break;
		i++;
		EnQueue(&Q,d);
	}while(i<MAXSIZE-1);

	printf("队列长度为: %d\n",QueueLength(Q));
	printf("现在队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
	printf("连续%d次由队头删除元素,队尾插入元素:\n",MAXSIZE);
	for(l=1;l<=MAXSIZE;l++)
	{
		DeQueue(&Q,&d);
		printf("删除的元素是%d,插入的元素:%d \n",d,l+1000);
		/* scanf("%d",&d); */
		d=l+1000;
		EnQueue(&Q,d);
	}
	l=QueueLength(Q);

	printf("现在队列中的元素为: \n");
	QueueTraverse(Q);
	printf("共向队尾插入了%d个元素\n",i+MAXSIZE);
	if(l-2>0)
		printf("现在由队头删除%d个元素:\n",l-2);
	while(QueueLength(Q)>2)
	{
		DeQueue(&Q,&d);
		printf("删除的元素值为%d\n",d);
	}

	j=GetHead(Q,&d);
	if(j)
		printf("现在队头元素为: %d\n",d);
	ClearQueue(&Q);
	printf("清空队列后, 队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
	return 0;
}

四、循环队列(Java)

Java中其实封装好了很多数据结构,但是我们为什么要自己再写一遍呢?如果我们遇到Java中的数据额结构无法满足生产需求的时候,我们这种时候就可以自己根据需求写一个数据结构来使用,但是这种情况下,往往容易出现一些未知的错误,因此,一般非特殊情况并不推荐使用自己写的数据结构。下面是自己写的一个代码。也可以进行泛型化,这样更接近源码。

package Text;

// 栈的链式存储:优点是不存在栈满的情况
public class LinkStack {
    private Node header = new Node();   // 创建头节点
    class Node {
        String value;
        Node next;      
    }
    public boolean push(String value) {
        Node n = new Node();
        n.value = value;
        if(header.next == null) {
            header.next = n;
            return true;
        }
        n.next = header.next;
        header.next = n;
        return true;

    }
    public boolean pop() {
        if(header.next == null) {
            return false;
        }
        header.next = header.next.next;
        return true;
    }
    public String getTop() {
        if(header.next != null) {
            return header.next.value;
        }
        return null;
    }
    public boolean isEmpty() {
        return header.next == null ? true : false;
    }
}

五、队列的链式存储结构的实现(C语言、Java)

相比于循环队列,其实队列的链式存储结构更加简单,它没有存储空间限制,并且也没有元素移动问题,操作与实现上与线性表是一模一样的。

上代码:

#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 Status; 

typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */

typedef struct QNode	/* 结点结构 */
{
   QElemType data;
   struct QNode *next;
}QNode,*QueuePtr;

typedef struct			/* 队列的链表结构 */
{
   QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;

Status visit(QElemType c)
{
	printf("%d ",c);
	return OK;
}

/* 构造一个空队列Q */
Status InitQueue(LinkQueue *Q)
{ 
	Q->front=Q->rear=(QueuePtr)malloc(sizeof(QNode));
	if(!Q->front)
		exit(OVERFLOW);
	Q->front->next=NULL;
	return OK;
}

/* 销毁队列Q */
Status DestroyQueue(LinkQueue *Q)
{
	while(Q->front)
	{
		 Q->rear=Q->front->next;
		 free(Q->front);
		 Q->front=Q->rear;
	}
	return OK;
}

/* 将Q清为空队列 */
Status ClearQueue(LinkQueue *Q)
{
	QueuePtr p,q;
	Q->rear=Q->front;
	p=Q->front->next;
	Q->front->next=NULL;
	while(p)
	{
		 q=p;
		 p=p->next;
		 free(q);
	}
	return OK;
}

/* 若Q为空队列,则返回TRUE,否则返回FALSE */
Status QueueEmpty(LinkQueue Q)
{ 
	if(Q.front==Q.rear)
		return TRUE;
	else
		return FALSE;
}

/* 求队列的长度 */
int QueueLength(LinkQueue Q)
{ 
	int i=0;
	QueuePtr p;
	p=Q.front;
	while(Q.rear!=p)
	{
		 i++;
		 p=p->next;
	}
	return i;
}

/* 若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR */
Status GetHead(LinkQueue Q,QElemType *e)
{ 
	QueuePtr p;
	if(Q.front==Q.rear)
		return ERROR;
	p=Q.front->next;
	*e=p->data;
	return OK;
}


/* 插入元素e为Q的新的队尾元素 */
Status EnQueue(LinkQueue *Q,QElemType e)
{ 
	QueuePtr s=(QueuePtr)malloc(sizeof(QNode));
	if(!s) /* 存储分配失败 */
		exit(OVERFLOW);
	s->data=e;
	s->next=NULL;
	Q->rear->next=s;	/* 把拥有元素e的新结点s赋值给原队尾结点的后继,见图中① */
	Q->rear=s;		/* 把当前的s设置为队尾结点,rear指向s,见图中② */
	return OK;
}

/* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */
Status DeQueue(LinkQueue *Q,QElemType *e)
{
	QueuePtr p;
	if(Q->front==Q->rear)
		return ERROR;
	p=Q->front->next;		/* 将欲删除的队头结点暂存给p,见图中① */
	*e=p->data;				/* 将欲删除的队头结点的值赋值给e */
	Q->front->next=p->next;/* 将原队头结点的后继p->next赋值给头结点后继,见图中② */
	if(Q->rear==p)		/* 若队头就是队尾,则删除后将rear指向头结点,见图中③ */
		Q->rear=Q->front;
	free(p);
	return OK;
}

/* 从队头到队尾依次对队列Q中每个元素输出 */
Status QueueTraverse(LinkQueue Q)
{
	QueuePtr p;
	p=Q.front->next;
	while(p)
	{
		 visit(p->data);
		 p=p->next;
	}
	printf("\n");
	return OK;
}

int main()
{
	int i;
	QElemType d;
	LinkQueue q;
	i=InitQueue(&q);
	if(i)
		printf("成功地构造了一个空队列!\n");
	printf("是否空队列?%d(1:空 0:否)  ",QueueEmpty(q));
	printf("队列的长度为%d\n",QueueLength(q));
	EnQueue(&q,-5);
	EnQueue(&q,5);
	EnQueue(&q,10);
	printf("插入3个元素(-5,5,10)后,队列的长度为%d\n",QueueLength(q));
	printf("是否空队列?%d(1:空 0:否)  ",QueueEmpty(q));
	printf("队列的元素依次为:");
	QueueTraverse(q);
	i=GetHead(q,&d);
	if(i==OK)
	 printf("队头元素是:%d\n",d);
	DeQueue(&q,&d);
	printf("删除了队头元素%d\n",d);
	i=GetHead(q,&d);
	if(i==OK)
		printf("新的队头元素是:%d\n",d);
	ClearQueue(&q);
	printf("清空队列后,q.front=%u q.rear=%u q.front->next=%u\n",q.front,q.rear,q.front->next);
	DestroyQueue(&q);
	printf("销毁队列后,q.front=%u q.rear=%u\n",q.front, q.rear);
	
	return 0;
}

下面是Java的代码

public class MyQueue {
    private Node front;// 指向队列头元素的引用
    private Node rear;// 指向队列尾元素的引用
 
    private MyQueue() {
        front = null;
        rear = null;
    }
    public boolean isEmpty() {
        // 指向队头元素的引用为空即队列为空
        return front == null;
    }
    public void addQueue(Object item) {
        // 创建一个要插入的新结点
        Node newNode = new Node();
        newNode.setData(item);
        newNode.setNext(null);
        if (isEmpty()) {
            front = newNode;
            rear = front;
        } else {
            rear.setNext(newNode);
            rear = newNode;
        }
    }
 
    public Object deleteQueue() throws Exception {
        if (isEmpty()) {
            throw new Exception("队列为空!");
        } else {
            Node delNode = front;
            if (front == rear) {
                front = rear = null;
            } else {
                front = front.getNext();
            }
            return delNode.getData();
        }
    }
}

六、总结

什么时候用循环队列,什么时候用链式队列?
在可以确定队列长度最大值的情况下,建议使用循环队列,如果无法确定队列的长度,则使用链队列

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1章 基本概念 1.1 概观:系统生命周期 1.2 指针和动态存储分配 1.3 算法形式规范 1.4 数据抽象 1.5 性能分析 1.6 性能度量 1.7 参考文献和选读材料 第2章 数组和结构 2.1 数组 2.2 数组的动态存储分配 2.3 结构体和联合体 2.4 多项式 2.5 稀松矩阵 2.6 多维数组的表示 2.7 字符串 2.8 参考文献和选读材料 2.9 补充习题 第3章 栈与队列 3.1 栈 .3.2 动态栈 3.3 队列 3.4 动态循环队列 3.5 迷宫问题 3.6 表达式求值 3.7 多重栈与多重队列 3.8 补充习题 第4章 链表 4.1 单向链表 4.2 用C语言表示单向链表 4.3 链式栈与链式队列 4.4 多项式 4.5 其它链表操作 4.6 等价类 4.7 稀疏矩阵 4.8 双向链表 第5章 树 5.1 引论 5.2 二叉树 5.3 遍历二叉树 5.4 其它二叉树操作 5.5 线索二叉树 5.6 堆 5.7 二叉查找树 5.8 选拔树 5.9 森林 5.10 不相交集合的表示 5.11 二叉树的计数 5.12 参考文献和选读材料 第6章 图 6.1 图的抽象数据类型 6.2 图的基本操作 6.3 最小代价生成树 6.4 最短路径和迁移闭包 6.5 活动网络 6.6 参考文献和选读材料 6.7 补充习题 第7章 排序 7.1 动机 7.2 插入排序 7.3 快速排序 7.4 排序最快有多快 7.5 归并排序 7.6 堆排序 7.7 多关键字排序 7.8 链表排序和索引表排序 7.9 内部排序小结 7.10 外部排序 7.11 参考文献和选读材料 第8章 Hash法 8.1 引言 8.2 静态Hash法 8.3 动态Hash法 8.4 Bloom滤波器 8.5 参考文献和选读材料 第9章 优先队列 9.1 单端优先队列和双端优先队列 9.2 左倾树 9.3 二项式堆 9.4 Fibonacci堆 9.5 配偶堆 9.6 对称最小-最大堆 9.7 区间堆 9.8 参考文献和选读材料 第10章 高效二叉查找树 10.1 最优二叉查找树 10.2 AVL树 10.3 红-黑树 10.4 Splay树 10.5 参考文献和选读材料 第11章 多路查找树 11.1 m-路查找树 11.2 B-树 11.3 B+树 11.4 参考文献和选读材料 第12章 数字查找结构 12.1 数字查找树 12. 2 二路Trie树和Patricia树 12.3 多路Trie树 12.4 后缀树 12.5 Trie树和互联网的包转发 12.6 参考文献和选读材料

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值