数据结构笔记整理第3章:栈和队列

第3章 栈和队列

本章内容

本章主要介绍栈和队列的基本概念,存储结构、特点、相应算法以及经典应用。

3.1 栈的基本概念与特点

栈的基本概念:栈是一种只能在一端进行插入或者删除操作的线性表。允许插入或者删除操作的一端称为栈顶(top),栈顶由一个栈顶指针来指示当前位置(顺序栈是栈顶元素在数组中下标,链栈是指向栈顶元素所在节点的指针),另一端是栈底,固定不变。栈的插入和删除操作就是入栈和出栈。

栈的特点:由其定义可以看出,栈的特点就是FILO(后进先出),后面入栈的元素先出栈。
大家想象:桌子上的一摞盘子,我们可以不断叠新盘子在上面,这就是入栈。最上面的盘子就是栈顶元素,垫在最下面的盘子就是栈底元素。我们只有把后叠上去的盘子取走,才能拿到下面的盘子,这就是FILO了。

栈的存储结构:顺序栈与链栈
顺序栈:对应线性表的顺序存储,数组实现。
链栈:对应线性表的链式存储,链表实现。

**栈的数学性质**n个编号元素以某种顺序进栈,并且可以在任意时刻出栈,获得出栈的编号排列数目为N,则:

3.2 栈的实现与相应算法

前文已讲到,栈分为顺序栈和链栈,这里我们介绍一下它们的实现和对应算法。

3.2.1 顺序栈的定义与算法

【定义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;
}
3.2.2 链栈的定义与算法

【定义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;
}

3.3 栈的经典应用

接下来,我们看看两个栈的经典应用的例子。在这里我们直接使用已经实现封装好的栈(可以进栈、出栈以及判断栈空等操作),就不必纠结究竟是链栈还是顺序栈实现。
【算法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();
}

3.4 队列的基本概念与特点

队列的基本概念:队列是一种在一端进行插入,在另一端进行删除操作的线性表。允许插入的一端称为队尾(rear),允许删除的一端称为队头(front)。队列的插入和删除操作就是入队和出队。

队列的特点:由其定义可以看出,队列的特点就是FIFO(先进先出),前面入队的元素先出队。
大家想象:超市里的收银台,一群人排队结账。先到的人先付帐,付账结束后从出口离开,紧接着后面的人跟着付账,这就是FIFO。

队列的存储结构:顺序队列与链队
顺序队列:对应线性表的顺序存储,数组实现。
链队:对应线性表的链式存储,链表实现。

3.5 队列的实现与相应算法

前文已讲到,队列分为顺序队列和链队,这里我们介绍一下它们的实现和对应算法。

3.5.1 顺序队列的定义与算法

【定义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;
}
3.5.2 链队的定义

【定义4】队的定义

typedef struct QNode {
    int data;           //数据域
    Struct QNode*next;  //指针域
}QNode;

typedef struct LiQueue {
    QNode *front;
    QNode *rear;
}LiQueue;

链队的操作实现考研基本没有考察,所以这里就不再详细介绍,有兴趣的同学可以自己去实现或者去查看C++和STL模板。

你可能感兴趣的:(数据结构)