栈(Stack):只允许在一端进行插入或删除操作的线性表。
栈顶(Top): 线性表允许进行插入和删除的那一端
栈底(Bottom): 固定的,不允许进行插入和删除操作的另一端。
空栈:不含任何元素的空表。
栈的一个明显的操作特性:后进先出(Last In First Out, LIFO),故又称为后进先出的线性表。
InitStack(&S):初始化一个空栈S。
StackEmpty(S):判断一个栈是否为空,若栈S为空返回true,否则返回false。
Push(&S,x):进栈,若栈S未满,将x加入使之成为新栈顶。
Pop(&S,&x)出栈,若栈S非空,弹出栈顶元素,并用x返回。
GetTop(S,&x):读栈顶元素,若栈S非空,用x返回栈顶元素。
ClearStack(&S):销毁栈,并释放栈S所占的存储空间。
在解答算法题时,若题干没有做出限制,可以直接使用这些基本的操作函数。
栈的顺序存储称为顺序栈,它是利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶的位置。
栈的顺序存储类型可描述为:
#define MaxSize 50 //定义栈中元素的最大个数
typedef struct {
ElemType data[MaxSize]; //存放栈中元素
int top; //栈顶指针
}SqStack;
栈顶指针:S.top,初始时设置S.top=-1;栈顶元素:S.data[S.top]。
进栈操作:栈不满时,栈顶指针先加1,再送值到栈顶元素。
出栈操作:栈非空时,先取栈顶元素值,再将栈顶指针减1。
栈空条件:S.top==-1;栈满条件:S.top==MaxSize-1;栈长:S.top+1。
void InitStack(SqStack &S) {
S.top = -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.data[++S.top] = x; //指针先加1,再入栈
return true;
}
bool Pop(SqStack &S, ElemType &x) {
if (S.top == -1) //栈空,报错
return false;
x = S.data[S.top--]; //先出栈,指针再减1
return true;
}
bool GetTop(SqStack &S, ElemType &x) {
if (S.top == -1) //栈空,报错
return false;
x = S.data[S.top]; //x记录栈顶元素
return true;
}
注意:这里栈顶指针指向的就是栈顶元素,所以进栈时的操作是S.data[++S.top] = x;出栈时的操作时x = S.data[S.top--]。如果栈顶指针初始化为S.top=0,即栈顶指针指向栈顶元素的下一个位置,则入栈操作变为S.data[S.top++] = x;出栈操作变为x = S.data[--S.top]。相应的栈空、栈满条件也会发生变化。
利用栈底位置相对不变的特性,可以让两个顺序栈共享一个一维数据空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸。
两个栈的栈顶指针都指向栈顶元素,top0=-1时0号栈为空,top1=MaxSize时1号栈为空;仅当两个栈顶指针相邻(top1-top0=1)时,判断为栈满。当0号栈进栈时top0先加1再赋值,1号栈进栈时top1先减1再赋值;出栈时则刚好相反。
共享栈是为了更有效地利用存储空间,两个栈地空间相互调节,只有在整个存储空间被占满时才发生上溢。其存取数据的时间复杂度均为O(1),所以对存取效率没有什么影响。
采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。通常采用单链表实现,并规定所有操作都是在单链表的表头进行。这里规定链栈没有头结点,Lhead指向栈顶元素。
栈的链式存储类型可描述为:
typedef struct Linknode {
ElemType data; //数据域
struct Linknode *next; //指针域
}*LiStack; //栈类型定义
采用链式存储,便于结点的插入与删除。
假设以I和O分别表示入栈和出栈操作。栈的初态和终态均为空,入栈和出栈的操作序列可表示为仅由I和O组成的序列,可以操作的序列称为合法序列,否则称为非法序列。
①下面所示的序列中哪些是合法的?
A. IOIIOIOO B. IOOIOIIO C. IIIOIOIO D. IIIOOIOO
②通过对①的分析,写出一个算法,判定所给的操作序列是否合法。若合法,返回true,否则返回false(假定被判定的操作序列已存入一维数组中)。
【算法思想】依次逐一扫描入栈出栈序列(即由”I”和”O”组成的字符串),每扫描至任一位置均需检查出栈次数(即”O”的个数)是否小于入栈次数(“I”的个数),若大于则为非法序列。扫描结束后,再判断入栈和出栈次数是否相等,若不相等则不合题意,为非法序列。
int Judge(char A[])
//判断字符数组A中的输入输出序列是否是合法序列。如是,返回true,否则返回false。
{
int i = 0; //i为下标。
int j = 0; int k = 0; //j和k分别为I和字母O的的个数。
while (A[i] != '\0') //未到字符数组尾。
{
switch (A[i])
{
case 'I': j++; break; //入栈次数增1
case 'O': k++;
if (k > j) { printf("序列非法\n"); exit(0); }
}
i++; //不论A[i]是'I'或'O',指针i均后移。
} //while
if (j != k) {
printf("序列非法\n");
return false;
}
else {
printf("序列合法\n");
return true;
}
}
设单链表的表头指针为h,结点结构由data和next两个域构成,其中data域为字符型。试设计算法判断该链表的前n个字符是否中心对称。例如xyx,xyyx都是中心对称。
【算法思想】使用栈来判断链表中的数据是否中心对称。将链表的前一半元素依次进栈。在处理链表的后一半元素时,当访问到链表的一个元素后,就从栈中弹出一个元素,两个元素比较,若相等,则将链表中下一个元素与栈中再弹出的元素比较,直至链表到尾。这时若是空栈,则得出链表中心对称的结论;否则,当链表中的一个元素与栈中弹出元素不等时,结论为链表非中心对称,结束算法的执行。
int dc(LinkList L, int n) {
//h是带头结点的n个元素单链表,本算法判断链表是否是中心对称
char s[n/2]; int i; //s字符栈
LNode *p = L->next; //p是链表的工作指针,指向待处理的当前元素
for(i = 0; i<n / 2; i++) {