数据结构笔记(三)

一、定义

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

我们把允许插入和删除的一端称为栈顶, 另一端称为栈底,不含任何数据元素的栈称为空栈。栈又称为后进先出的线性表,简称 LIFO 结构。

栈的插入操作,叫作进栈,也称压栈、入栈。

栈的删除操作,叫作出栈,也有的叫作弹栈。

二、栈的抽象数据类型

/**
 * 栈的抽象数据类型
 *
 * @author 胡明皓
 */
public abstract class AbstractStack {

    Object[] data;
    
    int maxSize;
    
    int top;

    /**
     * 初始化操作,建立一个栈,并把数据压进栈中
     *
     * @param data 要压进去的数据
     */
    public AbstractStack(Object[] data) {
        this.data = data;
    }

    /**
     * 若栈存在,则进行销毁
     */
    abstract void destroyStack();

    /**
     * 将栈清空
     */
    abstract void clearStack();

    /**
     * 若栈为空,返回true,否则返回false
     *
     * @return true/false
     */
    abstract boolean isEmpty();

    /**
     * 若栈存在且非空,则返回栈顶元素
     *
     * @return 栈顶元素
     */
    abstract Object getTop();

    /**
     * 若栈存在,插入新元素data到栈中并成为栈顶元素
     *
     * @param data 新元素
     */
    abstract void push(Object data);

    /**
     * 删除栈中的栈顶元素,并返回其值
     *
     * @return 栈顶元素
     */
    abstract Object pop();

    /**
     * 返回栈的元素个数
     *
     * @return 返回栈的元素个数
     */
    abstract int length();

}

三、顺序存储结构及实现

3.1、栈的顺序存储结构

3.1.1、定义

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

3.1.2、进栈操作
(1)、思路

对于栈的插入,即进栈操作,其实是将新添加的数据元素压到栈中最后一个元素的上面。如下图所示

(2)、代码实现
/**
 * 插入元素 e 为新的栈顶元素
 */
void push(Object e) throw Exception {
    // 栈满了
    if (length() == (maxSize - 1)) {
        throw new Exception("栈满");
    }
    // 栈顶指针加一
    top++;
    // 将新元素赋值给栈顶空间
    data[top] = e;
}
3.1.3、出栈操作
(1)、思路

与进栈操作相反,进栈操作是将数据元素,存放到栈中 t o p + 1 top+1 top+1 的位置。而出栈操作则是将栈中第 top 个位置的元素删除并返回。

(2)、代码实现
/**
 * 若栈不空,则删除栈顶元素,并返回元素对象
 */
Object pop() throw Exception {
    if (top == -1) {
        throw new NullPointException("空栈");
    }
    Object data = data[top];
    top--;
    return data;
}

四、两栈共享空间

如果有两个相同类型的栈,为其各自单独开辟了数组空间,极有可能是第一个栈已经满了,再进栈就溢出了,而另一个栈还有很多存储空间空闲。所以,完全可以用一个数组来存储两个栈。

做法如下图

4.1、思路

两个栈在数组的两端,向中间靠拢。top1和top2分别是栈1和栈2的栈顶指针,只要这两个指针不见面,两个栈就可以一直使用。

class DoubleStack {
    Object[] data = new Object()[MAX_SIZE];
    int top1 = -1;
    int top2 = MAX_SIZE + 1;
}

4.2、插入和删除

4.2.1、插入
/**
 * 压栈
 *
 * @param e 压进去的元素
 * @param stackIndex 要压进去的栈索引
 * @throws Exception 异常
 */
