(C语言) 栈和队列

#1 栈

#1.1 自定义栈结构体

// 支持动态增长的栈
typedef int DataType;

typedef struct Stack
{
	DataType* arr;
	int top;		// 栈顶
	int capacity;  // 容量 
}Stack;

在后续的初始化函数中,如果选择将top初始化为0,那么实际上可以把top当作是size,
标示栈中数据的个数。

#1.2 栈基本功能函数

// 初始化栈 
void StackInit(Stack* ps);

// 入栈 
void StackPush(Stack* ps, DataType data);

// 出栈 
void StackPop(Stack* ps);

// 获取栈顶元素 
DataType StackTop(Stack* ps);

// 获取栈中有效元素个数 
int StackSize(Stack* ps);

// 检测栈是否为空 
int StackEmpty(Stack* ps);

// 销毁栈 
void StackDestroy(Stack* ps);

# 1.2.1 初始化栈

void StackInit(Stack* ps)
{
	ps->arr = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

很简单,没什么好说的。

# 1.2.2 入栈

void StackPush(Stack* ps, DataType data)
{
	assert(ps);

	//这个if用于判断栈是否需要扩容,第一次入栈也会进这个if
	if (ps->top == ps->capacity)
	{
		int Newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		ps->capacity = Newcapacity;
		DataType* tmp = (DataType*)realloc(ps->arr, ps->capacity * sizeof(DataType));
		if (tmp == NULL)
		{
			perror("realloc failed");
			exit(-1);
		}
		ps->arr = tmp;
	}
	//进行到这里就说明一定有足够的空间放入下一个数据,
	//top的位置就是原本最后一个数据的下一个位置,也是新数据需要进入的位置。
	ps->arr[ps->top] = data;
	ps->top++;
}

# 1.2.3 出栈

void StackPop(Stack* ps)
{
	assert(ps);
	
	//if用于温柔的判断栈是否已经空了,当然也可以用后面的判空函数
	if (ps->top != 0)
	{
		//所谓的删除数据,只用让个数减一就好。
		ps->top--;
	}
}

这里写的出栈并没有返回栈顶元素,需要搭配后面的获取栈顶元素来使用。

# 1.2.4 获取栈顶元素

本函数,以及接下来的两个函数,都只需要简单的返回值就可以。
之所以需要把简单的语句也封装为一个函数,是为了调用者能够方便的调用使用。

DataType StackTop(Stack* ps)
{
	assert(ps);
	
	return ps->arr[ps->top - 1];
}

# 1.2.5 获取栈中有效元素的个数

int StackSize(Stack* ps)
{
	assert(ps);

	return ps->top;
}

# 1.2.6 检测栈是否为空

int StackEmpty(Stack* ps)
{
	assert(ps);
	
	//这里是简便的写法
	//空,返回1.为真
	//非空,返回0,为假
	return ps->top == 0;
}

注意该函数作用是判断栈是否为空,如果为空,就是真的,返回的就是1。
不要逻辑上搞错了,错误的认为空就是假。

# 1.2.7 销毁栈

void StackDestroy(Stack* ps)
{
	assert(ps);

	free(ps->arr);
	ps->arr = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

也没什么好说的,记得free之后要置空。

#2 队列

#2.1 自定义队列结构体

typedef int DataType;

// 每个结点的结构
typedef struct QListNode
{
	struct QListNode* next;
	DataType data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* front;  //队列的出口
	QNode* rear;   //队列的入口
	int size;      //队列的大小,元素个数
}Queue;

队列的结构选择使用双指针,分别指向队头和队尾,用于出队列与进队列。

#2.2 队列基本功能函数

// 初始化队列 
void QueueInit(Queue* q);

// 队尾入队列 
void QueuePush(Queue* q, DataType data);

// 队头出队列 
void QueuePop(Queue* q);

// 获取队列头部元素 
DataType QueueFront(Queue* q);

// 获取队列队尾元素 
DataType QueueBack(Queue* q);

// 获取队列中有效元素个数 
int QueueSize(Queue* q);

// 检测队列是否为空
int QueueEmpty(Queue* q);

// 销毁队列 
void QueueDestroy(Queue* q);

# 2.2.1 初始化队列

void QueueInit(Queue* q)
{
	assert(q);

	q->front = NULL;
	q->rear = NULL;
	q->size = 0;
}

同初始化栈。

# 2.2.2 队尾入队列

void QueuePush(Queue* q, DataType data)
{
	assert(q);

	QNode* NewNode = (QNode*)malloc(sizeof(QNode));
	if (NewNode == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	//这里在创建新节点的同时顺便初始化
	NewNode->data = data;
	NewNode->next = NULL;
	//队列为空时加入第一个结点,将会进入这个if
	//让这个新结点同时成为队头和队尾
	if (q->front == NULL)
	{
		q->front = NewNode;
		q->rear = NewNode;
	}
	//否则就只需要将队尾的结点连接向这个新结点
	//然后这个新结点成为新的队尾
	else
	{
		q->rear->next = NewNode;
		q->rear = NewNode;
	}
	//记得个数加一
	q->size++;
}

由于是用链表而不是动态数组写的,和上文栈的差别在于不需要进行容量的判断。
只需要创建新的结点,进行连接就好。

# 2.2.3 队头出队列

void QueuePop(Queue* q)
{
	assert(q);
	//一个温柔的判断,如果队列中没有数据,则该函数什么也不做
	if (q->size != 0)
	{
		//新建变量保存一下队头的下一个结点
		//这样才能在free队头结点后找到新的队头
		QNode* next = q->front->next;
		free(q->front);
		q->front = next;
		
		//一般出队头是不需要操作队尾的
		//除非队列中只有一个结点。队头就是队尾
		//那么就需要将队尾也置为空,避免野指针的存在
		if (q->size == 1)
		{
			q->rear = NULL;
		}
		q->size--;
	}
}

# 2.2.4 获取队列头部元素

DataType QueueFront(Queue* q)
{
	//该函数有两种情况可能无法正确的返回
	//第一种是这个队列根本不存在
	//用第一个assert判断
	//第二种是队列存在,但是里面没有结点
	//用第二个assert判断
	assert(q);
	assert(q->front);
	
	//函数走到这里,说明队头元素肯定存在,返回就好
	return q->front->data;
}

# 2.2.5 获取队列队尾元素

有时也会有需要获取队尾元素的场合,所以也写上这个函数。
这并不是从队尾出队列,只是单纯的获取队尾元素。
队列只能头出。

DataType QueueBack(Queue* q)
{
	//两个assert的情况与获取队头元素一致
	assert(q);
	assert(q->rear);

	return q->rear->data;
}

# 2.2.6 获取队列中有效元素个数

int QueueSize(Queue* q)
{
	assert(q);

	return q->size;
}

# 2.2.7 检测队列是否为空

int QueueEmpty(Queue* q)
{
	assert(q);

	return q->size == 0;
}

# 2.2.8 销毁队列

由于队列是用链表结构写的,意味着无法像动态数组一样直接一次性全部释放空间。
必须一个结点一个结点的释放。

void QueueDestroy(Queue* q)
{
	assert(q);
	
	//定义cur用于指向当前要释放的结点
	//定义mext保存下一个待释放的结点
	QNode* cur = q->front;
	QNode* next = NULL;
	//不能直接初始化next为cur->next
	//因为队列中可能没有元素,此时cur为NULL,那么这步初始化就访问空指针了
	//应该如下另写一个判断
	if (cur != NULL)
	{
		next = cur->next;
	}
	//循环,每次释放一个结点
	while (cur != NULL)
	{
		free(cur);
		cur = next;
		//同样注意,在释放最后一个结点完成后,此时next为NULL
		//如果不加这个if而直接让next=next->next的话,又会造成空指针的访问
		if (next != NULL)
		{
			next = next->next;
		}
	}
	//释放犬奴吧结束后,置空
	q->front = NULL;
	q->rear = NULL;
	q->size = 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值