本人数据结构编写规则
① 在函数中判断结构体指针是否为空时采用assert函数,而不是用if语句判断。
② 函数的命名规则遵循:操作+结构类型名 的规则。例如 InitSqList 与DestroySqList。
③ 严蔚敏老师一书中很多运用了C++的语法,而我们是用C语言来实现,因此编写规则与书上会有很多不同,但是思路是一样的。例如用malloc代替new,free代替delete,引用与指针的区别等。
④ 本篇判断队满和队空需要用到bool变量
正文
和栈相反,队列是一种先进先出(First In First Out,FIFO)的数据结构,只允许在队尾插入,队头删除,不能插队。同样,队列也是线性表的一个子集。队列可以应用于排队、挂号系统,达到公平排队的目的。
队列可以用数组实现或用链表实现。数组实现又有两种方式:
- 在空队列中,Head表示数组首元素的下标,Tail表示数组末元素的下标。在非空队列中,数组插入元素后删除元素,队头之后的元素依次向队头挪动,因此每次插入都要挪动大量的数据,时间复杂度时O(N^2)。
- 空队列中,Head与Tail同时表示数组首元素的下标,在非空队列中,当插入元素,Tail表示新元素的下标,当删除元素,Head表示新队头的下标。这两种方式在下图中呈现。
(左图为方式一,右图为方式二)
这两种方式都有极为明显的缺点:方式一时间复杂度太过大,效率低。方式二经过几次插入删除后会导致Head和Tail越界而出现溢出现象,但是实际上数组的空间并没有被完全使用,因此这种称谓“假溢出”。
本文介绍的顺序队是方式一,即使效率低,但是起码数组空间还能合理访问跟利用。
顺序队
函数声明
void InitLinkQueue(LinkQueue* q); 初始化
void DestroyLinkQueue(LinkQueue* q); 摧毁
void PrintLinkQueue(LinkQueue* q); 逐个打印
void EnLinkQueue(LinkQueue* q,LinkQDataType i); 入队
void DeLinkQueue(LinkQueue* q); 出队
LinkQDataType GetHeadLinkQueue(LinkQueue* q);取队头
LinkQDataType GetTailLinkQueue(LinkQueue* q);取队尾
int GetLenLinkQueue(LinkQueue* q); 计算长度
bool IsEmptyLinkQueue(LinkQueue* q); 判断队空
bool IsFullSqQueue(SqQueue* q);判断队满
结构体定义
typedef int SqQDataType;
#define IncNumSqQ 5
typedef struct SqQ
{
SqQDataType* data;
int head;
int tail;
int capacity;
}SqQueue;
顺序队初始化
void InitSqQueue(SqQueue* q)
{
assert(q);
q->data = (SqQDataType*)malloc(sizeof(int) * IncNumSqQ);
if (!q->data)
{
printf("初始化失败\n");
exit(-1);
}
q->head = 0;
q->tail = 0;
q->capacity = IncNumSqQ;
}
摧毁顺序队
void DestroySqQueue(SqQueue* q)
{
assert(q);
free(q->data);
q->data = NULL;
q->head = q->tail = q->capacity = 0;
}
打印顺序队
void PrintSqQueue(SqQueue* q)
{
assert(q);
if (IsEmptySqQueue(q))
{
printf("队空,打印失败\n");
return;
}
for (int begin = 0; begin < q->tail; begin++)
{
printf("%d ", q->data[begin]);
}
printf("\n");
}
入顺序队
void EnSqQueue(SqQueue* q, SqQDataType i)
{
assert(q);
if (IsFullSqQueue(q))
{
SqQDataType* tmp = (SqQDataType*)realloc(q->data, sizeof(int) * q->capacity + IncNumSqQ);
if (!tmp)
{
printf("扩容失败\n");
return;
}
q->data = tmp;
q->capacity += IncNumSqQ;
}
q->data[q->tail] = i;
q->tail++;
}
先赋值后再挪动tail
出顺序队
void DeSqQueue(SqQueue* q)
{
assert(q);
if (IsEmptySqQueue(q))
{
printf("队空,出队失败\n");
return;
}
for (int begin = 1;begin < q->tail;begin++)
{
q->data[begin - 1] = q->data[begin];
}
q->tail--;
}
把队头之后的所有元素往前挪动
取顺序队队头
SqQDataType GetHeadSqQueue(SqQueue* q)
{
assert(q);
if (IsEmptySqQueue(q))
{
printf("队空,获取队头元素失败\n");
return -1;
}
return q->data[q->head];
}
取顺序队队尾
SqQDataType GetTailSqQueue(SqQueue* q)
{
assert(q);
if (IsEmptySqQueue(q))
{
printf("队空,获取队尾元素失败\n");
return -1;
}
return q->data[q->tail - 1];
}
计算顺序队长度
int GetLenSqQueue(SqQueue* q)
{
assert(q);
if (IsEmptySqQueue(q))
{
printf("队空,获取队长度失败\n");
return -1;
}
return q->tail - q->head;
}
判断顺序队队空或队满
bool IsEmptySqQueue(SqQueue* q)
{
assert(q);
return (q->head == q->tail);
}
bool IsFullSqQueue(SqQueue* q)
{
assert(q);
return q->tail == q->capacity;
}
至此顺序队的操作就介绍完了。接着介绍链队。
链队相比单链表,可以多定义一个指向尾的尾指针。因为单链表尾插要遍历找到尾结点,但是尾删要找到尾结点的上一个结点,因此单链表没有必要单独为尾删这一步操作定义一个尾指针,但是链队不同,链队只在队尾插入队头删除,因此可以定义头指针和尾指针。
链队
函数声明
void InitLinkQueue(LinkQueue* q);
void DestroyLinkQueue(LinkQueue* q);
void PrintLinkQueue(LinkQueue* q);
void EnLinkQueue(LinkQueue* q,LinkQDataType i);
void DeLinkQueue(LinkQueue* q);
LinkQDataType GetHeadLinkQueue(LinkQueue* q);
LinkQDataType GetTailLinkQueue(LinkQueue* q);
int GetLenLinkQueue(LinkQueue* q);
bool IsEmptyLinkQueue(LinkQueue* q);
链表不会满,所以没有判满函数
结构体定义
typedef int LinkQDataType;
typedef struct LinkQueuenode
{
//这是记录每个结点的
LinkQDataType data;
struct LinkQueuenode* next;
}LinkQNode;
typedef struct LinkQueue
{
LinkQNode* head;
LinkQNode* tail;
}LinkQueue;
上一个结构体定义的是结点,下一个结构体定义的是指向整个链队的头尾指针。即可以LinkQueue定义一个链队,刚开始指针指向空,之后传参到函数中再用LinkQueuenode定义结点来完成各种操作。
这里定义链表的时候相比链表不用传二级指针,因为这里额外定义了头尾指针,而链表没有,要改变头尾指针的指向只需要传结构体(链队)的地址过去操作即可。
链队初始化
void InitLinkQueue(LinkQueue* q)
{
assert(q);
q->head = q->tail = NULL;
}
摧毁链队
void DestroyLinkQueue(LinkQueue* q)
{
assert(q);
LinkQNode* cur = q->head;
while (cur)
{
LinkQNode* next = cur->next;
free(cur);
cur = next;
}
q->head = q->tail = NULL;
}
打印链队
void PrintLinkQueue(LinkQueue* q)
{
assert(q);
LinkQNode* cur = q->head;
while (cur)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
入链队
void EnLinkQueue(LinkQueue* q, LinkQDataType i)
{
assert(q);
LinkQNode* newnode = (LinkQNode*)malloc(sizeof(LinkQNode));
if (newnode)
{
newnode->data = i;
newnode->next = NULL;
}
else
{
printf("\n开辟结点失败!\n");
return;
}
if (q->head == NULL)
{
q->head = q->tail = newnode;
}
else
{
q->tail->next = newnode;
q->tail = newnode;
}
}
出链队
void DeLinkQueue(LinkQueue* q)
{
assert(q);
assert(q->head && q->tail);
if (q->head->next == NULL)
{
free(q->head);
q->head = q->tail = NULL;
}
else
{
LinkQNode* cur = q->head;
q->head = q->head->next;
free(cur);
}
}
这里要单独处理只有一个结点的情况,如果没有单独处理,删除后head指向空,位置上head会在tail之后,这样指针关系就乱套了,下次再插入或删除都有隐患。
判断链队是否为空
bool IsEmptyLinkQueue(LinkQueue* q)
{
assert(q);
return q->head == NULL;
}
计算链队长度
int GetLenLinkQueue(LinkQueue* q)
{
assert(q);
int len = 0;
LinkQNode* cur = q->head;
while (cur)
{
cur = cur->next;
len++;
}
return len;
}
这里计算长度不能像顺序队一样用head和tail相减,因为结点在空间中是随机分配的,而不像数组是连续的。
取链队队头和队尾
LinkQDataType GetHeadLinkQueue(LinkQueue* q)
{
assert(q);
if (IsEmptyLinkQueue(q))
{
printf("队空,取队头元素失败\n");
return -1;
}
return q->head->data;
}
LinkQDataType GetTailLinkQueue(LinkQueue* q)
{
assert(q);
if (IsEmptyLinkQueue(q))
{
printf("队空,取队尾元素失败\n");
return -1;
}
return q->tail->data;
}
结语
好像没什么好总结的
本篇完