void push(Object e, int stackIndex) throws Exception {
    // 如果第一个栈的栈顶指针再加1后等于第二个栈的栈顶指针了,就表示这两个栈已经满了
    if ((top1 + 1) == top2) {
        throw new Exception("");
    }
    // 如果栈的索引是1则表示将元素添加到栈1顶,否则则添加到栈2顶
    if (stackIndex == 1) {
        data[top1++] = e;
	} else if (stackIndex == 2) {
        data[top2--] = e;
    }
}
4.2.2、删除
    /**
     * 弹栈
     *
     * @param stackIndex 要压进去的栈索引
     * @return 栈顶的元素
     * @throws Exception 异常
     */
    Object pop(int stackIndex) throws Exception {
        if (stackIndex == 1) {
            if (top1 == -1) {
//                说明栈1已经是空栈
                throw new Exception("");
            }
//            将栈1的栈顶元素出栈
            return this.data[top1--];
        } else if (stackIndex == 2) {
            if (top2 == MAX_SIZE) {
//                说明栈2已经是空栈
                throw new Exception("");
            }
//            将栈2的栈顶元素出栈
            return this.data[top2++];
        }
        return null;
    }

五、栈的链式存储结构及实现

5.1、定义

栈的链式存储结构,简称为链栈

class LinkStackNode {
    Object data;
    LinkStackNode next;
}

class LinkStack {
    LinkStackNode top;
    int count;
}

5.2、进栈

void push (Object e) {
	// 入栈元素分配空间
    Element elem = new Element();
    // 将元素放入
    elem.data = obj;
    // 元素指针指向当前top所指元素
    elem.next = top;
    // 修改top指针
    top = elem;
}

5.3、弹栈

Object pop() {
	if(this.isEmpty()){
    	System.out.println("退栈失败,栈为空!");
        return null;
	}
	//取出对象的值
	Object obj = top.data;
	//修改top指针
	top = top.next;
	//返回元素
    return obj;
}

六、递归

6.1、斐波那契数列实现

如果兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子。假设所有兔子都不死,那么一年后可以繁殖多少对兔子

6.1.2、常规
/**
 * 只需要打印出前40次繁殖后的数列
 */
public static void main(String[] args) {
    int i;
    int[] a = new int[40];
    a[0] = 0;
    a[1] = 1;
    for (i = 2; i < 40; i++) {
        a[i] = a[i - 1] + a[i - 2];
    }
    System.out.println(Arrays.toString(a));
}
6.1.3、递归
static int fbi(int i) {
	if (i < 2) {
    	return i == 0? 0 : 1;
	}
	return fbi(i - 1) + fbi(i - 2);
}

/**
* 只需要打印出前40次繁殖后的数列
*/
public static void main(String[] args) {
	int[] a = new int[40];
	for (int i = 0; i < 40; i++) {
	    a[i] = fbi(i);
	}
	System.out.println(Arrays.toString(a));
}

七、后缀(逆波兰)表示法

7.1、定义

一种不需要括号的后缀表达法,我们也把它称为逆波兰表示。

9 + ( 3 − 1 ) ∗ 3 + 10 / 2 9 + (3 - 1) * 3 + 10 / 2 9+(31)3+10/2 —> (9 3 1- 3 * + 10 2 / +)

后面的表达式就是 后缀表达式

7.2、计算结果

后缀表达式:9 3 1- 3 * + 10 2 / +

1、初始化一个空栈。此栈用来对要运算的数字进出使用。

2、后缀表达式中前三个都是数字,所以 9、 3、 1 进栈。

