数据结构与算法(八)——双端队列

目录

一、前情提要

         二、双端队列介绍

(一)双端队列的顺序存储

(二)双端队列的链式存储

三、代码实现

(一)双端队列的顺序存储

(二)双端队列的链式存储


一、前情提要

         上篇文章讲了队列的两种形式,一种是队列的顺序结构,称作循环队列。另一种时队列的链式结构,称作链队列。这两种形式都遵循着一个原则,先进先出的原则,从队尾入队,队头出队。但是还存在一种数据结构“双端队列”,它包含了栈和队列的所有特性。本篇文章将会给出所有操作的代码,并标注详细注释。

二、双端队列介绍

          双端队列(Double-ended Queue,简称Deque)是一种具有队列和栈特性的数据结构,可以在队列的两端进行插入和删除操作。双端队列允许从前端和后端同时进行插入和删除操作,因此可以称为“两端都可以进出的队列”。

           双端队列有以下几个特点:

           1、可以在队列的头部和尾部进行插入和删除的操作。

           2、元素的插入和删除操作可以分别称作入队和出队操作。

           3、可以进行先进先出(FIFO)和先进后出(LIFO)两种操作方式。

           4、可以实现栈和队列以及其他需要在两端进行插入和删除操作的场景。

           双端队列如图示:

      

    对于双端队列来说,就是两端都是结尾的队列。队列的每⼀端都可以插⼊数据项和移除数据项。相对于普通队列。双端队列的⼊队和出队操作在两端都可以进进⾏。
    这是一个很大的提升,这种数据结构的特性,使得他更加的实用和方便。当你只允许一端入队和出队的时候,这就是一个栈。当你限制一端只能入队和另一端只能出队,这就是一个普通队列。
    说完了原理,再来说说存储方式:
    对于双端队列而言,也存在两种存储结构——顺序存储、链式存储。
(一)双端队列的顺序存储
         我们知道顺序存储就是利用数组进行入队和出队。但是如何操作呢?
         首先,我们会设置两个指针。
          头指针——控制从左边入队和出队的操作。
         尾指针——控制从右边入队和出队的操作。
         现在我们有了两个指针,那么问题来了,指针指向哪呢?
         如果指针指向中间
       当出现这种情况时:
      当下次还要从左边插入元素,就可能出现溢出,但是右边还有位置空缺,所以就出现了 假溢出。说起假溢出,解决问题的方法,之前就说过,那就是利用 循环数组
      
       既然是循环的,指针指向哪已经不重要了,但正常情况下 头尾指针都是指向下标为0的数组位置。我们代码也就依据这个写。
       
       还有一个问题, 先开始下标为0的数组元素存储的是从左边入队的元素,还是从右边入队的元素,还是说都可以?
       可能有人有疑问,为什么要这么分?我们仔细分析一下代码。
       如果数组下标为0处 存储的是从左边入队的元素,那么左插和右插的代码就会是:
//假设数组为a[N]
//头尾指针都指向下标为0处

//左插入值为k1
//在下标为0处
  a[l]=k1;
  l--;

//当右插入值为k2
  r++;
  a[r]=k2;

        如果数组下标为0处存储的是从右边入队的元素,那么左插和右插的代码就会是:

//假设数组为a[N]
//先开始头尾指针指向数组下标为0处


//左插入值为k1
//在下标为0处
  l--;
  a[l]=k1;

//右插入值为k2
  a[r]=k2;
  r++;

   你会发现两份代码不一样,我们在写代码的时候不可能一种操作有两份代码。为了代码的统一性。所以最先开始下标为0的这个点必须规定好,要么存储的是左边入队的元素,要么存储的是右边入队的元素。

    当两个指针指向相同位置时要么队列为空,要么队列已满

    具体代码实现,请看后面。

    (二)双端队列的链式存储

     双端队列的链式存储可以理解为,是基于不带头结点的双向链表。

     此时我们会设置一个mid结点,头尾指针都会指向这个结点。

     那么还会有个问题值得思考。这个mid结点到底存不存数据呢?

     可能有人说存不存无所谓。我们先画个图,模拟一个场景。

    咱假设mid结点不存数据,此时从左边入队了两个元素,右边入队了两个元素。

    重点来了!如果此时我们要从左边出队三个元素,那该咋办呢?

    很明显我们要跳过mid结点去删除数据为3的结点,这样的话我们就得写个if语句特判一下。麻不麻烦暂且不说,很容易忽略掉。

     所以我们在mid结点处,要存入数据。跟上面的顺序存储一样,可以先开始存从左边入队的元素,也可以先开始存从右边入队的元素。在这里写代码统一规定,储存右边入队的元素。、

      因为是双向链表,所以入队的时候就是个简单的尾插代码,之前也有写过。

