本章主要介绍栈和队列的基本概念,存储结构、特点、相应算法以及经典应用。
栈的基本概念:栈是一种只能在一端进行插入或者删除操作的线性表。允许插入或者删除操作的一端称为栈顶(top),栈顶由一个栈顶指针来指示当前位置(顺序栈是栈顶元素在数组中下标,链栈是指向栈顶元素所在节点的指针),另一端是栈底,固定不变。栈的插入和删除操作就是入栈和出栈。
栈的特点:由其定义可以看出,栈的特点就是FILO(后进先出),后面入栈的元素先出栈。
大家想象:桌子上的一摞盘子,我们可以不断叠新盘子在上面,这就是入栈。最上面的盘子就是栈顶元素,垫在最下面的盘子就是栈底元素。我们只有把后叠上去的盘子取走,才能拿到下面的盘子,这就是FILO了。
栈的存储结构:顺序栈与链栈
顺序栈:对应线性表的顺序存储,数组实现。
链栈:对应线性表的链式存储,链表实现。
**栈的数学性质**n个编号元素以某种顺序进栈,并且可以在任意时刻出栈,获得出栈的编号排列数目为N,则:
前文已讲到,栈分为顺序栈和链栈,这里我们介绍一下它们的实现和对应算法。
【定义1】顺序栈的定义
#define maxSize 100 //定义顺序栈的最大长度为100
typedef struct SqStack {
int data[maxSize]; //存放元素的数组
int top; //栈顶指针
}Sqlist;
【算法1】顺序栈初始化,只需将栈顶指针置为-1
void initStack(SqStack &st) {
st.top = -1;
}
【算法2】顺序栈判断栈空
bool isEmpty(SqStack st) {
if (st.top == -1) {
return true;
}
return false;
}
【算法3】顺序栈进栈算法
/* push x into stack st.
* return true if success, false if failed.*/
bool Push(SqStack &st, int x) {
//The stack is full.
if (st.top == maxSize-1) {
return false;
}
++(st.top);
st.data[st.top] = x;
return true;
}
【算法4】顺序栈出栈算法
/* delete the stack top element and pass it to x
* return true if success, false if failed.*/
bool Pop(SqStack &st, int &x) {
if (st.top == -1) {
return false;
}
x = st.data[st.top];
--(st.top);
return true;
}
【定义2】链栈的定义
typedef struct LNode {
int data; //数据域
Struct LNode*next; //指针域
}LNode;
【算法5】链栈判断栈空算法
/* lst is the pointer pointed to the head node of the list */
bool isEmpty(LNode *lst) {
if (lst->next == null) {
return true;
}
return false;
}
【算法6】链栈进栈算法
/* push x into stack st.
* return true if success, false if failed.
* lst is the pointer pointed to the head node of the list.
* using head inserting method so that we can visit it through lst->next,
* the top element of the stack */
bool Push(LNode *lst, int x) {
LNode *p;
p = (Node*)malloc(sizeof(Node));
p->next = null;
p->data = x;
p->next = lst->next;
lst->next = p;
return true;
}
【算法7】链栈出栈算法
/* pop x from stack, and give it to x.
* return true if success, false if failed.
* lst is the pointer pointed to the head node of the list.*/
bool Pop(LNode *lst, int &x) {
LNode *p;
if (lst->next == null) {
return false;
}
p = lst->next;
x = p->data;
lst->next = p->next;
free(p);
return true;
}
接下来,我们看看两个栈的经典应用的例子。在这里我们直接使用已经实现封装好的栈(可以进栈、出栈以及判断栈空等操作),就不必纠结究竟是链栈还是顺序栈实现。
【算法8】编写算法,判断一个表达式中的括号是否正确匹配。
分析:括号是否匹配,无外乎给出的一个字符串,里面有着很多”(“以及”)”,每一个”)都会直接匹配离它最近的”(“,如果最后没有单独的两种括号剩下,那么就是匹配的。
因此,我们这样使用栈:当遇到每一个”(“的时候,我们将其进行进栈操作保存起来;每当遇到一个”)”,我们进行出栈操作,认为它与”)”进行匹配已经消耗掉了。
在判断的途中,如果遇到了一个”)”,我们应该进行出栈操作,但如果此时栈是空的,说明前面的”(“全部用完了,无法与此”)”匹配,肯定就是不匹配的。
在全部字符串判断后,如果栈刚好为空,则说明”(“与”)”恰好完全匹配,如果栈不为空,说明”(“多余”)”,不匹配(因为每一个”(“是与”)”严格对应的)。
/**
* return true if matches, false if not.
* @exp[] the array to store string
* @n the number of elements in the array
*/
bool match(char exp[], int n) {
Stack s;
int i;
for (i = 1; i <= n; ++i) {
if (exp[i] == '(') {
s.push('(');
}
else if (exp[i] == ')') {
if (s.isEmpty() == true) {
return false;
}
else {
s.pop();
}
}
}
if (s.isEmpty() == true) {
return true;
}
return false;
}
【算法9】求后缀表达式的数值,其中后缀表达式存储于一个字符数组exp中,exp的最后一个字符是’\0’,作为结束符,并且假设后缀表达式中的数字都只有一位。
分析:
形如:
中缀表达式:(a+b+c*d)/e
后缀表达式:abcd*++e
前缀表达式:/++ab*cde
int op(int a, char op, int b) {
if (op == '+') return a+b;
if (op == '-') return a-b;
if (op == '*') return a*b;
if (op == '/') {
if (b == 0) {
printf("error\n");
return 0;
}
else {
return a/b;
}
}
}
int calculate(char exp[]) {
Stack s;
int a, b, c, i;
char op;
for (i = 0; i != '\0'; ++i) {
if (exp[i] <= '9' && exp[i] >= '0') {
s.push(exp[i]);
}
else {
op = exp[i];
b = s.pop();
a = s.pop();
c = op(a, op, b);
s.push(c);
}
}
return s.top();
}
队列的基本概念:队列是一种在一端进行插入,在另一端进行删除操作的线性表。允许插入的一端称为队尾(rear),允许删除的一端称为队头(front)。队列的插入和删除操作就是入队和出队。
队列的特点:由其定义可以看出,队列的特点就是FIFO(先进先出),前面入队的元素先出队。
大家想象:超市里的收银台,一群人排队结账。先到的人先付帐,付账结束后从出口离开,紧接着后面的人跟着付账,这就是FIFO。
队列的存储结构:顺序队列与链队
顺序队列:对应线性表的顺序存储,数组实现。
链队:对应线性表的链式存储,链表实现。
前文已讲到,队列分为顺序队列和链队,这里我们介绍一下它们的实现和对应算法。
【定义3】顺序队列的定义
#define maxSize 100 //定义顺序队列的最大长度为100
typedef struct SqQueue {
int data[maxSize]; //存放元素的数组
int front; //队头指针
int rear; //队尾指针
}SqQueue;
为了防止出现“假溢出”的情况:rear和front指向同一地方(此时即使数组未满,也无法入队新的元素),我们设计了循环队列。循环队列必须损失一个存储空间,以此来区分队空与队满。
【算法10】顺序队列初始化。
void initQueue(SqQueue &sq) {
sq.rear = sq.front = -1;
}
【算法11】顺序队列判断队空
bool isEmpty(SqQueue sq) {
if (sq.rear == sq.front) {
return true;
}
return false;
}
【算法12】顺序队列判断队满
bool isFull(SqQueue sq) {
if (sq.front == (sq.rear+1)%maxSize) {
return true;
}
return false;
}
【算法13】顺序队列入队算法
/* push x into queue sq.
* return true if success, false if failed.*/
bool Push(SqQueue &sq, int x) {
//The queue is full.
if (sq.isFull() == true) {
return false;
}
sq.rear = (sq.rear+1) % maxSize;
sq.data[sq.rear] = x;
}
【算法14】顺序队列出队算法
/* delete the queue front element and pass it to x
* return true if success, false if failed.*/
bool Pop(SqQueue &sq, int &x) {
if (sq.isEmpty() == true) {
return false;
}
sq.front = (sq.front+1) % maxSize;
x = sq.data[sq.front];
return true;
}
【定义4】队的定义
typedef struct QNode {
int data; //数据域
Struct QNode*next; //指针域
}QNode;
typedef struct LiQueue {
QNode *front;
QNode *rear;
}LiQueue;
链队的操作实现考研基本没有考察,所以这里就不再详细介绍,有兴趣的同学可以自己去实现或者去查看C++和STL模板。