数据结构_Java【学习笔记】线性表 + 符号表


  • 若文章内容或图片失效,请留言反馈。
  • 部分素材来自网络,若不小心影响到您的利益,请联系博主删除。


  • 写这篇博客旨在制作笔记,方便个人在线阅览,巩固知识。无他用。
  • 博客的内容主要来自上述视频中的内容和其资料中提供的学习笔记。

1.顺序表


public class SequenceList<T> implements Iterable<T> {

    // 存储元素的数组
    private T[] elements;
    
    // 记录当前顺序表中的元素个数
    private int N;

    // 构造方法
    public SequenceList(int capacity) {
        elements = (T[]) new Object[capacity];
        N = 0;
    }

    // 将一个线性表置为空表
    public void clear() {
        N = 0;
    }

    // 判断当前线性表是否为空表
    public boolean isEmpty() {
        return N == 0;
    }

    // 获取线性表的长度
    public int length() {
        return N;
    }

    // 获取指定位置的元素
    public T get(int i) {
        if (i < 0 || i >= N) {
            throw new RuntimeException("当前元素不存在!");
        }
        return elements[i];
    }

    // 向线型表中添加元素 t
    public void insert(T t) {
        if (N == elements.length) {
            resize(elements.length * 2);
        }
        elements[N++] = t;
    }

    // 在 i 元素处插入元素 t
    public void insert(int i, T t) {
        if (i < 0 || i > N) {
            throw new RuntimeException("插入的位置不合法");
        }

        // 元素已经放满了数组,需要扩容
        if (N == elements.length) {
            resize(elements.length * 2);
        }
        // 把 i 位置空出来,i 位置及其后面的元素依次向后移动一位
        for (int index = N - 1; index > i; index--) {
            elements[index] = elements[index - 1];
        }
        // 把 t 放到 i 位置处
        elements[i] = t;
        // 元素数量+1
        N++;
    }

    // 删除指定位置 i 处的元素,并返回该元素
    public T remove(int i) {
        if (i < 0 || i > N - 1) {
            throw new RuntimeException("当前要删除的元素不存在");
        }
        // 记录 i 位置处的元素
        T result = elements[i];
        // 把 i 位置后面的元素都向前移动一位
        for (int index = i; index < N - 1; index++) {
            elements[index] = elements[index + 1];
        }
        // 当前元素数量-1
        N--;
        // 当元素已经不足数组大小的 1/4,则重置数组的大小
        if (N > 0 && N < elements.length / 4) {
            resize(elements.length / 2);
        }
        return result;
    }

    // 查找 t 元素第一次出现的位置
    public int indexOf(T t) {
        if (t == null) {
            throw new RuntimeException("查找的元素不合法");
        }
        for (int i = 0; i < N; i++) {
            if (elements[i].equals(t)) {
                return i;
            }
        }
        return -1;
    }

    // 打印当前线性表的元素
    public void showelements() {
        for (int i = 0; i < N; i++) {
            System.out.print(elements[i] + " ");
        }
        System.out.println();
    }

    @Override
    public Iterator iterator() {
        return new SIterator();
    }

    private class SIterator implements Iterator {
        private int cursor;

        public SIterator() {
            this.cursor = 0;
        }

        @Override
        public boolean hasNext() {
            return cursor < N;
        }

        @Override
        public T next() {
            return elements[cursor++];
        }
    }

    // 改变容量
    private void resize(int newSize) {
    	// 记录旧数组
        T[] temp = elements;
        // 创建新数组
        elements = (T[]) new Object[newSize];
        // 把旧数组中的元素拷贝到新数组
        for (int i = 0; i < N; i++) {
            elements[i] = temp[i];
        }
    }

    public int capacity() {
        return elements.length;
    }
}

Java 中 ArrayList 集合的底层也是一种顺序表,使用数组实现,同样提供了增删改查以及扩容等功能。

  1. 是否用数组实现;
  2. 有没有扩容操作;
  3. 有没有提供遍历方式;

2.链表


2.1.单向链表


在下方的代码块的设计中,它的头节点是不存储数据的,其位置可以理解为 -1。
头节点之后的节点才会存储数据,这个时候的位置是 0。
当然你也可以理解有数据的节点才是开始真正索引的地方。

