[入门必看]数据结构3.1:栈

时间:2021-09-30 01:18:12


第三章 栈、队列和数组

小题考频:23
大题考频:4


3.1 栈

难度:☆☆

知识总览

3.1.1_栈的基本概念

[入门必看]数据结构3.1:栈

3.1.2_栈的顺序存储实现

[入门必看]数据结构3.1:栈

3.1.3_栈的链式存储实现

[入门必看]数据结构3.1:栈


3.1.1_栈的基本概念

[入门必看]数据结构3.1:栈
数据结构三要素——逻辑结构、数据的运算、存储结构(物理结构)

存储结构不同,运算的实现方式不同

[入门必看]数据结构3.1:栈
栈(Stack)只允许在一端进行插入或删除操作线性表

逻辑结构:与普通线性表相同
数据的运算:插入、删除操作有区别

stack整齐的一堆:
[入门必看]数据结构3.1:栈
从顶部放入,从顶部取出。

  • 重要术语:栈顶、栈底、空栈
    [入门必看]数据结构3.1:栈

栈的基本操作

回顾线性表的基本操作
InitList(&L):初始化表。构造一个空的线性表L,分配内存空间
DestroyList(&L):销毁操作。销毁线性表,并释放线性表L所占用的内存空间

创、销

ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e。
ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。

增、删

LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。

查、改(也要查)

其他常用操作:
Length(L):求表长。返回线性表L的长度,即L中数据元素的个数。
PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。
Empty(L):判空操作。若L为空表,则返回true,否则返回false。

  • 栈的基本操作
    InitStack(&S):初始化栈。构造一个空栈S,分配内存空间
    DestroyStack(&S):销毁栈。销毁并释放栈S所占用的内存空间

创、销

Push(&S,x):进栈,若栈S未满,则将x加入使之成为新栈顶
Pop(&S,&x):出栈,若栈S非空,则弹出栈顶元素,并用x返回。

增、删;
返回栈顶元素、删除栈顶元素

GetTop(S, &x):读栈顶元素。若栈S非空,则用x返回栈顶元素

查:栈的使用场景中大多只访问栈顶元素;
返回栈顶元素、不删除栈顶元素

其他常用操作:
StackEmpty(S):判断一个栈S是否为空。若S为空,则返回true,否则返回false。

栈的常考题型

  • Q:进栈顺序:
    a->b->c->d->e
    有哪些合法的出栈顺序?

  • A:n个不同元素进栈,出栈元素不同排列的个数为 1 n + 1 C 2 n n \frac{1}{n+1}C_{2n}^{n} n+11C2nn
    上述公式称为卡特兰(Catalan)数,可采用数学归纳法证
    明(不要求掌握)。
    1 5 + 1 C 10 5 = 10 × 9 × 8 × 7 × 6 6 × 5 × 4 × 3 × 2 × 1 = 42 \frac{1}{5+1}C_{10}^{5}=\frac{10\times 9\times 8\times 7\times 6}{6\times 5\times 4\times 3\times 2\times 1}=42 5+11C105=6×5×4×3×2×110×9×8×7×6=42

出栈和入栈穿插进行,会得到不同的出栈顺序


3.1.2_栈的顺序存储实现

[入门必看]数据结构3.1:栈

初始化操作

  • 顺序栈结构体:
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct{
	ElemType data[Maxsize] //静态数组存放栈中元素
	int top; //栈顶指针
} SqStack; //Sq:sequence - 顺序

void testStack( ){
	SqStack S; //声明一个顺序栈(分配空间)
	//……
}

栈顶指针top,记录数组下标,从0开始。

[入门必看]数据结构3.1:栈

  • 初始化顺序栈:
//初始化栈
void InitStack(SqStack &S){
	S.top = -1; //初始化栈顶指针
}

data[0]此时还没有元素,栈顶指针不能指向0

[入门必看]数据结构3.1:栈

  • 判断顺序栈空:
bool StackEmpty(SqStack S){
	if(S.top == -1) //栈空
		return true;
	else //不空
		return false;
}

进栈操作

  • 新元素入栈:
//新元素入栈
bool Push(Sqstack &S, ElemType x){
	if(S.top == MaxSize - 1) //栈满,报错
		return false; 
	S.top = S.top + 1; //指针先加1
	S.data[S.top] = x; //新元素入栈
	return true; 
}

由于top指针,永远指向此时栈顶元素位置;
当新元素入栈时,先让top指针+1,然后往top指针指向的位置放入新元素。

注意:

	S.top = S.top + 1; //指针先加1
	S.data[S.top] = x; //新元素入栈

等价于

S.data[++S.top] = x;

小心错误写法:
S.data[S.top++] = x;
这句等价于:
S.data[S.top] = x;
S.top = S.top + 1;


出栈操作

  • 元素出栈:
bool Pop(Sqstack &S, ElemType &x){
	if(S.top == -1) //栈空,报错
		return false; 
	x = S.data[S.top]; //栈顶元素先出栈
	S.top = S.top - 1; //指针再减1
	return true;
}

