【数据结构-C语言】顺序队列,链式队列

1、基本概念

队列是最常见的概念,日常生活经常需要排队,仔细管擦和队列会发现,队列是一种逻辑结构,是一种特殊的线性表。特殊在只能在固定的两端操作线性表。

只要满足上述条件,那么这种特殊的线性表就会呈现一种“先进先出”的逻辑,这种逻辑就被称为队列。

由于约定了只能在线性表固定的两端进行操作,于是给队列这种特殊的线性表的插入删除,起个特殊的名称:

  • 队头:可以删除节点的一端
  • 队尾:可以插入节点的一端
  • 入队:将节点插入到队尾之后,函数名通常为enQueue()
  • 出队:将队头节点从队列中剔除,函数名通常为outQueue()
  • 取队头:取得队头元素,但不出队,函数名通常为front()

由于这种固定两端操作的简单约定,队列获得了“先进先出”的基本特性,如下图所示:

 2、顺序存储的队列:循环队列

与其他的逻辑结构类似,队列可以采用顺序存储形成循环队列,也可以采用链式存储形成链式队列。顺序存储的队列之所以被称为循环队列,是因为可以利用更新队头队尾的下标信息,来循环地利用整个数组,出队入队时也不必移动当中的数据。循环队列示意图如下所示:

从上述动图中可以观察到,需要牺牲至少数组中的一个存储位置,来区分循环队列中的满队和空队。满队和空队的约定如下:

  • 当front与rear相等时,队列为空
  • 当rear循环加一与front相等时,队列为满

与其他数据结构一样,管理循环队列除了需要一块连续的内存之外,还需要记录队列的总容量、当前队列的元素个数、当前队头、队尾元素位置,如果有多线程还需要配互斥锁和信号量等信息,为了便于管理,通常将这些信息统一于在一个管理结构体之中:

typedef struct seqQueue
{
    datatype *data; // 循环队列入口
    int capacity;   // 循环队列总容量
    int front;      // 循环队列队头元素下标
    int rear;       // 循环队列队头元素下标
}seqQueue;

注意:
循环队列中,需要牺牲一个存储位置来区分空队和满队

3、循环队列的代码实现

//初始化空队列
seqQueue* init(int size)
{
    seqQueue* queue = calloc(1,sizeof(seqQueue));
    if(queue != NULL)
    {
        queue->data = calloc(size + 1,sizeof(seqQueue));    //循环队列的大小要预留一个空位
        if(queue->data == NULL)
        {
            free(queue);
            return NULL;
        }
    
        //队列的总容量和队头队尾的赋值
        queue->data = size + 1;
        queue->front = queue->rear = 0;
    }
    

    return queue;
}

//判断队列是否为空
bool isEmpty(seqQueue* queue)
{
    return queue->front == queue->rear;    //初始化的时候front和rear都被初始化为0
}

//判断队列是否未满
bool isFull(seqQueue* queue)
{
    return (queue->rear + 1) % queue->size == queue->front;    //因为是循环队列,如果(队列下标+1)%队列总容量等于队列上标则说明循环队列已满
}

//入队
bool enQueue(seqQueue* queue,datatype data)
{
    //判断队列是否为满
    if(isFull(queue))
    {
        return false;
    }

    //队列数据所在下标赋值
    //队列的下标更新,因为是循环队列,如果下标超过队列大小需要对下标求余(循环特性)
    queue->data[queue->rear] = data;
    queue->rear = (queue->rear + 1) % queue->size;

    return true;
}

//出队
bool outQueue(seqQueue* queue,int* pm)
{
    //判断队列是否为空
    if(isEmpty(queue))
    {
        return true;
    }

    //存放队列的所在上标元素的数据
    //队列的上标更新,同样需要对上标进行求余(循环特性)
    *pm = queue->data[queue->front];
    queue->front = (queue->front + 1) % queue->size;
    
    return true;
}

//队列遍历
void show(seqQueue* queue)
{
    //判断队列是否为空
    if(isEmpty(queue))
		return;

    //根据循环队列的上下标特性来退出循环打印
	for (int i = queue->front; i != queue->rear; i = (i + 1) % queue->size)
	{
		printf("%d\t", queue->data[i]);
	}
	printf("\n");

}

