文章目录
栈和队列----栈详解
什么是栈?
栈的概念:
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
栈的特性:
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
栈的不同实现形式:
- 数组栈
即用顺序表实现的栈,性能方面优于链表栈。由于栈的特性,从栈中存取数据只能在栈顶,所以就不存在在栈底或栈的中间位置存取。将顺序表的头部做栈底,尾部做栈顶,栈存取数据更加方便。
- 链表栈
用链表实现的栈,性能比数组栈低。若用尾做栈顶,尾插尾删,要设计成双向链表,否则删数据效率低,若用链表头做栈顶,头插头删,就可以设计成单链表。
数组栈的实现和详解
思路:使用数组实现栈,定义一个可以动态存储的数组,可以定义一个整型变量来记录数组中的元素个数,也可以用它当作数组的下标,来实现对数据的快速存取。还需定义一个整型变量来记录数组的容量,来判断数组是否需要增容,最后还需要实现一个销毁函数,防止内存泄漏
栈的初始化和销毁
思路:将记录元素个数的变量,记录容量的变量,数组放在一个结构体类,方便对其操作。
typedef int DataType;
typedef struct Stack
{
DataType* arr;
int top;
int capacity;
}ST;
//初始化
void Stackinit(ST* p)
{
p->arr = NULL;
p->capacity = p->top = 0;
}
//栈的销毁
void Stackdestory(ST*p)
{
free(p->arr);
p->capacity = p->top = 0;
//可以不将p->arr置为NULL,函数调用完后,会自动销毁
}
进栈
思路:首先需判断是否已经满栈了,若满栈,需扩容,否则,直接插入。
//进栈
void Stackpush(ST*p,DataType x)
{
assert(p);
//当容量和数组元素个数相同时,即满栈了,需扩容。
if (p->capacity==p->top)
{
//三目运算符,若capacit为0,则赋值为4,若不为0,则直接扩容为原来的2倍。
int newcapacity = p->capacity == 0 ? 4 : sizeof(DataType) * 2;
p->arr = (DataType*)realloc(p->arr, sizeof(DataType) * newcapacity);
assert(p->arr);
//需将扩容后的值赋值给原来的capacity
p->capacity = newcapacity;
}
//利用p->top作为下标直接插入
p->arr[p->top++] = x;
}
出栈和获取栈顶元素
思路:进行这两个操作时,需先判断栈里面是否有数据,出栈只需将top的值减1就行,栈顶元素就是当前top的值减1作为下标,所对应的数组中的数据。
//出栈
void Stackpop(ST*p)
{
assert(p);
assert(Stackempty(p));
p->top--;
}
//取栈顶元素
DataType Stacktop(ST*p)
{
assert(p);
assert(Stackempty(p));
//数组的下标是从0开始的,top值为0时表示链表为空。
return p->arr[p->top-1];
}
获取栈中元素个数和栈的判空
思路:top的数值就是元素个数,根据top是否为0可判断栈是否为空
//栈判空
bool Stackempty(ST*p)
{
return p->top;
}
//取栈的元素个数
int Stacksize(ST*p)
{
assert(p);
return p->top;
}
打印栈中元素
思路:打印栈中元素不能直接从栈底遍历到栈顶,这样就不符合栈的性质了,只能先打印栈顶元素,然后再出栈,每次都打印栈顶元素,直到栈为空。
void test()
{
ST s;
//初始化栈
Stackinit(&s);
//进栈
Stackpush(&s, 1);
Stackpush(&s, 2);
Stackpush(&s, 3);
Stackpush(&s, 4);
Stackpush(&s, 5);
Stackpush(&s, 6);
//打印栈中元素个数
printf("%d ", Stacksize(&s));
//打印栈中的元素
while (Stackempty(&s))
{
//打印栈顶元素
printf("%d ", Stacktop(&s));
//出栈
Stackpop(&s);
}
//销毁栈
Stackdestory(&s);
}
链表栈的实现和详解
思路:跟数组栈的思路差不多,用头插和头删来进栈和出栈,需要定义一个结点来存储数据,还需定义个结构体来存放单链表的头指针,元素个数。容量不需要定义,单链表本来就是动态的。每次出栈和进栈都需要改变头指针的指向,需传二级指针。
栈的初始化和销毁
思路:初始化栈和数组栈的初始化差不多,栈的销毁需将结点和栈free,结点是动态内存开辟的空间,栈也是。防止内存泄漏。
typedef int DataType;
//定义存储数据的结点
typedef struct SNode
{
DataType data;
struct SNode* next;
}SN;
typedef struct SStackp
{
//头指针
SN* ptop;
int size;
}Sp;
//初始化栈
Sp* SStackinit()
{
Sp* p = (Sp*)malloc(sizeof(Sp));
assert(p);
p->ptop = NULL;
p->size = 0;
return p;
}
//销毁栈
void SStackdestory(Sp*p)
{
p->size = 0;
SN* cur = p->ptop;
while (cur)
{
SN* next = cur->next;
free(cur);
cur = next;
}
free(p);
}
进栈
思路:个人认为不需要判断是否满栈,毕竟是链表栈,要判断就得加一个记录容量的变量,偷懒不写。直接创建一个新的结点,然后将数据放进去,再链接到栈中,再让头指针指向新结点。
//进栈
void SStackpush(Sp**p,DataType x)
{
assert(p);
//创建新结点
SN* tem = (SN*)malloc(sizeof(SN));
assert(tem);
tem->data = x;
tem->next = NULL;
//栈的判空,若为空,直接将头指针指向第一个新结点
if (!SStackempty(*p))
{
(*p)->ptop = tem;
//size计数,方便判空和返回栈中元素数目
(*p)->size++;
}
else
{
tem->next = (*p)->ptop;
(*p)->ptop = tem;
//size计数,方便判空和返回栈中元素数目
(*p)->size++;
}
}
出栈和获取栈顶元素
思路:出栈要记得移动头指针和释放出栈结点的内存,栈顶元素即头指针指向的结点存储的数据,需要判空。
//出栈
void SStackpop(Sp**p)
{
assert(p);
assert(SStackempty(*p));
//记录被释放结点的下一个结点的位置
SN* tem = (*p)->ptop->next;
free((*p)->ptop);
(*p)->ptop = tem;
(*p)->size--;
}
//返回栈顶元素
DataType SStacktop(Sp*p)
{
assert(SStackempty(p));
return p->ptop->data;
}
获取栈中元素个数和栈的判空
思路:利用定义好的size很简单的搞定。
//栈判空
bool SStackempty(Sp*p)
{
return p->size;
}
//栈中元素数目
int SStacksize(Sp*p)
{
return p->size;
}
打印栈中元素
思路:和数组栈的打印一样。
void test()
{
Sp* s= SStackinit();
SStackpush(&s, 1);
SStackpush(&s, 2);
SStackpush(&s, 3);
SStackpush(&s, 4);
SStackpush(&s, 5);
SStackpush(&s, 6);
while (SStackempty(s))
{
printf("%d ", SStacktop(s));
SStackpop(&s);
}
SStackdestory(s);
}
总结
对栈这一块还是有点模糊,总觉得用链表实现栈那一块对一些变量的定义有点模糊,估计是对结构体不熟悉,也有可能单纯的不理解栈。栈的特性很重要,栈一般都是用数组来实现,比较方便。还得练练,回去看看基础!