万字详解!C语言数据结构栈和队列

目录

栈和队列

一、数组栈

1.1栈的数据结构为

1.2栈的初始化

1.3进栈操作

1.4判断栈是否为空

1.5销毁栈

1.6完整代码

二、两栈共享空间

2.1数据结构为

2.2初始化栈 置空栈

2.3判断栈是否为空

2.4插入元素到栈中

2.5出栈

2.6完整代码

三、队列

3.1队列的数据结构

3.2队列的初始化

3.3 销毁队列

3.4 从尾部插入数据到队列

3.5判断队列是否为空

3.6从头开始删除一个结点

3.7返回前一个结点

3.8返回队尾的元素

3.9返回队列的大小

4.0完整代码


栈和队列

概述: 栈(Stack)是一种具有特定存储结构的数据结构,它遵循先进后出的原则,即最后进入的元素首先被访问和删除。栈的基本操作包括进栈,出栈,销毁栈。栈分为数组栈和链式栈。

一、数组栈

概述: 数组栈用数组来存数据

1.1栈的数据结构为

typedef int STDtype;
typedef struct stack
{
	STDtype* data;	// 指向栈数组的指针 定义为指针方便动态扩展内存
	int top;	    // 记录栈的顶点
	int capacity;	// 记录栈的容量
} ST;

1.2栈的初始化

初始化栈就是把容量数据顶点都置零

// 初始化栈
void InitStack(ST* ps)
{
	ps->capacity = 0;
	ps->data = NULL;
    // top 可以给0或者1 给0意味着top 指向栈顶数据的下一个,所以在用时先给数据再加一
	// top 等于 -1时 要先加1 再给数据 
	ps->top = 0;  // ps->top = -1;
}

1.3进栈操作

// 插入栈数据
// 1. 先判断是否满容量 满容量就扩容
// 2. 扩容完检测是否扩容成功,不成功打印错误信息并退出程序
// 3. 扩容成功指向新的空间 更新新的容量大小
// 4. 插入数据
void StackPush(ST* ps, STDtype e)
{
	assert(ps);
	if (ps->capacity == ps->top) // 当栈顶等于容量时说明 没有容量了
	{
		// 没有容量有两种情况 1. 没有一个容量,四个容量 2. 容量达到设定容量了
		// 申请新的容量 为原来容量的2 倍。
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDtype* temp = realloc(ps->data, sizeof(STDtype) * newCapacity);  // // 给ps->data 扩容,如果ps->data 为空则申请空间
		if (temp == NULL)
		{
			printf("realloc faill\n");
			exit(-1);
		}
		ps->data = temp;		    // 扩容成功 指向新的空间
		ps->capacity = newCapacity;  // 更新新的容量
	}

	ps->data[ps->top] = e;
	ps->top++;  
}

1.4判断栈是否为空

// 判断栈是否为空
bool StackEmpty(ST* ps)
{
	return ps->top == 0;
}

注意: 上面的函数返回值为bool C语言中要使用bool 需要包含头文件 #include

1.5出栈操作

// 出栈
// 1. 判断栈是否为空,为空则返回0
// 2. 不为空用变量记录栈顶数据
// 3. 栈顶指针减1
// 4. 返回开始记录的栈顶数据
STDtype StackPop(ST* ps)
{
	if (StackEmpty(ps))  // 判断是否为空
	{
		return 0;		// 为空返回0
	}
	else
	{
		STDtype temp = ps->data[ps->top-1];		  // 记录栈顶数据
		ps->top--;							    // 栈顶指针减减
		return temp;							// 返回记录的栈顶
	}
}

1.5销毁栈

// 销毁栈
// 1.释放指针
// 2.将指针和容量指向0
void StackDestory(ST* ps)
{
	// 释放指针维护的数据
	free(ps->data);
	// 数据指针和容量指向0
	// 数据指针将其设置为0 意味着将其指向空地址。
	ps->data = NULL;
	ps->capacity = 0;
}

1.6完整代码

#define _CRT_SECURE_NO_WARNINGS 1

#include 
#include 
#include 
#include 

typedef int STDtype;
typedef struct stack
{
	STDtype* data;
	int top;
	int capacity;
} ST;

// 初始化栈
void InitStack(ST* ps)
{
	ps->capacity = 0;
	ps->data = NULL;
	ps->top = 0;  // 这里初始化为0 指向栈
}

