本文大量饮用了相关公众号和博客图片和代码,本人只做整理总结,便于学习和使用,如有涉及到相关作者原创内容,请联系,本人将及时删除。
目录
线性结构: 有且只有一个根节点,且每个节点最多有一个直接前驱和一个直接后继的非空数据结构
非线性结构: 不满足线性结构的数据结构
队列
1、基本概念:
1.1 定义:
队列是指允许在一端进行插入,在另一端进行删除的线性表,又称“先进先出”的线性表
队列是一种特殊的线性结构,它只允许在队列的首部(head)进行删除操作,这称为出队,在尾部进行加入数据操作,称为入队;
队列这种数据结构非常容易理解,就像我们平时去超市买东西,在收银台结账的时候需要排队,先去排队的就先结账出去,排在后面的就后结账,有其他人再要过来结账,必须排在队尾不能在队中间插队。
1.2 概念补充:
1.2.1 数组
数组是一个有限的、类型相同的数据的集合,在内存中是一段连续的内存区域。 数组在访问操作方面有着独特的性能优势,因为数组是支持随机访问的,也就是说我们可以通过下标随机访问数组中任何一个元素,其原理是因为数组元素的存储是连续的,所以我们可以通过数组内存空间的首地址加上元素的偏移量计算出某一个元素的内存地址;
因为数组元素的连续性要求,所以导致数组在插入和删除元素的时候效率比较低。如果要在数组中间插入一个新元素,就必须要将要相邻的后面的元素全部往后移动一个位置,留出空位给这个新元素。
数组的缺陷:
1.一个数组中所有元素的类型必须一致;
2.数组元素个数必须事先制定并且制定后就不能更改
解决方法:
1.第一个缺陷靠结构体去解决,结构体允许其中的元素的类型不相同;
2.第二个缺陷:普通数组的大小不可以实时收缩或扩展,链表可以解决;
1.2.2 链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,一般用于插入与删除较为频繁的场景。
上图是“单链表”示例,链表并不需要数组那样的连续空间,它只需要一个个零散的内存空间即可,因此对内存空间的要求也比数组低。
链表的每一个节点通过“指针”链接起来,每一个节点有2部分组成,一部分是数据(上图中的Data),另一部分是后继指针(用来存储后一个节点的地址),在这条链中,最开始的节点称为Head,最末尾节点的指针指向NULL。
「 链表 」也分为好几种,上图是最简单的一种,它的每一个节点只有一个指针(后继指针)指向后面一个节点,这个链表称为:单向链表,除此之外还有 双向链表、循环链表 等。
双向链表与单向链表的区别是前者是2个方向都有指针,后者只有1个方向的指针。双向链表的每一个节点都有2个指针,一个指向前节点,一个指向后节点。双向链表在操作的时候比单向链表的效率要高很多,但是由于多一个指针空间,所以占用内存也会多一点。
循环链表:
其实循环链表就是一种特殊的单向链表,只不过在单向链表的基础上,将尾节点的指针指向了Head节点,使之首尾相连。
-
链表的访问
链表的优势并不在与访问,因为链表无法通过首地址和下标去计算出某一个节点的地址,所以链表中如果要查找某个节点,则需要一个节点一个节点的遍历
-
链表的插入与删除
也正式因为链表内存空间是非连续的,所以它对元素的插入和删除时,并不需要像数组那样移动其它元素,只需要修改指针的指向即可。
例如:删除一个元素E:
加入 一个元素:
2、 队列常见分类:
顺序队列—>顺序队列是用数组实现的队列
链式队列—>链式队列即用链表实现的队列
循环队列–> 循环队列是指队列是前后连成一个圆圈,它以循环的方式去存储元素,但还是会按照队列的先进先出的原则去操作
2.1顺序队列
用数组实现的队列,叫做 顺序队列:
用数组实现的思路是这样的:初始化一个长度为n的数组,创建2个变量指针front和rear,front用来标识队头的下标,而rear用来标识队尾的下标。因为队列总是从对头取元素,从队尾插入数据。因此我们在操作这个队列的时候通过移动front和rear这两个指针的指向即可。初始化的时候front和rear都指向第0个位置。
当有元素需要入队的时候,首先判断一下队列是否已经满了,通过rear与n的大小比较可以进行判断,如果相等则说明队列已满(队尾没有空间了),不能再插入了。如果不相等则允许插入,将新元素赋值到数组中rear指向的位置,然后rear指针递增加一(即向后移动了一位),不停的往队列中插入元素,rear不停的移动,如图:
当队列装满的时候,则是如下情况
当需要做出队操作时,首先要判断队列是否为空,如果front指针和rear指针指向同一个位置(即front==rear)则说明队列是空的,无法做出队操作。如果队列不为空,则可以进行出队操作,将front指针所指向的元素出队,然后front指针递增加一(即向后移动了一位),加入上图的队列出队了2个元素:
所以对于数组实现的队列而言,需要用2个指针来控制(front和rear),并且无论是做入队操作还是出队操作,front或rear都是往后移动,并不会往前移动。入队的时候是rear往后移动,出队的时候是front往后移动。
2.2 循环队列
在顺序队列中,当队尾指针已经到数组的上界,不能再有入队操作,但其实数组中还有空位置,这就叫做“假溢出”,解决假溢出的途径----采用循环队列。
( 当然也可以在数组尾部已满的这种情况下,去移动数据,把数据所有的元素都往前移动以填满前面的空间,释放出尾部的空间,以便尾部还可以继续插入新元素。但是这个移动也是消耗时间复杂度的。 )
循环队列就可以天然的解决这个问题,下面是循环队列的示意图:
可以看到,整个队列的入队和出队的过程,就是头指针 _front 和尾指针 _rear互相追赶的过程,如果 _rear追赶上了 _front就说明队满了(前提是相隔一个空闲单元),如果 _front追赶上了 _rear就说明队列空了。
新问题:
在循环队列中,空队特征是front = rear, 队满时也会有front = rear; 判断条件将出现二义性
解决方案有三种:
- 加设标志位,让删除动作使其为1,插入动作使其为0, 则可识别当前front == rear;
- 使用一个计数器记录队列中元素个数(即队列长度)
- 人为浪费一个单元,令队满特征为 front = (rear +1)%N—空闲单元法
这里分享2、3两种方法的C语言实现代码
- 方法2:计数器记录队列中元素的个数
#define mQBufNormal 0
#define mQBufFull 1
#define mQBufEmpty 2
typedef struct
{
UINT8 *pu8In;
UINT8 *pu8Out;
UINT8 *pu8Start;
UINT8 u8Length;
UINT8 u8Size;
}STQUEUE;
UINT8 u8QBuff[64] = {0};
STQUEUE stRxQueue;
void sQInit(STQUEUE *pstQ,UINT8 *pu8Start,UINT8 u8SizeTemp);
UINT8 su8QDataIn(STQUEUE *pstQ,UINT8 u8Data);
UINT8 su8QDataOut(STQUEUE *pstQ, UINT8 *pData);
/************************************************************************************
*Function name: sQInit *
*Parameters: pq: pointer to queue structure to be initialized *
* start:start address of ring buffer *
* size:the size of the ring buffer *
*Description: initialize a queue structure *
*************************************************************************************/
void sQInit(STQUEUE *pstQ,UINT8 *pu8Start,UINT8 u8SizeTemp)
{
pstQ->pu8In = pu8Start;
pstQ->pu8Out = pu8Start;
pstQ->pu8Start = pu8Start;
pstQ->u8Length = 0;
pstQ->u8Size = u8SizeTemp;
}
/************************************************************************************
*Function name: sQDataIn *
*Parameters: pq: pointer to queue structure to be initialized *
* data:the data to be inserted into the queue *
* option:how to deal with the data when the buffer is full *
* cQCoverFirst:cover the first data *
* cQCoverLast:cover the latest data *
*Returns: cQBufNormal:data has been inserted into the queue *
* cQBufFull:the buffer is full *
*Description: insert a data into the queue *
*************************************************************************************/
UINT8 su8QDataIn(STQUEUE *pstQ,UINT8 u8Data)
{
if(pstQ->u8Length >= pstQ->u8Size) // use (>=) just in case
{
return(mQBufFull);
}
else
{
*(pstQ->pu8In) = u8Data;
pstQ->u8Length++;
if(pstQ->pu8In >= pstQ->pu8Start + pstQ->u8Size - 1) // use (>=) just in case
{
pstQ->pu8In = pstQ->pu8Start;
}
else
{
pstQ->pu8In++;
}
return(mQBufNormal);
}
}
/************************************************************************************
*Function name: sQDataOut *
*Parameters: pq: pointer to queue structure to be initialized *
* pdata:the address to save the data *
*Returns: cQBufNormal:data has been inserted into the queue *
* cQBufEmpty:the buffer is empty *
*Description: Get a data from the queue *
*************************************************************************************/
UINT8 su8QDataOut(STQUEUE *pstQ, UINT8 *pData)
{
if(pstQ->u8Length == 0)
{
return(mQBufEmpty);
}
*pData = *(pstQ->pu8Out);
pstQ->u8Length--;
if(pstQ->pu8Out >= pstQ->pu8Start + pstQ->u8Size - 1) // use (>=) just in case
{
pstQ->pu8Out = pstQ->pu8Start;
}
else
{
pstQ->pu8Out++;
}
return(mQBufNormal);
}
- 空闲单元法
#include <stdio.h>
#include <stdlib.h>
#define Maxsize 10
typedef int dataType;
typedef struct
{
dataType *base;
int front;
int rear;
}CyQueue;
int create(CyQueue *q)
{
q->base=(dataType *)malloc(Maxsize*sizeof(dataType));
if(!q->base)
{
printf("Space allocation failed!\n");
return;
}
q->front=q->rear=0;
return;
}
int EnQueue(CyQueue *q,dataType value)
{
if((q->rear+1)%Maxsize==q->front)
{
printf("Cyclic Queue is Full!\n");
return;
}
q->base[q->rear]=value;
q->rear=(q->rear+1)%Maxsize;
printf("EnQueue Element is %d\n",value);
return;
}
int DeQueue(CyQueue *q,dataType *value)
{
if(q->front==q->rear)
{
printf("Cyclic Queue is Empty!\n");
return;
}
*value=q->base[q->front];
q->front=(q->front+1)%Maxsize;
printf("DeQueue Element is %d\n",*value);
return;
}
dataType getHead(CyQueue *q)
{
if(q->front==q->rear)
{
printf("Cyclic Queue is Empty! Unable to fetch Header of Cyclic Queue\n");
return;
}
return(q->base[q->front]);
}
int main()
{
CyQueue q;
dataType elem;
int i;
create(&q);
for(i=1;i<11;i++)
EnQueue(&q,i);
printf("The Header is %d\n",getHead(&q));
for(i=0;i<10;i++)
DeQueue(&q,&elem);
printf("The Header is %d\n",getHead(&q));
return 0;
}
2.3 链式队列
用链表实现的队列,叫做 链式队列: 链式队列就是一个操作受限的单向链表
简单描述一下上图的步骤
第一步:初始化队列(就是添加一个头节点在队列中),头结点不存储数据,使队首指针和队尾指针都指向头结点
第二步:入队(添加节点),使队尾指针指向头新建的节点,队首指针不变仍然指向头结点
出队时队首指针的位置是不变的,队首指针始终指向头节点,出队时头节点的指针域指向出队节点的后一节点,并将出队的节点用free()函数释放掉
注:
1.根据队列的入队,出队规则,设置头结点便于实现出队操作;
2.头指针front始终指向头结点,尾指针rear指向队列最后一个元素,length用于记录队列中元素个数,遍历操作会用到length。
#include <stdio.h>
#include <stdlib.h>
typedef int dataType;
typedef struct Node
{
dataType data;
struct Node *next;
}QueueNode;
typedef struct
{
QueueNode *front;
QueueNode *rear;
int length;
}LinkQueue;
int create(LinkQueue *q)
{
q->front=q->rear=(QueueNode *)malloc(sizeof(QueueNode));
q->front->next=NULL;
q->front->data=0;
q->length=0;
return;
}
int EnQueue(LinkQueue *q,dataType value)
{
QueueNode *p;
p=(QueueNode *)malloc(sizeof(QueueNode));
p->data=value;
p->next=NULL;
q->length++;
q->rear->next=p;
q->rear=p;
return;
}
int DeQueue(LinkQueue *q,dataType *value)
{
QueueNode *p;
if(q->front->next==NULL)
{
printf("LinkQueue is Empty!\n");
return;
}
p=q->front->next;
*value=p->data;
q->front->next=p->next;
q->length--;
printf("DeQueue Element is %d.\n",*value);
free(p);
return;
}
dataType getHead(LinkQueue *q)
{
if(q->front->next==NULL)
{
printf("LinkQueue is Empty!\n");
return;
}
return(q->front->next->data);
}
int traverse(LinkQueue *q)
{
QueueNode *p;
int i;
if(q->front->next==NULL)
{
printf("LinkQueue is Empty!\n");
return;
}
p=q->front->next;
printf("Head -> ");
for(i=0;i<q->length;i++)
{
printf("%d -> ",p->data);
p=p->next;
}
printf("NULL\n");
return;
}
int main()
{
LinkQueue q;
int i;
dataType value;
create(&q);
for(i=1;i<10;i++)
EnQueue(&q,i);
traverse(&q);
printf("\n");
for(i=1;i<10;i++)
{
DeQueue(&q,&value);
printf("First Element is %d.\n",getHead(&q));
traverse(&q);
printf("\n");
}
return 0;
}
3、小结
1.顺序、循环队列是用数组实现的,首指针在出队的时候移动,尾指针在入队的时候移动,需要考虑队列为空和队列为满的两种情况
2.链式队列是用链表实现的,首指针不移动始终指向头节点,尾指针在入队的时候移动,只考虑队列为空的情况(不用考虑满是因为链表长度在程序运行过程中可以不断增加,只要存储空间够malloc就能申请内存空间来存放节点)