其实就是哨兵节点,它只有一个指向头结点的指针,不存储数据。
下方的的代码的变量名是直接拿头结点当哨兵节点了,所以链表真正开始存储数据的地方是 head.next

这样一来就可以比较容易理解下方的代码块了。

不过这只是下方代码块中的定义,链表中的头节点是否存储数据,这个是没有硬性要求的。

public class LinkList<T> implements Iterable<T> {
    // 记录头结点
    private Node head;

    // 记录链表的长度
    private int N;

    public LinkList() {
        // 初始化头结点
        head = new Node(null, null);
        N = 0;
    }

    // 清空链表
    public void clear() {
        head.next = null;
        head.item = null;
        N = 0;
    }

    // 获取链表的长度
    public int length() {
        return N;
    }

    // 判断链表是否为空
    public boolean isEmpty() {
        return N == 0;
    }

    // 获取指定位置 i 出的元素
    public T get(int i) {
        if (i < 0 || i >= N) {
            throw new RuntimeException("位置不合法!");
        }
        Node n = head.next;

        for (int index = 0; index < i; index++) {
            n = n.next;
        }
        return n.item;
    }

    // 向链表中添加元素 t
    public void insert(T t) {
        // 找到最后一个节点
        Node lastNode = head;
        while (lastNode.next != null) {
            lastNode = lastNode.next;
        }
        Node newNode = new Node(t, null);
        lastNode.next = newNode;
        // 链表长度 + 1
        N++;
    }

    // 向指定【位置 i】处,添加元素 t
    public void insert(int i, T t) {
        if (i < 0 || i >= N) {
            throw new RuntimeException("位置不合法!");
        }

        // 寻找【位置 i -1】的结点
        Node prevNode = head;
        for (int index = 0; index <= i - 1; index++) {
            prevNode = prevNode.next;
        }

        // 构建新的结点,让新结点指向位置 i 的结点
        Node insertNode = new Node(t, prevNode.next);

        // 让之前的结点指向新结点
        prevNode.next = insertNode;

        // 长度 + 1
        N++;
    }

    // 删除指定位置 i 处的元素,并返回被删除的元素
    public T remove(int i) {
        if (i < 0 || i >= N) {
            throw new RuntimeException("位置不合法");
        }

        // 寻找 i 之前的元素
        Node preNode = head;
        for (int index = 0; index <= i - 1; index++) {
            preNode = preNode.next;
        }

        // 当前 i 位置的结点
        Node removedNode = preNode.next;

        // 前一个结点指向下一个结点,删除当前结点
        preNode.next = removedNode.next;

        // 长度-1
        N--;

        return removedNode.item;
    }

    // 查找元素 t 在链表中第一次出现的位置
    public int indexOf(T t) {
        Node n = head;
        for (int i = 0; n.next != null; i++) {
            n = n.next;
            if (n.item.equals(t)) {
                return i;
            }
        }
        return -1;
    }

    // 打印当前线性表的元素
    // 这个方法是会打印出首节点的值的,也就是 null
    // 如果只想获取数据的话,就改动第一行的代码:Node node = head.next;
    public void show() {
        Node node = head;
        while (node != null) {
            System.out.print(node.item + " ");
            node = node.next;
        }
    }

    // 结点类
    private class Node {
        // 存储数据
        T item;
        // 下一个结点
        Node next;

        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }

    @Override
    public Iterator iterator() {
        return new LIterator();
    }

    private class LIterator implements Iterator<T> {
        private Node n;

        public LIterator() {
            this.n = head;
        }

        @Override
        public boolean hasNext() {
            return n.next != null;
        }

        @Override
        public T next() {
            n = n.next;
            return n.item;
        }
    }
}

2.2.双向链表


public class TwoWayLinkList<T> implements Iterable<T> {
    // 首结点
    private Node headNode;
    // 最后一个结点
    private Node lastNode;
    // 链表的长度
    private int N;

    public TwoWayLinkList() {
        lastNode = null;
        headNode = new Node(null, null, null);
        N = 0;
    }