// 插入栈数据
// 1. 先判断是否满容量 满容量就扩容
// 2. 扩容完检测是否扩容成功,不成功打印错误信息并退出程序
// 3. 扩容成功指向新的空间 更新新的容量大小
// 4. 插入数据
void StackPush(ST* ps, STDtype e)
{
	assert(ps);
	if (ps->capacity == ps->top) // 当栈顶等于 容量时说明 没有容量了
	{
		// 没有容量有两种情况 1. 没有一个容量,四个容量 2. 容量达到设定容量了
		// 申请新的容量 为原来容量的2 倍。
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDtype* temp = realloc(ps->data, sizeof(STDtype) * newCapacity);  // // 给ps->data 扩容,如果ps->data 为空则申请空间
		if (temp == NULL)
		{
			printf("realloc faill\n");
			exit(-1);
		}
		ps->data = temp;		    // 扩容成功 指向新的空间
		ps->capacity = newCapacity; // 更新新的容量
	}

	ps->data[ps->top] = e;
	ps->top++;  
}

// 判断栈是否为空
bool StackEmpty(ST* ps)
{
	return ps->top == 0;
}

// 出栈
// 1. 判断栈是否为空,为空则返回0
// 2. 不为空用变量记录栈顶数据
// 3. 栈顶指针减1
// 4. 返回开始记录的栈顶数据
STDtype StackPop(ST* ps)
{
	if (StackEmpty(ps))  // 判断是否为空
	{
		return 0;		 // 为空返回0
	}
	else
	{
		STDtype temp = ps->data[ps->top-1];		// 记录栈顶数据
		ps->top--;							    // 栈顶指针减减
		return temp;							// 返回记录的栈顶
	}
}

// 销毁栈
// 1.释放指针
// 2.将指针和容量指向0
void StackDestory(ST* ps)
{
	// 释放指针维护的数据
	free(ps->data);
	// 数据指针和容量指向0
	// 数据指针将其设置为0 意味着将其指向空地址。
	ps->data = NULL;
	ps->capacity = 0;
}

void test01(void)
{
	ST ps;
	InitStack(&ps);			// 初始化栈

	// 栈插入数据
	StackPush(&ps, 1);
	StackPush(&ps, 2);
	StackPush(&ps, 3);
	StackPush(&ps, 4);
	
    // 若栈不为空则出栈并打印
	while (!StackEmpty(&ps))
	{
		printf("%d ", StackPop(&ps));
	}
	
	StackDestory(&ps);		// 释放栈空间
}

int main(void)
{
	test01();
	return 0;
}

二、两栈共享空间

概述: 当两个栈的空间需求相反时,如一个增加另一个减少,适合使用两栈共享空间

让一个栈的栈底为数组的始端即下标为0 处,另外一个为数组的末端即下标为n-1处。如果有元素就增加就像两端靠拢。

2.1数据结构为

// 定义两栈的数据结构
#define MAXSIZE 20
typedef int STDtype;
typedef struct Stack
{
	STDtype data[MAXSIZE];
	int top1;  // 指向栈1的顶指针
	int top2;  // 指向栈2的顶指针
}ST;

万字详解!C语言数据结构栈和队列_第1张图片

初始top1 可以使它指向-1 也可以使它指向0 指向-1 后再后面的运算时要先移动再运算,top2 也样的。

2.2初始化栈 置空栈

// 初始化栈
// 1. 把栈顶指针top1 指向-1 所以在进栈的时候要先移动指针再 进行赋值操作 
//      栈顶指针top2 指向的是MAXSIZE 进栈时也要先移动指针再赋值
void StackInit(ST* ps)
{
	ps->top1 = -1;
	ps->top2 = MAXSIZE;
}

// 置空栈
// 置空栈就是让他们的顶指针复位
void StackDestory(ST* ps)
{
	ps->top1 = -1;
	ps->top2 = MAXSIZE;
}

2.3判断栈是否为空

当top1 和 top2 都指向初始值时 此时栈为空

// 判断栈是否为空
// 为空返回1 
bool StackEmpty(ST* ps)
{
	return (ps->top1 == -1 && ps->top2 == MAXSIZE);
}

2.4插入元素到栈中

// 插入元素到栈中
// 参数1 栈的指针
// 参数2 插入的数据
// 参数3 给第几个栈插入
// 方法
// 1. 先判断容量是否满了 满了就退出
// 2. 判断是插入到哪个栈
// 3. 插入数据

void StackPush(ST* ps, STDtype e, int stackNumber)
{
	if (ps->top1 + 1 == ps->top2)
	{
		printf("栈满不能进栈\n");
		exit(-1);
	}
		
	if (stackNumber == 1)
	{
		ps->data[++ps->top1] = e; // 栈顶指针指向的是前一个 每次进栈时要先移动栈顶指针再赋值
	}
	else if (stackNumber == 2)
	{
		ps->data[--ps->top2] = e; // 栈顶指针指向的是后一个 每次进栈时要先移动栈顶指针再赋值
	}
}

2.5出栈

