C语言实现数据结构——顺序队和链队

本人数据结构编写规则

① 在函数中判断结构体指针是否为空时采用assert函数,而不是用if语句判断。
② 函数的命名规则遵循:操作+结构类型名 的规则。例如 InitSqList 与DestroySqList。
③ 严蔚敏老师一书中很多运用了C++的语法,而我们是用C语言来实现,因此编写规则与书上会有很多不同,但是思路是一样的。例如用malloc代替new,free代替delete,引用与指针的区别等。
④ 本篇判断队满和队空需要用到bool变量

正文

和栈相反,队列是一种先进先出(First In First Out,FIFO)的数据结构,只允许在队尾插入,队头删除,不能插队。同样,队列也是线性表的一个子集。队列可以应用于排队、挂号系统,达到公平排队的目的。
队列可以用数组实现或用链表实现。数组实现又有两种方式:

  1. 在空队列中,Head表示数组首元素的下标,Tail表示数组末元素的下标。在非空队列中,数组插入元素后删除元素,队头之后的元素依次向队头挪动,因此每次插入都要挪动大量的数据,时间复杂度时O(N^2)。
  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;
}

结语

好像没什么好总结的

本篇完

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值