    // 清空链表
    public void clear() {
        lastNode = null;
        headNode.next = null;
        headNode.pre = null;
        headNode.item = null;
        N = 0;
    }

    // 获取链表长度
    public int length() {
        return N;
    }

    // 判断链表是否为空
    public boolean isEmpty() {
        return N == 0;
    }

    // 插入元素 t
    public void insert(T t) {
        if (lastNode == null) {
            lastNode = new Node(t, headNode, null);
            headNode.next = lastNode;
        } else {
            Node oldLastNode = lastNode;
            Node newNode = new Node(t, oldLastNode, null);
            oldLastNode.next = newNode;
            lastNode = newNode;
        }
        // 长度 + 1
        N++;
    }

    // 向指定位置 i 处插入元素 t
    public void insert(int i, T t) {
        if (i < 0 || i >= N) {
            throw new RuntimeException("位置不合法");
        }
        // 找到位置i的前一个结点
        Node preNode = headNode;
        for (int index = 0; index < i; index++) {
            preNode = preNode.next;
        }
        // 当前结点
        Node currentNode = preNode.next;
        // 构建新结点
        Node insertNode = new Node(t, preNode, currentNode);
        currentNode.pre = insertNode;
        preNode.next = insertNode;
        // 长度 + 1
        N++;
    }

    // 获取指定位置 i 处的元素
    public T get(int i) {
        if (i < 0 || i >= N) {
            throw new RuntimeException("位置不合法");
        }
        // 寻找当前结点
        Node currentNode = headNode.next;
        for (int index = 0; index < i; index++) {
            currentNode = currentNode.next;
        }
        return currentNode.item;
    }

    // 找到元素 t 在链表中第一次出现的位置
    public int indexOf(T t) {
        Node n = headNode;
        for (int i = 0; n.next != null; i++) {
            n = n.next;
            if (n.item.equals(t)) {
                return i;
            }
        }
        return -1;
    }

    // 删除位置 i 处的元素,并返回该元素
    public T remove(int i) {
        if (i < 0 || i >= N) {
            throw new RuntimeException("位置不合法");
        }
        // 寻找 i 位置的前一个元素
        Node preNode = headNode;
        for (int index = 0; index < i; index++) {
            preNode = preNode.next;
        }
        // i 位置的元素
        Node removedNode = preNode.next;
        // i 位置的下一个元素
        Node removedNextNode = removedNode.next;
        preNode.next = removedNextNode;
        if (removedNextNode != null) {
            removedNextNode.pre = preNode;
        }
        // 长度-1;
        N--;
        return removedNode.item;
    }

    // 获取第一个元素
    public T getFirst() {
        if (isEmpty()) {
            return null;
        }
        return headNode.next.item;
    }

    // 获取最后一个元素
    public T getLast() {
        if (isEmpty()) {
            return null;
        }
        return lastNode.item;
    }

    // 打印当前线性表的元素
    public void show() {
        Node node = headNode;
        while (node != null) {
            System.out.print(node.item + " ");
            node = node.next;
        }
    }

    @Override
    public Iterator<T> iterator() {
        return new TIterator();
    }

    private class TIterator implements Iterator {
        private Node n = headNode;

        @Override
        public boolean hasNext() {
            return n.next != null;
        }

        @Override
        public Object next() {
            n = n.next;
            return n.item;
        }
    }

    // 结点类
    private class Node {
        public Node(T item, Node pre, Node next) {
            this.item = item;
            this.pre = pre;
            this.next = next;
        }

        // 存储数据
        public T item;
        // 指向上一个结点
        public Node pre;
        // 指向下一个结点
        public Node next;
    }
}

Java 中 LinkedList 集合也是使用双向链表实现,并提供了增删改查等相关方法

  1. 底层是否用双向链表实现
  2. 结点类是否有三个域

2.3.链表反转


单链表的链表反转

使用递归可以完成反转。递归反转其实就是从原链表的第一个存数据的结点开始,依次递归调用反转每一个结点,直到把最后一个结点反转完毕,整个链表就反转完毕。

注意:下方的代码采用的是头结点不存储数据的设定,链表真正开始于 headNode.next

public void reverse() {
    if (N == 0) {
        return;
    }
    reverse(headNode.next);
}
/**
 * @param currentNode 当前遍历的结点
 * @return
 */