三、代码实现

       因为很多操作之前都写过了,所以注释就不再重复了,如果看不懂的就回去看对应双向链表的知识。

(一)双端队列的顺序存储
#include <stdio.h>
#include <stdlib.h>

#define QSize 10

int* Queue;
int Left,Right;
int MaxSize;
int Size;

void InitQueue()
{
	Queue=(int*)malloc(QSize*sizeof(int));
	MaxSize= QSize;
	Size=0;
	Left=Right=0;
}

void Left_Insert(int Date)
{
	if(Size==MaxSize)
	{
		printf("空间已满");
		return;
	}
	else
	{
		Left=(Left-1+QSize)%QSize;//如果看不懂,去看循环队列那篇 
		Queue[Left]=Date;
		Size++;
	}
}

void Right_Insert(int Date)
{
	if(Size==MaxSize)
	{
		printf("空间已满");
		return;
	}
	else
	{
		Queue[Right]=Date;
		Right=(Right+1)%QSize;
		Size++; 
	}
}

void Left_Remove()
{
	if(Size==0)
	{
		printf("队列为空");
		return;
	}
	else
	{
		Left=(Left+1)%QSize;
		Size--;
	} 
}

void Right_Remove()
{
	if(Size==0)
	{
		printf("队列为空");
		return;
	}
	else
	{
		Right=(Right-1+QSize)%QSize;
		Size--;
	}
}

void Show()
{
	if(Size==0) printf("队列为空");
	else 
	{
		int flag=Left;
		while(flag!=Right)
		{
			printf("%d ",Queue[flag]);
			flag=(flag+1)%QSize;
		} 
		printf("\n");
	}
}
int main()
{
	InitQueue();
	Left_Insert(1);
	Left_Insert(2);
	Left_Insert(3);
	Left_Insert(4);
	Right_Insert(5);
	Right_Insert(6);
	
	Show();
	
	Left_Remove();
	Right_Remove();
	Show();
	
	return 0;
} 
(二)双端队列的链式存储
#include <stdio.h>
#include <stdlib.h>

#define MaxSize 10

typedef struct LinkQueue
{
	int Data;
	struct LinkQueue* Pre;
	struct LinkQueue* Next;
}Node;

Node* Right;//创建左右指针 
Node* Left;

void InitQueue()
{
	Right=Left=(Node*)malloc(sizeof(Node));
	Left->Next=Left->Pre=NULL;
}

void Left_Insert(int Data)
{
	Node* P=(Node*)malloc(sizeof(Node));
	if(P==NULL) 
	{
		printf("申请内存失败");
		return;
	}
	P->Data=Data;
	
	P->Pre=NULL;     //尾插 
	P->Next=Left;
	Left->Pre=P;
	Left=P;
}

void Right_Insert(int Data)
{
	Node* P=(Node*)malloc(sizeof(Node));
	if(P==NULL)
	{
		printf("申请内存失败");
		return;
	}
	Right->Data=Data;
	
	Right->Next=P;  //尾插 
	P->Pre=Right;
	P->Next=NULL;
	Right=P;
} 
 
void Left_Remove() 
{
	if(Left==NULL||Left==Right)
	{
		printf("队列为空");
		return;
	}
	Node* P=Left;
	Left=Left->Next;
	Left->Pre=NULL;
	free(P);
} 

void Right_Remove()
{
	//注意删的是right->pre ,right->pre才是真正的最后一个
    //实际操作时只需要把right删掉,即可 
	if(Left==NULL||Left==Right)
	{
		printf("队列为空");
		return;
	}
	Node* P=Right;
	Right=Right->Pre;
	Right->Next=NULL;
	free(P);
}

void Show()
{
	if(Left==NULL||Left==Right)
	{
		printf("此表为空");
		return;
	}
	
	for(Node* i=Left;i!=Right;i=i->Next)
	{
		printf("%d ",i->Data);
	}
	printf("\n");
}
int main()
{
	InitQueue();
	
	Left_Insert(1);
	Left_Insert(2);
	Left_Insert(3);
	Left_Insert(4);
	Left_Insert(5);
	Right_Insert(6);
	Right_Insert(7);
	
	Show();
	
	Left_Remove();
	Right_Remove();
	Show();
	
	return 0;
}

  • 20
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值