1.栈
1.1概念

可以比作为羽毛球桶,弹夹
1.2栈的实现
1.2.1实现栈的选择
栈可以使用链表和数组来实现,哪种方法比较好
数组,我们对最后一个元素进行删除和插入,可以实现压栈和出栈
链表有两种,一种是单链表,另一种是双向链表
单链表我们可以把头结点当做栈顶,进行头插和头删
双向链表则没有什么限制,头插和头删,尾插,尾删都可以
那如何选择呢?
首先来看单双链表,我们既然能用单链表实现,就可以放弃双向链表,因为双向链表比单链表多一个指针,实现的时候就可以少维护一个指针,并且空间也优化了一点
其实单链表和数组的实现差不多,本章我们就只实现数组来实现栈,有兴趣的同学可以用链表来实现,而我们实现的栈和链表一样是动态的
1.2.2 准备工作
创建三个文件
1、头文件Stack.h 是来声明接口函数,定义顺序表,将几个公共用到的库函数集合起来 2、源文件Stack.c 是用来具体实现接口 3、源文件test.c 用于接口的测试工作 ,即体的使用场景
在这里提一嘴,有人可能会问创建文件为什么要用这个名字,例如头文件Stack.h不能换成XXX.h第一是可以让我们更好的辨认这个文件是干嘛的,Stack代表栈
第二后面学习STL库的时候,这个名字就很重要了,因此大家可以先按照我这个来写,同时在下面函数的实现中,有些函数名可能不知道什么意思,没关系,有印象就行。
1.2.3基础接口实现
1.2.3.1 创建栈的结构
typedef int STDataType;
typedef struct Stack
{STDataType* _a;int _top; // 栈顶位置int _capacity; // 容量
}Stack;
1.2.3.2 初始化栈
void StackInit(Stack* ps)
{void StackInit(Stack* ps)
{assert(ps);//断言不作叙述ps->_a = NULL;ps->_capacity = 0;ps->_top = 0;//ps->_top = -1;
}
}
top可以选择0或者-1,这两者有什么区别
之后处理栈顶元素的时候根据自己的top处理一下就行,这里我就用top=0来处理
1.2.3.3 入栈
void StackPush(Stack* ps, STDataType data)
{assert(ps);//扩容if (ps->_top == ps->_capacity){int new = ps->_capacity == 0 ? 4 : ps->_capacity * 2;STDataType* tmp = (STDataType*)realloc(ps->_a, new*sizeof(STDataType));if (tmp==NULL){perror("relloc");return;}ps->_a = tmp;ps->_capacity = new;}//插入数据top=0ps->_a[ps->_top] = data;ps->_top++;//top=1//ps->_a[++ps->_top] = data;
}
1.2.3.4 出栈
// 出栈
void StackPop(Stack* ps)
{assert(ps);ps->_top--;
}
1.2.3.5 获取栈顶元素
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{assert(ps);return ps->_a[ps->_top - 1];//top=0//return ps->_a[ps->_top ];top=-1;
}
1.2.3.6 获取栈中有效元素个数
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{assert(ps);return ps->_a[ps->_top ];//top=0//return ps->_a[ps->_top+1 ];top=-1;
}
1.2.3.7 检测栈是否为空
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps)
{assert(ps);//top=0if (ps->_top == 0){return 1;}else{return 0;}//top=-1;/*if (ps->_top == -1){return 1;}else{return 0;}*/
}
1.2.3.8 销毁栈
// 销毁栈
void StackDestroy(Stack* ps)
{assert(ps);free(ps->_a);ps->_a = NULL;ps->_capacity = 0;ps->_top = 0;
}
2.队列
2.1概念
只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有 先进先出入队列:进行插入操作的一端称为 队尾出队列:进行删除操作的一端称为队头

2.2队列的实现
2.2.1实现队列的选择
和栈一样,队列有三种选择:数组,单双链表,那根据队列的先进先出的原则,来看看那个结构比较好
先来看看数组,我们发现,每次数组进行出队列的操作时,数组内的元素总要往前移动,效率较低
而链表就不需要移动数据,出队列头删,入队列尾插就行了,同样在创建队列的时候只需要单链表就行了,可以节约一部分空间
2.2.2准备工作
和链表一样创建三个文件
1、头文件Queue.h 是来声明接口函数,定义顺序表,将几个公共用到的库函数集合起来 2、源文件Queue.h 是用来具体实现接口
3、源文件test.c 用于接口的测试工作 ,即体的使用场景
2.2.3基础接口实现
2.2.3.1创建队列结构
typedef int QDataType;
typedef struct QListNode
{struct QListNode* _pNext;QDataType _data;
}QNode;
当我们实现这个结构的时候,我们每次入队列的时候都要找到最后一个节点,我们写入队列函数时要传入三个参数,分别是头结点,尾结点,数据,尾插为什么要头结点那,当队列没有数据是,为NULL,就需要头结点和尾结点维护
、、
而且实现函数时,要传二级指针,这样才能改变实参,这样实现函数非常麻烦,此时我们可以用一个结构体来存储队列的头尾节点
typedef int QDataType;
typedef struct QListNode// 链式结构:表示队列
{struct QListNode* _pNext;QDataType _data;
}QNode;
typedef struct Queue// 队列的结构
{QNode * phead;//头结点QNode* ptail;//尾结点int size;//数据个数
}Queue;
这样我们在入队列函数传参数的时候就可以只用传这个结构体的指针,以及数据,这样不仅可以少传一个参数,又避免了二级指针
2.2.3.2 队尾入队列
// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{assert(q);//创建链表QNode* newnode= (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc");return;}newnode->_pNext = NULL;newnode->_data = data;//没有数据时if ( q->ptail == NULL){q->phead = q->ptail = newnode;}//有数据时else{q->ptail->_pNext = newnode;q->ptail = newnode;}q->size++;
}
2.2.3.3队头出队列
// 队头出队列
void QueuePop(Queue* q)
{assert(q);assert(q->size != 0);//只有一个节点时if (q->phead->_pNext == NULL){free(q->phead);q->phead = q->ptail = NULL;}//多个节点else{QNode* node = q->phead->_pNext;free(q->phead);q->phead = node;}q->size--;
}
2.2.3.4获取队列头部元素
// 获取队列头部元素
QDataType QueueFront(Queue* q)
{assert(q);assert(q->phead);return q->phead->_data;
}
2.2.3.5获取队列队尾元素
// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{assert(q);assert(q->ptail);return q->ptail->_data;
}
2.2.3.6获取队列中有效元素个数
int QueueSize(Queue* q)
{assert(q);return q->size;
}
2.2.3.7检测队列是否为空
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q)
{assert(q);return q->size == 0;
}
2.2.3.7销毁队列
// 销毁队列
void QueueDestroy(Queue* q)
{assert(q);QNode* cur = q->phead;while (cur){QNode* next = cur->_pNext;free(cur);cur = next;}q->size = 0;
}