public Node reverse(Node currentNode) {
    if (currentNode.next == null) {
        // 若是遍历到最后一个节点了,就让头结点指向这个节点
        headNode.next = currentNode;
        return currentNode;
    }

    Node newPreNode = reverse(currentNode.next); // 递归
    newPreNode.next = currentNode;
    currentNode.next = null;

    return currentNode;
}

吐槽:其实不用递归也是可以实现单链表的反转的

public void reverse_2() {
    Node prevNode = null;
    Node currentNode = headNode.next;

    while (currentNode != null) {
        if (currentNode.next == null) {
            headNode.next = currentNode;
        }

        Node nextNode = currentNode.next;
        currentNode.next = prevNode;
        prevNode = currentNode;
        currentNode = nextNode;
    }
}

2.4.快慢指针


快慢指针指的是定义两个指针,这两个指针的移动速度一块一慢,以此来制造出自己想要的差值。

这个差值可以让我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍。


2.4.1.中间值问题


中间值问题

public class TestFastSlow {

    public static void main(String[] args) throws Exception {
        //创建结点
        Node<String> first = new Node<String>("aa", null);
        Node<String> second = new Node<String>("bb", null);
        Node<String> third = new Node<String>("cc", null);
        Node<String> fourth = new Node<String>("dd", null);
        Node<String> fifth = new Node<String>("ee", null);
        Node<String> six = new Node<String>("ff", null);
        Node<String> seven = new Node<String>("gg", null);

        //完成结点之间的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;

        //查找中间值
        String mid = getMid(first);
        System.out.println("中间值为:" + mid);
    }

    //结点类
    private static class Node<T> {
        //存储数据
        T item;
        //下一个结点
        Node next;

        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}
/**
 * @param first 链表的首结点
 * @return 链表的中间结点的值
 */
public static String getMid(Node<String> first) {
    //定义两个指针
    Node<String> fast = first;
    Node<String> slow = first;
    //使用两个指针遍历链表,当快指针指向的结点没有下一个结点了,就可以结束了,结束之后,慢指针指向的结点就是中间值
    while (fast != null && fast.next != null) {
        //变化 fast 的值和 slow 的值
        fast = fast.next.next;
        slow = slow.next;
    }

    return slow.item;
}

2.4.2.单向链表是否有环问题


单向链表是否有环问题


public class TestCircleListCheck {
    public static void main(String[] args) throws Exception {
        // 创建结点
        Node<String> first = new Node<String>("aaa", null);
        Node<String> second = new Node<String>("bbb", null);
        Node<String> third = new Node<String>("ccc", null);
        Node<String> fourth = new Node<String>("ddd", null);
        Node<String> fifth = new Node<String>("eee", null);
        Node<String> six = new Node<String>("fff", null);
        Node<String> seven = new Node<String>("ggg", null);
        Node<String> eight = new Node<String>("hhh", null);

        // 完成结点之间的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;
        // seven.next = third;
        seven.next = eight;
        eight.next = third;

        // 判断链表是否有环
        boolean circle = isCircle(first);
        System.out.println("first 链表中是否有环:" + circle);
    }

    // 结点类
    private static class Node<T> {
        // 存储数据
        T item;
        // 下一个结点
        Node next;

        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}
/**
 * 判断链表中是否有环
 *
 * @param first 链表首结点
 * @return true 为有环,false 为无环
 */
public static boolean isCircle(Node<String> first) {
    // 定义快慢指针
    Node<String> fast = first;
    Node<String> slow = first;

    // 遍历链表,如果快慢指针指向了同一个结点,那么证明有环
    while (fast != null && fast.next != null) {
        // 变换 fast 和 slow
        fast = fast.next.next;
        slow = slow.next;

        if (fast.equals(slow)) {
            return true;
        }
    }

    return false;
}

2.4.3.有环链表入口问题


有环链表入口问题

public class TestCircleListIn {
    public static void main(String[] args) throws Exception {
        Node<String> first = new Node<String>("AAA", null);
        Node<String> second = new Node<String>("BBB", null);
        Node<String> third = new Node<String>("CCC", null);
        Node<String> fourth = new Node<String>("DDD", null);
        Node<String> fifth = new Node<String>("EEE", null);
        Node<String> six = new Node<String>("FFF", null);
        Node<String> seven = new Node<String>("GGG", null);

        // 完成结点之间的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;
        // 产生环
        seven.next = third;

        // 查找环的入口结点
        Node<String> entrance = getEntrance(first);
        System.out.println("first 链表中环的入口结点元素为:" + entrance.item);
    }