// 出栈
// 参数1 栈指针
// 参数2 第几个栈
// 方法
// 1. 先判断栈是否为空
// 2. 选择哪个栈出栈
//
STDtype StackPop(ST* ps, int stackNumber)
{
	if (StackEmpty(ps))
	{
		printf("栈为空不能出栈\n");
		return -1;	
	}
		
	if (1 == stackNumber)
	{
		return ps->data[ps->top1 --]; // 进完栈 栈顶指针指向的是当前的值 所以出完栈后再进行减减
	}
	else if (2 == stackNumber)
	{
		return ps->data[ps->top2++]; // 进完栈 栈顶指针指向的是当前的值 所以出完栈后再进行加加
	}
}

注意:出栈时的顶指针移动顺序和初始化时的值有关。

2.6完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 

// 定义两栈的数据结构
#define MAXSIZE 20
typedef int STDtype;
typedef struct Stack
{
	STDtype data[MAXSIZE];
	int top1;  // 指向栈1的顶指针
	int top2;  // 指向栈2的顶指针
}ST;

// 初始化栈
// 1. 把栈顶指针1 指向-1 所以在进栈的时候要先移动指针再 进行赋值操作 栈顶指针2 指向的是MAXSIZE 进栈时也要先移动指针再赋值
void StackInit(ST* ps)
{
	ps->top1 = -1;
	ps->top2 = MAXSIZE;
}

// 把栈置空
void StackDestory(ST* ps)
{
	ps->top1 = -1;
	ps->top2 = MAXSIZE;
}

// 判断栈是否为空
// 为空返回1 
bool StackEmpty(ST* ps)
{
	return (ps->top1 == -1 && ps->top2 == MAXSIZE);

}

// 插入元素到栈中
// 参数1 栈的指针
// 参数2 插入的数据
// 参数3 给第几个栈插入
// 方法
// 1. 先判断容量是否满了 满了就退出
// 2. 判断是插入到哪个栈
// 3. 插入数据

void StackPush(ST* ps, STDtype e, int stackNumber)
{
	if (ps->top1 + 1 == ps->top2)
	{
		printf("栈满不能进栈\n");
		exit(-1);
	}
		
	if (stackNumber == 1)
	{
		ps->data[++ps->top1] = e; // 栈顶指针指向的是前一个 每次进栈时要先移动栈顶指针再赋值
	}
	else if (stackNumber == 2)
	{
		ps->data[--ps->top2] = e; // 栈顶指针指向的是后一个 每次进栈时要先移动栈顶指针再赋值
	}
}

// 出栈
// 参数1 栈指针
// 参数2 第几个栈
// 方法
// 1. 先判断栈是否为空
// 2. 选择哪个栈出栈
//
STDtype StackPop(ST* ps, int stackNumber)
{
	if (StackEmpty(ps))
	{
		printf("栈为空不能出栈\n");
		return -1;	
	}
		
	if (1 == stackNumber)
	{
		return ps->data[ps->top1 --]; // 进完栈 栈顶指针指向的是当前的值 所以出完栈后再进行减减
	}
	else if (2 == stackNumber)
	{
		return ps->data[ps->top2++]; // 进完栈 栈顶指针指向的是当前的值 所以出完栈后再进行加加
	}
}

void test01(void)
{
	ST ps;
	// 初始化栈
	StackInit(&ps);

	// 给栈插入数据
	StackPush(&ps, 1, 1);
	StackPush(&ps, 2, 1);
	StackPush(&ps, 3, 1);

	StackPush(&ps, 3, 2);
	StackPush(&ps, 2, 2);
	StackPush(&ps, 1, 2);

	// 出栈
	printf("%d ", StackPop(&ps, 1));
	printf("%d ", StackPop(&ps, 1));
	printf("%d ", StackPop(&ps, 1));

	printf("%d ", StackPop(&ps, 2));
	printf("%d ", StackPop(&ps, 2));
	printf("%d ", StackPop(&ps, 2));

	// 清除栈
	StackDestory(&ps);
}

int main(void)
{
	/*test01();*/
	ST ps;
	test01();
	// 清除栈
	StackDestory(&ps);
	return 0;
}

三、队列

概述:只允许在一端插入数据 在另一端删除数据的线性表,队列具有先进先出

入队列:进行插入操作的一端称为队尾

出队列:进行删除一端为队头

3.1队列的数据结构

队列可以用数组来实现也可以用链来实现,但链实现的队列更加节省空间。如下是用链表实现的队列。

// 链队列的数据结构
typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QueueNode;

通常为了操作队列会定义一个头指针和尾指针,队列在此头结点创建,一般会用结构体封装一下。如下

// 定义头指针和尾指针
typedef struct Queue
{
	QueueNode* head;
	QueueNode* tail;
}Queue;

3.2队列的初始化

初始化队列就是初始化好头结点和尾结点。如下

// 初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}

3.3 销毁队列

依次释放队列的每个元素,和单链表的释放类似