4、循环队列的主函数测试

int main()
{
	//初始化一个空的循环队列
	struct seqQueue* queue = initQueue(10);
	if (queue != NULL)
	{
		printf("初始化空的循环队列成功!\n");
	}

	int n;
	while (1)
	{
		if (scanf("%d", &n) == 1)
		{	
			//入队
			if (!enQueue(queue,n))
			{
				printf("入队失败!\n");
				continue;
			}
		}
		else
		{
			//清空缓冲区中的非法输入
			while(getchar() != '\n');

			//出队
			int m;
			if (!outQueue(queue, &m))
			{
				printf("出队失败!\n");
				continue;
			}
		}
		show(queue);
	}
	return 0;
}

5、链式存储的队列:链式队列

链式队列的组织形式与链表无异,只不过插入删除被约束在固定的两端。为了便于操作,通常也会创建所谓管理结构体,用来存储队头指针、队尾指针、队列元素个数等信息:

从上图可以看到,链式队列主要控制队头和队尾,由于管理结构体中保存了当前队列元素个数size,因此可以不必设计链表的头节点,初始化空队列时只需要让队头队尾指针同时指向空即可。

// 链式队列节点
typedef struct node
{
    datatype data;
    struct node *next;
}node;

// 链式队列管理结构体
typedef struct
{
    node *front; // 队头指针
    node *rear;  // 队尾指针

    int  size;   // 队列当前元素个数
}linkQueue;

6、链式队列代码实现

//初始化空队列
linkQueue* initQueue()
{
    linkQueue* queue = calloc(1,sizeof(linkQueue));
    if(queue != NULL)
    {
        queue->front = NULL;
        queue->rear = NULL;
        queue->size = 0;
    }

    return queue;
}


//初始化队列一个节点
node* newNode(int data)
{
    node* new = mallpc(sizeof(node));
    if(new != NULL)    
    {
        new->data = data;
        new->next = NULL;
    }
}


//判断队列是否为空
bool isEmptt(linkQueue* queue)
{
    return queue->size == 0;    //队列的元素数量为0即为空
}

//入队
void enQueue(linkQueue* queue,node* new)
{
    //判断队列是否为空
    //队列空和不空插入节点的结构是不一样
    if(isEmpty(queue))
    {
        queue->front = new;    //当队列为空的时候,队列的尾应该为新插入的节点
    }
    else
        queue->rear->next = new;    //当队列不为空的时候,需要更新当前的队列尾,当前队列尾的下一个为新插入的节点

    queue->rear = new;    //更新队列的尾,是队列尾为新插入的节点
    queue->size++;        //队列的总容量+1
}    

//出队
node* outQueue(linkQueue* queue)
{
    //判断队列是否为空 
    if(isEmpty(queue))
    {
        return;
    }

    //创建临时节点指向当前队列的队首
    node* p = queue->front;

    //因为要执行出队的动作,需要更新队列的队首位置
    queue->front = p->next;
    p->next = NULL;

    //队列的元素个数-1
    queue->size--;

    return p;
}

//取队头元素
bool front(linkQueue *q, int data)
{
    //判断队列是否为空
    if(isEmpty(q))
        return false;
    
    //pm保存当前队列队首元素的数据域里面的数据
    *pm = q->front->data;

    return true;
}


//遍历队列
void show(linkQueue* queue)
{
    //判断队列是否为空
	if (isEmpty(queue))
		return;

    //链式队列退出的条件
	for (node* p = queue->front; p != NULL; p = p->next)
	{
		printf("%d\t", p->data);
	}
	printf("\n");
}

7、链式队列主函数测试

int main()
{
	//初始化一个空队列
	linkQueue* queue = initQueue();
	for (int i = 1; i <= 5; i++)
	{
		node* new = newNode(i);
		enQueue(queue,new);
	}

	show(queue);

	node* p;
	while (!isEmpty(queue))
	{
		p = outQueue(queue);
		if (p != NULL)
		{
			printf("出队节点:%d\t", p->data);
		}
	}
	return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值