    // 结点类
    private static class Node<T> {
        // 存储数据
        T item;
        // 下一个结点
        Node next;

        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}
/**
 * 查找有环链表中环的入口结点
 *
 * @param first 链表首结点
 * @return 环的入口结点
 */
public static Node getEntrance(Node<String> first) {
    // 定义快慢指针
    Node<String> fast = first;
    Node<String> slow = first;
    Node<String> temp = null;

    // 遍历链表,先找到环(快慢指针相遇)
    // 准备一个临时指针,指向链表的首结点,继续遍历
    // 直到慢指针和临时指针相遇,那么相遇时所指向的结点就是环的入口
    while (fast != null && fast.next != null) {
        // 变换快慢指针
        fast = fast.next.next;
        slow = slow.next;

        // 判断快慢指针是否相遇
        if (fast.equals(slow) && temp == null) {
            temp = first;
            continue;
        }

        // 让临时结点变换
        if (temp != null && !fast.equals(slow)) {
            temp = temp.next;
            // 判断临时指针是否和慢指针相遇
            if (temp.equals(slow)) {
                break;
            }
        }
    }

    return temp;
}

2.5.循环链表


循环链表,顾名思义,链表整体要形成一个圆环状。在单向链表中,最后一个节点的指针为 null,不指向任何结点,因为没有下一个元素了。要实现循环链表,我们只需要让单向链表的最后一个节点的指针指向头结点即可。

在这里插入图片描述


2.6.约瑟夫问题


问题描述

传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39 个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41 个人排成一个圆圈,第一个人从 1 开始报数,依次往后,如果有人报数到 3,那么这个人就必须自杀,然后再由他的下一个人重新从 1 开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第 16 个与第 31 个位置,从而逃过了这场死亡游戏 。


问题转换

41 个人坐一圈,第一个人编号为 1,第二个人编号为 2,第 n 个人编号为 n。

  1. 编号为 1 的人开始从 1 报数,依次向后,报数为 3 的那个人退出圈;
  2. 自退出那个人开始的下一个人再次从 1 开始报数,以此类推;
  3. 求出最后退出的那个人的编号。

在这里插入图片描述


解题思路

  1. 构建含有 41 个结点的单向循环链表,分别存储 1~41 的值,分别代表这 41 个人;
  2. 使用计数器 count,记录当前报数的值;
  3. 遍历链表,每循环一次,count++
  4. 判断 count 的值,如果是 3,则从链表中删除这个结点并打印结点的值,把 count 重置为 0;

public class TestJoseph {
    public static void main(String[] args) {
        // 1.构建循环链表,包含 41 个结点,分别存储 1~41 之间的值
        Node<Integer> first = null;
        // 记录前一个结点
        Node<Integer> pre = null;
        for (int i = 1; i <= 41; i++) {
            // 第一个元素
            if (i == 1) {
                first = new Node(i, null);
                pre = first;
                continue;
            }
            Node<Integer> node = new Node<>(i, null);
            pre.next = node;
            pre = node;
            if (i == 41) {
                // 构建循环链表,让最后一个结点指向第一个结点
                pre.next = first;
            }
        }

        // 2.使用 count,记录当前的报数值
        int count = 0;
        // 3.遍历链表,每循环一次,count++
        // 记录每次遍历拿到的节点,默认从首节点开始
        Node<Integer> n = first;
        // 记录当前节点的上一个节点
        Node<Integer> before = null;
        while (n != n.next) {
            // 4.判断 count 的值,如果是 3,则从链表中删除这个结点并打印结点的值,把 count 重置为 0;
            count++;
            if (count == 3) {
                // 删除当前结点
                before.next = n.next;
                System.out.print(n.item + ",");
                count = 0;
                n = n.next;
            } else {
                before = n;
                n = n.next;
            }
        }
        
        /* 打印剩余的最后那个人 */
        System.out.println(n.item);
    }

