上一节,我们用数组实现了队列。这一节,我们用链表实现。
下面看看用链表实现的队列吧:
//声明结点的结构体
struct Node{
int num; //具体数据
struct Node * next; //指向下一个结点的指针
};
struct Node head; //普通的结构体变量:头结点
//结构体的指针变量:指向链表的尾结点。
struct Node *tail = &head; //初始值为head的地址
下面我们需要实现几个方法:
0、依次显示所有数据
void show() //遍历,其实队列不需要这个功能
{
struct Node *currentNode; //当前结点的指针
currentNode = head.next; //当前结点的指针指向head结点的下一个结点
while(currentNode != NULL) //遍历
{
printf("%d, ", currentNode->num); //输出结点信息
currentNode = currentNode->next; //跳转到下一个结点
}
printf("\r\n");
}
1、读取队列的首元素,(不出列)
void peek()
{
if(head.next == NULL)
{
printf("队列为空\r\n");
return;
}
printf("队列的首元素: %d\r\n", head.next->num);
}
2、入列:在队列最后面插入一个数,时间复杂度为O(1)
void enqueue(int n) //在链表的指定位置插入新节点
{
//向操作系统申请一段内存空间,并强制转化成node的指针
struct Node * node = (struct Node *)(malloc(sizeof(struct Node)));
node->num = n; //为node结点赋值
node->next = NULL; //为node结点赋值
tail->next = node; //更换currentNode的next结点。也就是插入操作
tail = node;
head.num++;//head结点的num表示这个链表一共有几个结点
}
2.1、入列在队列最后面插入一个数,时间复杂度为O(N)。
这个方法时间复杂度高的原因是每次都是从head结点开始找到最后一个结点,然后再插入结点。遍历的过程时间复杂度为O(N),插入结点的时间复杂度为O(1),所以总的时间复杂度为O(N)。
void enqueue2(int n) //在链表的指定位置插入新节点
{
//向操作系统申请一段内存空间,并强制转化成node的指针
struct Node * node = (struct Node *)(malloc(sizeof(struct Node)));
node->num = n; //为node结点赋值
node->next = NULL; //为node结点赋值
struct Node * currentNode; //当前结点的指针
currentNode = &head; //当前结点的指针指向head结点
struct Node * temp; //临时结点的指针
while(currentNode->next != NULL) //遍历到最后一个节点
{
currentNode = currentNode->next;
}
currentNode->next = node; //更换currentNode的next结点。也就是插入操作
head.num++;//head结点的num表示这个链表一共有几个结点
}
3、出列:时间复杂度为O(1)
int dequeue() //删除链表的首元素 (不是head结点)
{
struct Node * temp; //结点的临时指针
//用temp指向要删除的index结点
temp = head.next;
int num = temp->num;
head.next = head.next->next; //删除index的结点
free(temp); //要回收内存
head.num--; //head结点的num表示这个链表一共有几个结点
return num;
}
4、int getSize() //获得链表中的结点个数
{
return head.num;
}
5、void release() //释放内存
{
struct Node * temp; //结点的临时指针
while(head.next != NULL) //当head结点的next不为NULL时循环
{
temp = head.next; //用temp记录head结点的子节点
head.next = head.next->next; //删除head结点的子节点
free(temp); //回收内存
}
}
下面是完整的代码:
#include <stdio.h>
#include <stdlib.h>
//声明结点的结构体
struct Node{
int num; //具体数据
struct Node * next; //指向下一个结点的指针
};
struct Node head; //普通的结构体变量:头结点
//结构体的指针变量:指向链表的尾结点。
struct Node *tail = &head; //初始值为head的地址
//0、依次显示所有数据
void show() //遍历,其实队列不需要这个功能
{
struct Node *currentNode; //当前结点的指针
currentNode = head.next; //当前结点的指针指向head结点的下一个结点
while(currentNode != NULL) //遍历
{
printf("%d, ", currentNode->num); //输出结点信息
currentNode = currentNode->next; //跳转到下一个结点
}
printf("\r\n");
}
//1、读取队列的首元素,(不出列)
void peek()
{
if(head.next == NULL)
{
printf("队列为空\r\n");
return;
}
printf("队列的首元素: %d\r\n", head.next->num);
}
//2、入列:在队列最后面插入一个数,时间复杂度为O(1)
void enqueue(int n) //在链表的指定位置插入新节点
{
//向操作系统申请一段内存空间,并强制转化成node的指针
struct Node * node = (struct Node *)(malloc(sizeof(struct Node)));
node->num = n; //为node结点赋值
node->next = NULL; //为node结点赋值
tail->next = node; //更换currentNode的next结点。也就是插入操作
tail = node;
head.num++;//head结点的num表示这个链表一共有几个结点
}
//2.1、入列在队列最后面插入一个数,时间复杂度为O(N)
void enqueue2(int n) //在链表的指定位置插入新节点
{
//向操作系统申请一段内存空间,并强制转化成node的指针
struct Node * node = (struct Node *)(malloc(sizeof(struct Node)));
node->num = n; //为node结点赋值
node->next = NULL; //为node结点赋值
struct Node * currentNode; //当前结点的指针
currentNode = &head; //当前结点的指针指向head结点
struct Node * temp; //临时结点的指针
while(currentNode->next != NULL) //遍历到最后一个节点
{
currentNode = currentNode->next;
}
currentNode->next = node; //更换currentNode的next结点。也就是插入操作
head.num++;//head结点的num表示这个链表一共有几个结点
}
//3、出列:时间复杂度为O(1)
int dequeue() //删除链表的首元素 (不是head结点)
{
struct Node * temp; //结点的临时指针
//用temp指向要删除的index结点
temp = head.next;
int num = temp->num;
head.next = head.next->next; //删除index的结点
free(temp); //要回收内存
head.num--; //head结点的num表示这个链表一共有几个结点
return num;
}
//4、获得链表中的结点个数
int getSize()
{
return head.num;
}
//5、释放内存
void release()
{
struct Node * temp; //结点的临时指针
while(head.next != NULL) //当head结点的next不为NULL时循环
{
temp = head.next; //用temp记录head结点的子节点
head.next = head.next->next; //删除head结点的子节点
free(temp); //回收内存
}
}
int main()
{
printf("入列1~5\r\n");
for(int i = 1; i <= 15; i++)
{
enqueue(i); //入列
}
printf("显示所有数据:");
show(); //依次显示所有数据
printf("出列: %d\r\n", dequeue());
printf("出列: %d\r\n", dequeue());
peek(); //查看当前首元素
enqueue(6); //入列
printf("入列6, 显示所有数据:");
show();
printf("一共有几个元素: %d\r\n", getSize());
printf("显示所有数据: ");
show(); //依次显示所有数据
release(); //释放内存
return 0;
}
运行结果与用数组实现的队列完全一样:
我们再尝试一次性输入15个数据会怎么样?结果也没有任何问题。从这里就可以看出链表 (实现的队列) 的优势了,只要总的内存空间足够大,我们就不需要在程序一开始设计很大的一个数组,只需要在使用的时候申请一个结点大小的内存空间即可。
接下来我们讨论一下链表实现的队列的入列、出列的效率。
如果我们每次都需要从头开始寻找最后一个结点,然后进行入列操作,这个过程的时间复杂度为O(N)。但是如果始终放一个指向尾结点的指针,那么入列操作的时间复杂度仅仅为O(1)。出列的操作始终为O(1)。
对比一下数组和链表实现的队列:
队列就介绍到这里,下一节开始我们要讲解栈了。