x加了引用符号,出栈操作中操作的x和函数调用者定义的变量x相同。

[入门必看]数据结构3.1:栈

注意:

	x = S.data[S.top]; //栈顶元素先出栈
	S.top = S.top - 1; //指针再减1

等价于

x = S.data[S.top--];

小心错误写法:
x = S.data[–S.top];
这句等价于:
S.top = S.top + 1;
x = S.data[S.top];


读栈顶元素操作

  • 读栈顶元素:
//读栈顶元素
bool GetTop(SqStack S, ElemType &x){
	if(S.top == -1) //栈空,报错
		return false;
	x = S.data[S.top]; //x记录栈顶元素
	return true;
}

和出栈操作唯一的区别是:
x = S.data[S.top–];
出栈操作会让top指针减减(S.top–)即往下移一位


另一种方式实现

#define MaxSize 10 //定义栈中元素的最大个数
typedef struct{
	ElemType data[Maxsize] //静态数组存放栈中元素
	int top; //栈顶指针
} SqStack; 

//初始化栈
void InitStack(SqStack &S){
	S.top = 0; //初始化栈顶指针
}

//判断栈空
bool StackEmpty(SqStack S){
	if(S.top == 0} //栈空
		return true;
	else //不空
		return false;
}


void testStack(){
	SqStack S; //声明一个顺序栈(分配空间)
	InitStack(S);
	//……
}
S.top = 0; //初始化栈顶指针

此时初始栈顶指针指向0
[入门必看]数据结构3.1:栈

此时top指针指向了下一个可以插入元素的位置。

  • 入栈操作步骤为:
    Step1:先把新的数据元素x放到top所指向的位置
    Step2:再让top指针+1
//进栈
S.data[S.top] = x;
S.top = S.top + 1;

等价于:

S.data[S.top++] = x;
  • 出栈操作步骤为:
    Step1:先让top指针-1
    Step2:再把top指向的元素传回去。
//出栈
S.top = S.top - 1;
x = S.data[S.top];

等价于:

x = S.data[--S.top];
  • 判断栈满:
S.top == MaxSize;

注意审题!top指针指向栈顶元素,还是栈顶元素后面的一个位置!两种方式实现代码是不一样的!

  • 顺序栈的缺点:栈的大小不可变

分配大片的存储空间,会导致内存资源的浪费,可以使用共享栈的方式来提高这一整片内存空间的利用率。


共享栈

——两个栈共享同一片空间
设置两个栈顶指针 - 0号栈、1号栈栈顶指针

  • 0号栈栈顶指针初始化为-1;
    往0号栈中放入数据元素,从下往上依次递增。
    [入门必看]数据结构3.1:栈

  • 1号栈栈顶指针初始化为MaxSize;
    往1号栈中放入数据元素,从上往下依次递增。
    [入门必看]数据结构3.1:栈

代码实现:

#define MaxSize 10 //定义栈中元素的最大个数
typedef struct{
	ElemType data[Maxsizel; //静态数组存放栈中元素
	int top0; //0号栈栈顶指针
	int top1; //1号栈栈顶指针
}ShStack;

//初始化栈
void InitStack(ShStack &S){
	S.top0 = -1; //初始化栈顶指针
	S.top1 = MaxSize;
}

//判断栈满的条件
S.top0 + 1 == S.top1

在逻辑上,实现了两个栈;
在物理上,两个栈共享同一片存储空间。
提高了内存资源的利用率


3.1.3_栈的链式存储实现

[入门必看]数据结构3.1:栈

单链表中
头结点的后插操作 - 对应:进栈
头结点的后删操作 - 对应:出栈
链栈和单链表类似。

  • 链栈的定义:
typedef struct Linknode{
 	ElemType data; //数据域
 	struct Linknode *next; //指针域
 }*LiStack; //栈类型定义

带头结点的初始化:
[入门必看]数据结构3.1:栈
不带头结点的初始化:
[入门必看]数据结构3.1:栈

两种方式初始化,判断栈空的方式不一样。


知识回顾与重要考点

3.1.1_栈的基本概念

[入门必看]数据结构3.1:栈

  • 插入和删除只能在栈顶进行,后进先出(LIFO)

3.1.2_栈的顺序存储实现

[入门必看]数据结构3.1:栈

  • 初始化时top = -1,可以让栈顶指针指向当前的栈顶元素;
    初始化时top = 0,也可以让栈顶指针指向下一个可以插入数据元素的位置。

  • 所有的基本操作都可以在 O ( 1 ) O(1) O(1)的时间复杂度内完成

  • 销毁一个顺序栈:
    在逻辑上把这个栈给清空,接下来回收这个栈所占用的内存资源
    逻辑上清空一个栈,只需要让top指针指向初始化的位置即可。
    本节中使用变量声明的方式分配相应的内存空间,没有使用malloc函数,所以函数运行结束后系统自动回收内存。
    [入门必看]数据结构3.1:栈


3.1.3_栈的链式存储实现

[入门必看]数据结构3.1:栈