    // 结点类
    private static class Node<T> {
        //存储数据
        T item;
        //下一个结点
        Node next;

        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}

3.栈


3.1.栈的实现


栈是一种基于先进后出(FILO)的数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。我们称数据进入到栈的动作为压栈,数据从栈中出去的动作为弹栈。

在这里插入图片描述

栈可以用数组实现,也可以用链表实现。


public class Stack<T> implements Iterable<T> {
    // 记录首节点
    private Node head;
    // 栈中的元素个数
    private int N;

    // 节点类
    public class Node {
        public T item;
        public Node next;

        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }

    // 栈的构造方法
    public Stack() {
        this.head = new Node(null, null);
        this.N = 0;
    }

    // 判断当前栈中的元素个数是否为 0
    public boolean isEmpty() {
        return false;
    }

    // 获取栈中的元素的个数
    public int size() {
        return N;
    }

    // 将 t 元素压入栈中
    public void push(T t) {
        // 找到首节点指向的第一个节点
        Node oldFirst = head.next;
        // 创建新的节点
        Node newNode = new Node(t, null);
        // 让首节点指向新结点
        head.next = newNode;
        // 让新节点指向原来的第一个节点
        newNode.next = oldFirst;
        // 元素个数 + 1
        N++;
    }

    // 弹出栈顶元素
    public T pop() {
        // 找到首节点指向的第一个节点
        Node oldFirst = head.next;
        if (oldFirst == null) {
            return null;
        }
        // 让首节点指向原来的第一个节点的下一个节点
        head.next = oldFirst.next;
        // 元素个数 -1
        N--;

        return oldFirst.item;
    }


    @Override
    public Iterator<T> iterator() {
        return new SIterator();
    }

    private class SIterator implements Iterator {
        private Node n;

        public SIterator() {
            this.n = head;
        }

        public boolean hasNext() {
            return n.next != null;
        }

        public T next() {
            n = n.next;
            return n.item;
        }
    }
}

3.2.括号匹配问题


问题描述:给定一个字符串,里边可能包含 “()” 小括号和其他字符,请编写程序检查该字符串的中的小括号是否成对出现。


public class TestBracketsMatch {
    public static void main(String[] args) {
        String str = "上海(长安)())";
        boolean match = isMatch(str);
        System.out.println(str + "中的括号是否匹配:" + match);
    }

    /**
     * 判断 str 中的括号是否匹配
     *
     * @param str 括号组成的字符串
     * @return 如果匹配,返回 true,如果不匹配,返回 false
     */
    public static boolean isMatch(String str) {
        // 1.创建栈对象,用来存储左括号
        Stack<String> charsString = new Stack<>();
        // 2.从左往右遍历字符串
        for (int i = 0; i < str.length(); i++) {
            // 字符操作起来不是很方便,故于 +"" 将此转换成字符串
            String currCharString = str.charAt(i) + "";
            // 3.判断当前字符是否为左括号,如果是,则把字符放入到栈中
            if (currCharString.equals("(")) {
                charsString.push(currCharString);
            } else if (currCharString.equals(")")) {
                // 4.继续判断当前字符是否是右括号
                //  如果是,则从栈中弹出一个左括号,并判断弹出的结果是否为 null
                //  如果为 null 证明没有匹配的左括号
                //  如果不为 null,则证明有匹配的左括号
                String pop = charsString.pop();
                if (pop == null) {
                    return false;
                }
            }
        }
        // 5.判断栈中还有没有剩余的左括号,如果有,则证明括号不匹配
        return charsString.size() == 0;
    }
}

3.3.逆波兰表达式


中缀表达式

中缀表达式就是我们平常生活中使用的表达式,例如:1+3*22-(1+3) 等等,中缀表达式的特点是:二元运算符总是置于两个操作数中间。

中缀表达式是人们最喜欢的表达式方式,因为简单,易懂。但是对于计算机来说就不是这样了,因为中缀表达式的运算顺序不具有规律性。不同的运算符具有不同的优先级,如果计算机执行中缀表达式,需要解析表达式语义,做大量的优先级相关操作。


逆波兰表达式后缀表达式

逆波兰表达式是 波兰逻辑学家 J・卢卡西维兹(J・Lukasewicz)于 1929 年首先提出的一种表达式的表示方法。

后缀表达式的特点:运算符总是放在跟它相关的操作数之后。

中缀表达式逆波兰表达式
a+bab+
a+(b-c)abc-+
a+(b-c)*dabc-d*+
a*(b-c)+dabc-*d+

public class TestReversePolishNotation {
    public static void main(String[] args) {
        // 中缀表达式 [3*(17-15)+18/6] 的逆波兰表达式如下
        String[] notation = {"3", "17", "15", "-", "*", "18", "6", "/", "+"};
        int result = calculate(notation);
        System.out.println("逆波兰表达式的结果为:" + result); // 9
    }