3、接下来是 “ − - ”,所以将栈中的 1 出栈作为被减数,3 出栈作为减数,并运算 3 − 1 3 - 1 31 得 2 ,再将2进栈。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6TB3deFT-1593476657323)(http://www.wuliang9455.top/blog/assets/img/minus(3, 1)].jpg)

4、接着是数字 3

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0uB9rLoz-1593476657323)(http://www.wuliang9455.top/blog/assets/img/insertStack(3)].jpg)

5、后面是 “ $ * $ ”,也就意味着栈中 3 和 2 出栈,2 与 3 相乘得6,并将 6 进栈。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wPS970ls-1593476657324)(http://www.wuliang9455.top/blog/assets/img/multiply(3, 2)].jpg)

6、下面是 “ $ + $ ”,所以 6 和 9 出栈,9 与 6 相加得 15 ,将 15 进栈。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dx2mLzla-1593476657325)(http://www.wuliang9455.top/blog/assets/img/addition(9, 6)].jpg)

7、接着是10 与 2 两数字进栈。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vDHmDPHI-1593476657325)(http://www.wuliang9455.top/blog/assets/img/insertStack(10, 2)].jpg)

8、接下来是符号 “ $ / $ ” ,因此,栈顶的 2 与 10 出栈,10 与 2 相除得5,将 5 进栈。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QxxKAaQy-1593476657326)(http://www.wuliang9455.top/blog/assets/img/division(10, 2)].jpg)

9、最后一个是符号 “ $ + $ ”,所以15 与 5 出栈并相加,得 20,将 20 进栈。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5tPOhPdn-1593476657327)(http://www.wuliang9455.top/blog/assets/img/addition(15, 5)].jpg)

10、结果是 20 出栈,栈变为空。

7.3、中缀表达式转后缀表达式

中缀表达式:“ $ 9 + (3 - 1) * 3 + 10 / 2 $ ” 转化为后缀表达式 “ 9 3 1- 3* + 10 2/ + ”

7.3.1、规则

从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号,则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。

7.3.2、步骤

1、初始化一空栈,用来对符号进出栈使用。

2、第一个字符是数字 9,输出 9,后面是 “ + ”,进栈。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MO6nCw26-1593476657327)(http://www.wuliang9455.top/blog/assets/img/insertStack(+)].jpg)

3、第三个字符是 “ ( ”,依然是符号,因其只是左符号,还未配对,故进栈。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BWki5Hzp-1593476657327)(http://www.wuliang9455.top/blog/assets/img/insertStack(()].jpg)

4、第四个字符是数字 3 ,输出,总表达式为 9 3 ,接着是 “ $ - $ ”,进栈。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-liakWF1W-1593476657328)(http://www.wuliang9455.top/blog/assets/img/insertStack(-)].jpg)

5、接下来是数字 1 , 输出,总表达式为 9 3 1 ,后面是符号 “ $ ) $ ” ,此时,我们需要去匹配此前的 “ $ ($ ” ,所以栈顶依次出栈,并输出,直到 “ $ ( $ ” 出栈为止。此时左括号上方只有 “ $ - $ ”,因此输出 “ $ - $ ”。总的表达式为 9 3 1-。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DruNjWNf-1593476657328)(http://www.wuliang9455.top/blog/assets/img/insertStack(+)].jpg)

6、接着是数字 3 ,输出,总的表达式为:9 3 1- 3。紧接着符号 “ $ * $ ”,因为此时的栈顶符号为 “ $ + $ ”,优先级低于 “ $ * $ ” ,因此不输出,“ $ * $ ” 进栈。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n8HCbd9B-1593476657329)(http://www.wuliang9455.top/blog/assets/img/insertStack(multiply)].jpg)

7、之后是符号 “ $ + $ ”,此时当前栈顶元素 “ $ * $ ” 比这个 “ $ + $ ” 的优先级高,因此栈中元素出栈并输出(没有比 “ $ + $ ” 号更低的优先级,所以全部出栈),总表达式为 9 3 1 - 3 * + 。然后将当前这个符号 “ $ + $ ” 进栈。也就是说,前 6 张图的栈底 “ $ + $ ” 是指中缀表达式中开头 9 后面的那个 “ $ + $ ” ,而下图中的 “ $ + $ ” 是指 “ 9 + ( 3 − 1 ) ∗ 3 + 10 / 2 9 + (3-1) *3 + 10/2 9+(31)3+10/2 ” 中的最后一个 “ $ + $ ” 号。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PniRgyU2-1593476657329)(http://www.wuliang9455.top/blog/assets/img/insertStack(+)].jpg)

8、紧接着是数字10,输出,总表达式变为 9 3 1 - 3 * + 10。后是符号 “ $ / $ ” ,所以 “ $ / $ ” 进栈

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ETTXWDTw-1593476657330)(http://www.wuliang9455.top/blog/assets/img/insertStack(divide)].jpg)

9、最后一个数字 2 ,输出,总的表达式为 9 3 1 - 3 * + 10 2。

10、因已经到最后,所以将栈中符号全部出栈并输出,最后表达式为 9 3 1 - 3 * + 10 2 / +。

7.3.3、总结

1、将中缀表达式转化为后缀表达式(栈用来进出运算的符号)。

2、将后缀表达式进行运算得出结果(栈用来进出运算的数字)。

7.3.4、代码实现
/**
 * @author Administrator
 */
public class Main {

    static Stack<String> op = new Stack<>();

    /**
     * 计算每一步运算的结果
     *
     * @param op 运算符号
     * @param f1 被运算的数
     * @param f2 运算的数
     * @return 运算结果
     */
    public static Float getCurrentResult(String op, Float f1, Float f2) {
        if ("+".equals(op)) {
            return f2 + f1;
        } else if ("-".equals(op)) {
            return f2 - f1;
        } else if ("*".equals(op)) {
            return f2 * f1;
        } else if ("/".equals(op)) {
            return f2 / f1;
        } else {
            return (float) -0;
        }
    }

    /**
     * 计算反向波兰表达式的值
     *
     * @param rp 反向波兰语表达
     * @return 表达的结果
     */
    public static float calRp(String rp) {
        Stack<Float> v = new Stack<>();
        String[] arr = rp.split(" ");
        for (String ch : arr) {
            // 如果是数字,则推入堆栈
            if (String.valueOf(ch).matches("\\d*")) {
                v.push((float) (Integer.parseInt(ch)));
            } else {
                // 如果是运算符,则使用堆栈中前2个操作数计算结果,并将结果压入堆栈
                float result = getCurrentResult(ch, v.pop(), v.pop());
                System.out.println(result);
                v.push(result);
            }
        }
        return v.pop();
    }

    /**
     * 从中缀到后缀
     *
     * @param s - 中缀表达式
     * @return 后缀表达式
     */
    public static String getRp(String s) {
        String[] arr = s.split(" ");
        StringBuilder out = new StringBuilder();

        for (String ch : arr) {
            if (" ".equals(ch)) {
                continue;
            }

            // 如果是数字,则直接添加到输出字符串中
            if (String.valueOf(ch).matches("\\d*")) {
                out.append(ch).append(" ");
                continue;
            }

            // 如果为'(',则直接压入堆栈
            if ("(".equals(ch)) {
                op.push(ch);
            }

            // 如果是'+'或'-',则从堆栈中弹出运算符,直到'('并添加到输出流中,然后将运算符推入堆栈
            if ("+".equals(ch) || "-".equals(ch)) {
                while (!op.empty() && (!"(".equals(op.peek()))) {
                    out.append(op.pop()).append(" ");
                }
                op.push(ch);
                continue;
            }

            // 如果为'*'或'/',则弹出运算符堆栈并添加到输出流中,直到优先级较低或'('将运算符推入堆栈
            if ("*".equals(ch) || "/".equals(ch)) {
                while (!op.empty() && ("*".equals(op.peek()) || "/".equals(op.peek()))) {
                    out.append(op.pop()).append(" ");
                }
                op.push(ch);
                continue;
            }

            // 如果是')',则将运算符堆栈弹出并添加到输出流中,直到'(',弹出'('
            if (")".equals(ch)) {
                while (!op.empty() && !"(".equals(op.peek())) {
                    out.append(op.pop()).append(" ");
                }
                op.pop();
            }
        }
        while (!op.empty()) {
            out.append(op.pop()).append(" ");
        }
        return out.toString();
    }

    public static void main(String[] args) {
        // 约束:操作数应等于或大于0但等于或小于9
        String exp = "9 + ( 3 - 1 ) * 4 + 10 / 2";
        float result = calRp(getRp(exp));
        System.out.println(result);
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值