// 释放结点信息
void QueueDestory(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;		// 定义工作指针从头开始释放结点
	while (cur != NULL)			    // 目前结点不为空就一直执行
	{
		QueueNode* next = cur->next; // 临时存储下一个结点
		free(cur);				    // 释放当前结点
		cur = next;				    // 指向存储的结点
	}
	pq->head = pq->tail = NULL;		 // 释放完给头尾结点指针置空
}

3.4 入队列

入队列就是从队尾插入数据

// 插入数据到队列
// 创建新结点 给结点数据赋值
// 链接新结点到队尾
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));		// 创建新的结点
	newnode->data = x;			// 给结点数据赋值
	newnode->next = NULL;		// 结点的尾置空
	if (pq->head == NULL)		// 当插入的是第一个结点时
	{
		pq->head = pq->tail = newnode;	// 头结点和尾结点 都指向这个节点
	}
	else
	{
		pq->tail->next = newnode;  // 尾结点的下一个指向新结点
		pq->tail = newnode;		  //  更新尾结点为刚刚插入的新结点
	}
 }

注意: 当第一次插入数据时要判断

3.5判断队列是否为空

判断头是否为空就能得出队列是否为空

// 判断是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}

3.6删除队头一个结点

删除的是头结点或头的后继结点,看队列定义的时候头结点是否也存储信息,如存储就从头开始删除,如不存储就从头的下一结点删除。

如下头结点也是有效结点,所以出队列就是删除现有头结点再更新头结点。

// 删除结点
void QueuePop(Queue* pq)
{
	assert(pq);
	// 不等于空 等于就保错 提示
	assert(!QueueEmpty(pq));
	QueueNode* next = pq->head->next; // 临时保存要删结点的下一结点
	free(pq->head);					  // 删除结点
	pq->head = next;				  // 更新头结点到下一结点
	if (pq->head == NULL)
	{
		pq->tail = NULL;  // 当删除完结点head 为空时 tail 也应该为空 不然tail 是一个野指针
	}
}

3.7返回前一个结点

前一个结点相当于队头的数据

// 返回队列前一个数
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->head->data;
}

注意:出队列就是先返回队头一个结点,然后再删除队头一个结点。即上面两步的结合组成出队列操作也可以把上面两步合为一步。

3.8返回队尾的元素

// 返回队列后一个数
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}

3.9返回队列的大小

队列的大小可以在定义的时候多定义一个变量来记录,也可以现用现求。如下是现用现求。

int QueueSize(Queue* pq)
{
	assert(pq);
	int size = 0;	// 定义记录变量
	QueueNode* cur = pq->head; // 定义工作结点	
	while (cur)		// 当当前结点为空时 退出
	{
		++size;		// 记录大小
		cur = cur->next;  // 更新节点
	}
	return size;	// 返回大小
}

3.10 完整代码

#define _CRT_SECURE_NO_WARNINGS 1

#include 
#include 
#include 

// 队列的数据结构
typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QueueNode;

// 定义头指针和尾指针
typedef struct Queue
{
	QueueNode* head;
	QueueNode* tail;
}Queue;

// 初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}
// 释放结点信息
void QueueDestory(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	while (cur != NULL)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

// 插入数据到队列
// 创建新结点 给结点数据赋值
// 
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	newnode->data = x;
	newnode->next = NULL;
	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}

}
// 判断是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}

// 删除结点前一个节点 从头结点开始删
void QueuePop(Queue* pq)
{
	assert(pq);
	// 不等于空 等于就保错 提示
	assert(!QueueEmpty(pq));
	QueueNode* next = pq->head->next;
	free(pq->head);
	pq->head = next;
	if (pq->head == NULL)
	{
		pq->tail = NULL;  // d当head 出完为空时 tail 也应该为空 不然tail 是一个野指针
	}
}

// 返回队列前一个数
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->head->data;
}

// 返回队列后一个数
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}
int QueueSize(Queue* pq)
{
	assert(pq);
	int n = 0;
	QueueNode* cur = pq->head;
	while (cur)
	{
		++n;
		cur = cur->next;
	}
	return n;
}

void test01()
{
	Queue q; // 创建头指针
	QueueInit(&q);

	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 5);

	QueuePop(&q);		// 删除一个节点 从前面开始删
	printf("%d \n", QueueSize(&q));

	while (!QueueEmpty(&q))	// 遍历队列 遍历一个删除一个 
	{
		QDataType front = QueueFront(&q);
		printf("%d ", front);
		QueuePop(&q);	// 删除一个队列
	}
		
	QueueDestory(&q); // 销毁队列
}

int main(void)
{
	test01();
	return 0;
}

运行结果

 4
2 3 4 5

上一篇:C语言数据结构单链表详解

下一篇:

你可能感兴趣的:(数据结构,数据结构,开发语言)