    /**
     * @param notation 逆波兰表达式的数组表示方式
     * @return 逆波兰表达式的计算结果
     */
    public static int calculate(String[] notation) {
        // 1.定义一个栈,用来存储操作数
        Stack<Integer> operands = new Stack<>();
        // 2.从左往右遍历逆波兰表达式,得到每一个元素
        for (String currentString : notation) {
            // 3.判断当前元素是运算符还是操作数
            Integer o1;
            Integer o2;
            int result;
            switch (currentString) {
                case "+":
                    // 4.运算符,从栈中弹出两个操作数,完成运算,运算完的结果再压入栈中
                    o1 = operands.pop();
                    o2 = operands.pop();
                    result = o2 + o1;
                    operands.push(result);
                    break;
                case "-":
                    // 4.运算符,从栈中弹出两个操作数,完成运算,运算完的结果再压入栈中
                    o1 = operands.pop();
                    o2 = operands.pop();
                    result = o2 - o1;
                    operands.push(result);
                    break;
                case "*":
                    // 4.运算符,从栈中弹出两个操作数,完成运算,运算完的结果再压入栈中
                    o1 = operands.pop();
                    o2 = operands.pop();
                    result = o2 * o1;
                    operands.push(result);
                    break;
                case "/":
                    // 4.运算符,从栈中弹出两个操作数,完成运算,运算完的结果再压入栈中
                    o1 = operands.pop();
                    o2 = operands.pop();
                    result = o2 / o1;
                    operands.push(result);
                    break;
                default:
                    // 5.操作数,把该操作数放入到栈中;
                    operands.push(Integer.parseInt(currentString));
                    break;
            }
        }
        // 6.得到栈中最后一个元素,就是逆波兰表达式的结果
        return operands.pop();
    }
}

4.队列


队列是一种基于先进先出(FIFO)的数据结构,是一种只能在一端进行插入,在另一端进行删除操作的特殊线性表,它按照先进先出的原则存储数据,先进入的数据,在读取数据时先读被读出来。

在这里插入图片描述


public class Queue<T> implements Iterable<T> {
    // 记录首节点
    private Node head;
    // 记录最后一个节点
    private Node last;
    // 记录队列中元素的个数
    private int N;

    public class Node {
        public T item;
        public Node next;

        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }

    public Queue() {
        this.head = new Node(null, null);
        this.last = null;
        this.N = 0;
    }

    // 判断队列是否为空
    public boolean isEmpty() {
        return N == 0;
    }

    // 返回队列中元素的个数
    public int size() {
        return N;
    }

    // 向队列中插入元素 t
    public void enqueue(T t) {
        if (last == null) {
            last = new Node(t, null);
            head.next = last;
        } else {
            Node oldLast = last;
            last = new Node(t, null);
            oldLast.next = last;
        }
        N++;
    }

    // 从队列中拿出一个元素
    public T dequeue() {
        if (isEmpty()) {
            return null;
        }
        Node oldFirst = head.next;
        head.next = oldFirst.next;
        N--;
        // 因为出队列是在删除元素,故队列中无元素时,需重置 last = null
        if (isEmpty()) {
            last = null;
        }
        return oldFirst.item;
    }

    @Override
    public Iterator<T> iterator() {
        return new QIterator();
    }

