《大话数据结构》--栈

358 阅读3分钟

栈的定义

栈是限定仅在表尾进行插入和删除操作的线性表

把允许插入和删除的一端称之为栈顶,另一端为栈底。不含任何数据元素的栈称之为空栈。栈又被称作后进先出(Last In First Out)的线性表,简称LIFO结构。

栈的插入操作称之为入栈栈的删除操作称之为出栈

栈的结构

既然栈是线性表的特例,线性表分为顺序存储和链式存储,那么栈的结构也分为顺序存储结构和链式存储结构。简称顺序栈链栈。

顺序栈

栈的顺序存储其实也是线性表顺序存储的简化,我们简称为顺序栈。

结构

private static final int MAXSIZE = 5;
static class SqStack {    
    int top; // 栈顶元素
    int[] data;    
    public SqStack() {        
        this.top = -1;   
        this.data = new int[MAXSIZE];    
    }
}

入栈Push

插入元素elementData为新的栈顶元素

static void push(SqStack s, int elementData) throws Exception {    
    if(s.top == MAXSIZE -1) {        
        throw new Exception("栈满");    
    }    
    s.top ++; // 栈顶指针增加1    
    s.data[s.top] = elementData; // 将新插入元素赋值到栈顶空间
}

出栈Pop

若栈不空,删除S栈顶元素,返回结果

static Integer pop(SqStack s) throws Exception {    
    if(s.top == -1) {        
        throw new Exception("栈是空的");    
    }    
    return s.data[s.top--];
}

顺序栈的出栈和入栈都没涉及到循环语句,时间复杂度均为O(1)

链栈

栈的链式存储结构,称之为链栈。

结构

static class Node{    
    int data;    
    Node next;    
    public Node() {    }
}
static class LinkStack {    
    Node top;    
    int count;    
    public LinkStack() {        
        this.count = 0;        
        this.top = new Node();    
    }
}

进栈

对于链栈的push操作,假设元素值为e,新结点s,top为栈顶指针。

static void push(LinkStack linkStack, Integer element) {    
    Node node = new Node();    
    node.data = element;    
    node.next = linkStack.top;   
    linkStack.top = node;    
    linkStack.count ++;
}

出栈

对于链栈的pop操作,需出栈的结点为ai,top为栈顶指针。

static Integer pop(LinkStack linkStack) throws Exception {    
    if(linkStack == null || linkStack.count == 0) {        
        throw new Exception("栈是空的");    
    }    
    Node remove = linkStack.top;    
    linkStack.top = remove.next;    
    linkStack.count --;    
    return remove.data;
}

链栈的出栈push和入栈pop都没涉及到循环语句,时间复杂度均为O(1)

栈的应用 --递归

把一个直接调用自己或通过一系列的调用语句间接调用自己的函数,称作递归函数。

分解问题是进栈(或者说压栈)的过程,解决问题是一个出栈的过程。

斐波那契数列实现

假如我们需要打印前40位斐波那契数列数。

传统方法

int[] a = new int[40];
a[0] = 1;
a[1] = 1;
System.out.print(a[0] + " ");
System.out.print(a[1] + " ");
for (int i=2; i<40; i++) {    
    a[i] = a[i-1] + a[i-2];    
    System.out.print(a[i] + " ");
}

递归

for (int i=0; i<40; i++) {    
    System.out.print(Fbi(i) + " ");
}
static int Fbi(int n) {    
    if(n < 2) {        
        return n == 0? 0:1;    
    }   
    return Fbi(n-1) + Fbi(n-2);
}

递归代码看着干净很多,但是理解就有点费脑子了。

递归和栈的关系

我们已经看到递归是如何执行它的前行和退回阶段的。递归过程退回的顺序是它前行顺序的逆序。在退回过程中,可能要执行某些动作,包括恢复在前行过程中存储起来的某些数据。这种存储某些数据,并在后面又以存储的逆序恢复这些数据,以提供之后使用的需求,显然很符合栈这样的数据结构。

简单的说,就是在前行阶段,对于每一层递归, 函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。