    private class QIterator implements Iterator<T> {
        private Node n = head;

        @Override
        public boolean hasNext() {
            return n.next != null;
        }

        @Override
        public T next() {
            n = n.next;
            return n.item;
        }
    }
}

5.符号表


符号表最主要的目的就是将一个键和一个值联系起来,符号表能够将存储的数据元素是一个键和一个值共同组成的键值对数据,我们可以根据键来查找对应的值。

符号表中,键具有唯一性。

符号表在实际生活中的使用场景是非常广泛的,见下表:

应用查找目的
字典找出单词的释义单词释义
图书索引找出某个术语相关的页码术语一串页码
网络搜索找出某个关键字对应的网页关键字网页名称

5.1.符号表的实现


public class SymbolTable<Key, Value> {
    // 记录首节点
    private Node head;
    // 记录符号表中的元素的个数
    private int N;

    private class Node {
        public Key key;
        private Value value;
        private Node next;

        public Node(Key key, Value value, Node next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }

    public SymbolTable() {
        this.head = new Node(null, null, null);
        this.N = 0;
    }

    // 获取符号表中键值对的个数
    public int size() {
        return N;
    }

    // 往符号表中插入键值对(头插法)
    public void put(Key key, Value value) {
        // 先从符号表中查找键为 key 的键值对
        Node n = head;
        while (n.next != null) {
            n = n.next;
            if (n.key.equals(key)) {
                n.value = value;
                return;
            }
        }

        //符号表中没有键为 key 的键值对
        Node oldFirst = head.next;
        head.next = new Node(key, value, oldFirst);
        N++;
    }

    // 删除符号表中键为 key 的键值对
    public void delete(Key key) {
        Node n = head;
        while (n.next != null) {
            if (n.next.key.equals(key)) {
                n.next = n.next.next;
                N--;
                return;
            }
            n = n.next;
        }
    }

    //从符号表中获取 key 对应的值
    public Value get(Key key) {
        Node n = head;
        while (n.next != null) {
            n = n.next;
            if (n.key.equals(key)) {
                return n.value;
            }
        }
        return null;
    }
}

5.2.有序符号表的实现


刚才实现的符号表,我们可以称之为无序符号表,因为在插入的时候,并没有考虑键值对的顺序。

在实际生活中,有时候我们需要根据键的大小进行排序,插入数据时要考虑顺序,那么接下来我们就实现一下有序符号表。


此处的 OrderSymbolTable.java 和 上面的 SymbolTable.java 的区别仅在 put 方法中

public class OrderSymbolTable<Key extends Comparable<Key>, Value> {
    // 记录首节点
    private Node head;
    // 记录符号表中的元素的个数
    private int N;

    private class Node {
        public Key key;
        private Value value;
        private Node next;

        public Node(Key key, Value value, Node next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }

    public OrderSymbolTable() {
        this.head = new Node(null, null, null);
        this.N = 0;
    }

    // 获取符号表中键值对的个数
    public int size() {
        return N;
    }

    // 往符号表中插入键值对
    public void put(Key key, Value value) {
        // 定义两个 Node 变量,分别记录当前节点和当前节点的上一个节点
        Node current = head.next;
        Node previous = head;

        while (current != null && key.compareTo(current.key) > 0) {
            // 变换当前节点和前一个节点即可
            previous = current;
            current = current.next;
        }

        // 若当前节点的 键 和要插入的 key 一样,则替换值
        if (current != null && key.compareTo(current.key) == 0) {
            current.value = value;
            return;
        }

        // 若当前节点的 键 和要插入的 key 不一样,则插入新结点到 current 之前即可
        previous.next = new Node(key, value, current);
        // 元素的个数+1
        N++;
    }

    // 删除符号表中键为 key 的键值对
    public void delete(Key key) {
        Node n = head;
        while (n.next != null) {
            if (n.next.key.equals(key)) {
                n.next = n.next.next;
                N--;
                return;
            }
            n = n.next;
        }
    }

    //从符号表中获取 key 对应的值
    public Value get(Key key) {
        Node n = head;
        while (n.next != null) {
            n = n.next;
            if (n.key.equals(key)) {
                return n.value;
            }
        }
        return null;
    }
}

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值