算法

1.冒泡排序
2.选择排序
3.插入排序
4.希尔排序
5.归并排序
6.快速排序
7.线性表
8.顺序表
9.链表
10.栈
11.队列
12.符号表
13.二叉树
14.堆
15.优先队列
16.2-3 树
17.红黑树
18.B-树
19.B+树
20.并查集
21.图
22.有向图

一. 冒泡排序

image.png

排序原理:
1.比较相邻的元素,如果前一个元素比后一个元素大,就交换这两个元素的位置
2.对每一个相邻元素做同样的工作,从开始第一对元素到结尾的最后一个元素,最终最后位置的元素就是最大值

冒泡排序API设计

public class Bubble {
    /**
     * 对数组a中的元素进行排序
     */
    public static void sort(Comparable[] a) {
        for (int i = a.length - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                // 比较索引j和索引j+1处的值
                if (greater(a[j], a[j + 1])) {
                    exch(a, j, j + 1);
                }
            }
        }
    }

    /**
     * 比较v元素是否大于w元素
     */
    private static boolean greater(Comparable v, Comparable w) {
        return v.compareTo(w) > 0;
    }

    /**
     * 数组元素i和j交换位置
     */
    private static void exch(Comparable[] a, int i, int j) {
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

二. 选择排序

image.png

排序原理:
1.每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较,如果当前索引处的值大于其他某个索引处的值,则假定其他某个索引处的值为最小值,最后可以找到最小值所在的索引
2.交换第一个索引处和最小值所在的索引处的值

选择排序API设计

public class Selection {
    /**
     * 对数组a中的元素进行排序
     */
    public static void sort(Comparable[] a) {
        for (int i = 0; i <= a.length - 2; i++) {
            //定义一个变量,记录最小元素所在的索引,默认为参与选择排序的第一个元素所在的位置
            int minIndex = i;
            for (int j = i + 1; j < a.length; j++) {
                //需要比较最小索引minIndex处的值和j索引处的值
                if (greater(a[minIndex], a[j])) {
                    minIndex = j;
                }
            }

            //交换最小元素所在索引minIndex处的值和索引i的值
            exch(a, i, minIndex);
        }
    }

    /**
     * 比较v元素是否大于w元素
     */
    private static boolean greater(Comparable v, Comparable w) {
        return v.compareTo(w) > 0;
    }

    /**
     * 数组元素i和j交换位置
     */
    private static void exch(Comparable[] a, int i, int j) {
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

三.插入排序

image.png

插入排序API设计

public class Insertion {
    /**
     * 对数组a中的元素进行排序
     */
    public static void sort(Comparable[] a) {
        for (int i = 1; i < a.length; i++) {
            for (int j = i; j > 0; j--) {
        // 比较索引j处的值和索引j-1处的值,如果索引j-1处的值大,则交换数据,如果不大,
        // 那么就找到合适的位置了,退出循环即可
                if (greater(a[j - 1], a[j])) {
                    exch(a, j - 1, j);
                } else {
                    break;
                }
            }
        }
    }

    /**
     * 比较v元素是否大于w元素
     */
    private static boolean greater(Comparable v, Comparable w) {
        return v.compareTo(w) > 0;
    }

    /**
     * 数组元素i和j交换位置
     */
    private static void exch(Comparable[] a, int i, int j) {
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

4.希尔排序API设计

public class Shell {
    /**
     * 对数组a中的元素进行排序
     */
    public static void sort(Comparable[] a) {
        // 1.根据数组a的长度,确定增长量h的初始值
        int h = 1;
        while (h < a.length / 2) {
            h = h * 2 + 1;
        }

        //2.希尔排序
        while (h >= 1) {
            //排序
            //2.1.找到待插入的元素
            for (int i = h; i < a.length; i++) {
                //2.2 把待插入的元素插入到有序数列中
                for (int j = i; j >= h; j -= h) {
                    //待插入的元素是a[j],比较a[j]和a[j-h]
                    if (greater(a[j - h], a[j])) {
                        //交换元素
                        exch(a, j - h, j);
                    } else {
                        // 待插入元素找到合适的位置,结束循环
                        break;
                    }
                }
            }
        }
    }

    /**
     * 比较v元素是否大于w元素
     */
    private static boolean greater(Comparable v, Comparable w) {
        return v.compareTo(w) > 0;
    }

    /**
     * 数组元素i和j交换位置
     */
    private static void exch(Comparable[] a, int i, int j) {
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

5.n的阶乘

public class TestFactorial {

    /**
     * n的阶乘
     * @param n
     * @return
     */
    public long factorial(int n) {
        if (n == 1) {
            return 1;
        }

        return n * factorial(n - 1);
    }
}

6.归并排序API设计

public class Merge {
    // 归并所需要的辅助数组
    private static Comparable[] assist;

    /**
     * 比较v元素是否小于w元素
     */
    private static boolean less(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;
    }

    /**
     * 数组元素i和j交换位置
     */
    private static void exch(Comparable[] a, int i, int j) {
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    /**
     * 对数组a的元素进行排序
     */
    public static void sort(Comparable[] a) {
        //1.初始化辅助数组assist
        assist = new Comparable[a.length];
        //2.定义一个lo变量,hi变量,分别记录数组中最小的索引和最大的索引
        int lo = 0;
        int hi = a.length - 1;
        //3.调用sort重载方法完成数组a中,从索引lo到hi的元素的排序
        sort(a, lo, hi);
    }

    /**
     * 对数组a中从lo到hi的元素进行排序
     */
    private static void sort(Comparable[] a, int lo, int hi) {
        // 做安全性校验
        if (hi <= lo) {
            return;
        }

        //对lo到hi之间的数据分为两组
        int mid = lo + (hi - lo) / 2;

        //分别对每一组数据进行排序
        sort(a, lo, mid);
        sort(a, mid + 1, hi);

        //再把两个组中的数据进行归并
        merge(a, lo, mid, hi);
    }

    /**
     * 对数组中,从lo到mid为一组,从mid+1到hi为一组,对这两组数据进行归并
     */
    private static void merge(Comparable[] a, int lo, int mid, int hi) {
        //定义三个数组
        int i = lo;
        int p1 = lo;
        int p2 = mid + 1;

        // 遍历,移动p1指针和p2指针,比较对应索引处的值,找到小的那个,放到辅助数组的对应索引处
        while (p1 <= mid && p2 <= hi) {
            //比较对应索引处的值
            if (less(a[p1], a[p2])) {
                assist[i++] = a[p1++];
            } else {
                assist[i++] = a[p2++];
            }
        }

        //遍历,如果p1的指针没有走完,那么顺序移动p1指针,把对应的元素放到辅助数组的对应索引处
        while (p1 <= mid) {
            assist[i++] = a[p1++];
        }

        //遍历,如果p2的指针没有走完,那么顺序移动p2指针,把对应的元素放到辅助数组的对应索引处
        while (p2 <= hi) {
            assist[i++] = a[p2++];
        }

        //把辅助数组中的元素拷贝到原数组中
        for (int index = lo; index <= hi; index++) {
            a[index++] = assist[index++];
        }
    }
}

7.快速排序API设计

public class Quick {
    /**
     * 比较v元素是否小于w元素
     */
    private static boolean less(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;
    }

    /**
     * 数组元素i和j交换位置
     */
    private static void exch(Comparable[] a, int i, int j) {
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    /**
     * 对数组a的元素进行排序
     */
    public static void sort(Comparable[] a) {
        int lo = 0;
        int hi = a.length - 1;
        sort(a, lo, hi);
    }

    /**
     * 对数组a中从lo到hi的元素进行排序
     */
    private static void sort(Comparable[] a, int lo, int hi) {
        //安全性校验
        if (hi < lo) {
            return;
        }

        //需要对数组中lo索引到hi索引处的元素进行分组(左子组和右子组)
        //返回的是分组的分界值所在的索引
        int partition = partition(a, lo, hi);

        //让左子组有序
        sort(a, lo, partition - 1);

        //让右子组有序
        sort(a, partition + 1, hi);
    }

    // 对数组a中,从索引lo到索引hi之间的元素进行分组,并返回分组界限对应的索引
    public static int partition(Comparable[] a, int lo, int hi) {
        // 确定分界值
        Comparable key = a[lo];
        // 定义两个指针,分别指向待切分元素的最小索引处和最大索引处的下一个位置
        int left = lo;
        int right = hi + 1;

        //切分
        while (true) {
            //先从右到左扫描,移动right指针,找到一个比分界值小的元素,停止
            while (less(key, a[--right])) {
                if (right == lo) {
                    break;
                }
            }

            //再从左往右扫描,移动left指针,找到一个比分界值大的元素,停止
            while (less(a[++left], key)) {
                if (left == hi) {
                    break;
                }
            }

            //判断left>=right,如果是,则证明元素扫描完毕,结束循环,如果不是,则交换元素即可
            if (left >= right) {
                break;
            } else {
                exch(a, left, right);
            }
        }

        //交换分界值
        exch(a, lo, right);
        return right;
    }
}

8.顺序表API设计

public class SequenceList implements Iterable {
    //存储元素的数组
    private T[] eles;
    //记录当前顺序表的元素个数
    private int N;

    //构造方法
    public SequenceList(int capacity) {
        //初始化数组
        this.eles = (T[]) new Object[capacity];
        //初始化长度
        this.N = 0;
    }

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

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

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

    //获取指定位置的元素
    public T get(int i) {
        return eles[i];
    }

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

    //向线性表中添加元素t
    public void insert(int i, T t) {
        if (N == eles.length) {
            resize(2 * eles.length);
        }

        //先把i索引处的元素及其后面的元素依次向后移动一位
        for (int index = N; index > i; index--) {
            eles[index] = eles[index - 1];
        }
        //再把t元素放到i索引处即可
        eles[i] = t;

        //元素个数+1
        N++;
    }

    //删除指定位置i处的元素,并返回该元素
    public T remove(int i) {
        //记录索引i处的值
        T current = eles[i];
        //索引i后面元素依次向前移动一位即可
        for (int index = i; index < N - 1; index++) {
            eles[index] = eles[index + 1];
        }
        //元素个数-1
        N--;

        if (N < eles.length / 4) {
            resize(eles.length / 2);
        }
        return current;
    }

    //查询T元素第一次出现的位置
    public int indexOf(T t) {
        for (int i = 0; i < N; i++) {
            if (eles[i].equals(t)) {
                return i;
            }
        }
        return -1;
    }

    //根据参数newSize,重置eles的大小
    public void resize(int newSize) {
        //定义一个临时数组,指向原数组
        T[] temp = eles;
        //创建新数组
        eles = (T[]) new Object[newSize];
        //把原数组的数据拷贝到新数组即可
        for (int i = 0; i < N; i++) {
            eles[i] = temp[i];
        }
    }


    @NonNull
    @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 Object next() {
            return eles[cursor++];
        }
    }
}

9.单向链表API设计

public class LinkedList implements Iterable {
    //记录头结点
    private Node head;
    //记录链表的长度
    private int N;

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

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

    public LinkedList() {
        //初始化头结点
        this.head = new Node(null, null);
        //初始化元素个数
        this.N = 0;
    }

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

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

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

    //获取指定位置i处的元素
    public T get(int i) {
        //通过循环,从头结点开始往后找,依次找i次,就可以找到对应的元素
        Node n = head.next;
        for (int index = 0; index < i; index++) {
            n = n.next;
        }
        return n.item;
    }

    //向链表中添加元素t
    public void insert(T t) {
        //找到当前最后一个结点
        Node n = head;
        while (n.next != null) {
            n = n.next;
        }
        //创建新结点,保存元素t
        Node newNode = new Node(t, null);
        //让当前最后一个结点指向新结点
        n.next = newNode;
        //元素的个数+1
        N++;
    }

    //向指定位置i处,添加元素t
    public void insert(int i, T t) {
        //找到i位置前一个结点
        Node pre = head;
        for (int index = 0; index <= i - 1; index++) {
            pre = pre.next;
        }
        //找到i位置的结点
        Node curr = pre.next;
        //创建新结点,并且新结点需要指向原来i位置的结点
        Node newNode = new Node(t, curr);
        //原来i位置的前一个结点指向新结点即可
        pre.next = newNode;
        //元素的个数+1
        N++;
    }

    //删除指定位置i处的结点,并返回被删除的元素
    public T remove(int i) {
        //找到i位置前一个结点
        Node pre = head;
        for (int index = 0; index <= i - 1; i++) {
            pre = pre.next;
        }
        //找到i位置的结点
        Node curr = pre.next;
        //找到i位置的下一个结点
        Node nextNode = curr.next;
        //前一个结点指向下一个结点
        pre.next = nextNode;
        //元素个数-1
        N--;
        return curr.item;
    }

    //查找元素t在链表中第一次出现的位置
    public int indexOf(T t) {
        //从头结点开始,依次找到每一个结点,取出item,和t比较,如果相同,就找到了
        Node n = head;
        for (int i = 0; n.next != null; i++) {
            n = n.next;
            if (n.item.equals(t)) {
                return i;
            }
        }
        return -1;
    }

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

    private class LIterator implements Iterator {
        private Node n;

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

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

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

    //用来反转整个链表
    public void reverse(){
        //判断当前链表是否为空链表,如果是空链表,则结束运行,如果不是,则调用重载的reverse方法完成反转
        if (isEmpty()){
            return;
        }
        reverse(head.next);
    }

    //反转指定的结点curr,并把反转后的结点返回
    public Node reverse(Node curr) {
        if (curr.next==null){
            head.next=curr;
            return curr;
        }
        //递归的反转当前结点curr的下一个结点,返回值就是链表反转后,当前结点的上一个结点
        Node pre=reverse(curr.next);
        //让返回的结点的下一个结点变成当前结点curr
        pre.next=curr;
        //把当前结点的下一个结点变成null
        curr.next=null;
        return curr;
    }
}

10.双向链表API设计

public class TwoWayLinkList implements Iterable {
    //首结点
    private Node head;
    //最后一个结点
    private Node last;
    //链表的长度
    private int N;

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

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

    public TwoWayLinkList() {
        //初始化头结点和尾结点
        this.head = new Node(null,null,null);
        this.last=null;
        this.N=0;
    }

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

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

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

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

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

    //插入元素t
    public void insert(T t){
        if (isEmpty()){
            //如果链表为空
            //创建新的结点
            Node newNode = new Node(t,head,null);
            //让新结点成为尾结点
            last=newNode;
            //让头结点指向尾结点
            head.next=last;
        }else {
            //如果链表不为空
            Node oldLast =last;
            //创建新的结点
            Node newNode = new Node(t,oldLast,null);
            //让当前的尾结点指向新结点
            oldLast.next=newNode;
            //让新结点成为尾结点
            last=newNode;
        }

        //元素个数+1
        N++;
    }

    //向指定位置i处插入元素t
    public void insert(int i,T t){
        //找到i位置的前一个结点
        Node pre=head;
        for (int index=0;index iterator() {
        return new LIterator();
    }

    private class LIterator implements Iterator {
        private Node n;
        public LIterator() {
            this.n=head;
        }

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

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

11.快慢指针-中间值问题

public class FastSlowTest {

    public static void main(String[] args) {
        Node first=new Node<>("aa",null);
        Node second=new Node<>("bb",null);
        Node third=new Node<>("cc",null);
        Node fourth=new Node<>("dd",null);
        Node fifth=new Node<>("ee",null);
        Node six=new Node<>("ff",null);
        Node seven=new Node<>("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);
    }

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

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

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

12.单链表是否有环问题

public class CircleListCheckTest {
    public static void main(String[] args) {
        Node first=new Node<>("aa",null);
        Node second=new Node<>("bb",null);
        Node third=new Node<>("cc",null);
        Node fourth=new Node<>("dd",null);
        Node fifth=new Node<>("ee",null);
        Node six=new Node<>("ff",null);
        Node seven=new Node<>("gg",null);

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

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

    //判断链表中是否有环
    public static boolean isCircle(Node first) {
        //定义两个指针
        Node fast=first;
        Node 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;
    }

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

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

13.有环链表入口问题

public class CircleListInTest {
    public static void main(String[] args) {
        Node first=new Node<>("aa",null);
        Node second=new Node<>("bb",null);
        Node third=new Node<>("cc",null);
        Node fourth=new Node<>("dd",null);
        Node fifth=new Node<>("ee",null);
        Node six=new Node<>("ff",null);
        Node seven=new Node<>("gg",null);

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

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

    //判断链表中是否有环
    public static Node getEntrance(Node first) {
        //定义快慢指针
        Node fast=first;
        Node slow=first;
        Node temp=null;

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

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

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

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

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

14.约瑟夫问题

public class JosephTest {

    public static void main(String[] args) {
        //1.构建循环链表,包含41个结点,分别存储1-41之间的值
        //首结点
        Node first = null;
        //用来记录前一个结点
        Node pre = null;
        for (int i = 0; i <= 41; i++) {
            //如果是第一个结点
            if (i == 1) {
                first = new Node<>(i, null);
                pre = first;
                continue;
            }

            //如果不是第一个结点
            Node newNode = new Node<>(i, null);
            pre.next = newNode;
            pre = newNode;
            //如果是最后一个结点,那么需要让最后一个结点的下一个结点变成first,变成循环链表了
            if (i == 41) {
                pre.next = first;
            }
        }

        //2.需要count计数器,模拟报数
        int count = 0;
        //3.遍历循环链表
        //记录每次遍历拿到的结点,默认从首结点开始
        Node n = first;
        //记录当前结点的上一个结点
        Node before = null;
        while (n != n.next) {
            //模拟报数
            count++;
            //判断当前报数是不是为3
            if (count == 3) {
                //如果是3,则把当前结点删除调,打印当前节点,重置count=0,让当前结点n后移
                before.next = n.next;
                System.out.print(n.item + ",");
                n = n.next;
            } else {
                //如果不是3,让before变成当前结点,让当前结点后移
                before = n;
                n = n.next;
            }
        }

        //打印最后一个结点
        System.out.println(n.item);
    }

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

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

15.栈的API设计

public class Stack implements Iterable {
    //记录头结点
    private Node head;
    //记录链表的长度
    private int N;

    //结点类
    private class Node {
        //存储数据
        T item;
        //下一个结点
        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 N == 0;
    }

    //获取栈中元素的个数
    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;
    }

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

    private class LIterator implements Iterator {
        private Node n;

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

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

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

16.栈案例括号匹配问题

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

    //判断str中的括号是否匹配
    public static boolean isMatch(String str) {
        //1.创建栈对象,用来存储左括号
        Stack chars = new Stack<>();
        //2.从左往右遍历字符串
        for (int i = 0; i < str.length(); i++) {
            String currChar = str.charAt(i) + "";
            //3.判断当前是否为左括号,如果是,则把字符放入栈中
            if (currChar.equals("(")) {
                chars.push(currChar);
            } else if (currChar.equals(")")) {
                //4.继续判断当前字符是否为右括号,如果是,则从栈中弹出一个左括号,并判断弹出的结果是否为null,如果为null
                //证明没有匹配的左括号
                String pop = chars.pop();
                if (pop == null) {
                    return false;
                }
            }
        }

        //判断栈中还有没有剩余的左括号,如果有,则证明左括号不匹配
        if (chars.size() == 0) {
            return true;
        } else {
            return false;
        }
    }
}

17.栈案例逆波兰表达式求值问题

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

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

        //6.得到栈中最后一个元素,就是逆波兰表达式的结果
        int result = oprands.pop();
        return result;
    }
}

18.队列API设计

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

    //结点类
    private class Node {
        //存储数据
        T item;
        //下一个结点
        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 int length() {
        return N;
    }

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

    //向队列中插入元素t
    public void enqueue(T t) {
        if (last == null) {
            //当前尾结点last为null
            last = new Node(t, null);
            head.next = last;
        } else {
            //当前尾结点last不为null
            Node oldLast = last;
            last = new Node(t, null);
            oldLast.next = last;
        }

        //元素个数+1
        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;
    }

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

    private class QIterator implements Iterator {
        private Node n;

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

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

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

19.符号表API设计

public class SymbolTable {
    //记录首结点
    private Node head;
    //记录符号表中元素的个数
    private int N;

    private class Node {
        //键
        public Key key;
        //值
        public Value value;
        //下一个结点
        public 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的键值对,那么只需要找到该结点,替换值为value即可
        Node n = head;
        while (n.next != null) {
            //变换n
            n = n.next;
            //判断n结点存储的键是否为key,如果是,则替换n结点的值
            if (n.key.equals(key)) {
                n.value = value;
                return;
            }
        }

        //如果符号表中不存在键为key的键值对,只需要创建新的结点,保存要插入的键值对,把新结点插入到链表的头部,head.next=新结点即可
        Node newNode = new Node(key, value, null);
        Node oldFirst = head.next;
        newNode.next = oldFirst;
        head.next = newNode;

        //元素个数+1
        N++;
    }

    // 删除符号表中键为key的键值对
    public void delete(Key key) {
        //找到键为key的结点,把该结点从链表中删除
        Node n = head;
        while (n.next != null) {
            //判断n结点的下一个结点的键是否为key,如果是,就删除该结点
            if (n.next.key.equals(key)) {
                n.next = n.next.next;
                N--;
                return;
            }

            //变换n
            n = n.next;
        }
    }

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

20.有序符号表API设计

public class OrderSymbolTable {
    //记录首结点
    private Node head;
    //记录符号表中元素的个数
    private int N;

    private class Node {
        //键
        public Key key;
        //值
        public Value value;
        //下一个结点
        public 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 curr = head.next;
        Node pre = head;
        while (curr != null && key.compareTo(curr.key) > 0) {
            //变换上一个结点和当前结点
            pre = curr;
            curr = curr.next;
        }

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

        //如果当前结点curr的键和要插入的key不一样,把新的结点插入到curr之前
        Node newNode = new Node(key, value, curr);
        pre.next = newNode;

        //元素个数+1
        N++;
    }

    // 删除符号表中键为key的键值对
    public void delete(Key key) {
        //找到键为key的结点,把该结点从链表中删除
        Node n = head;
        while (n.next != null) {
            //判断n结点的下一个结点的键是否为key,如果是,就删除该结点
            if (n.next.key.equals(key)) {
                n.next = n.next.next;
                N--;
                return;
            }

            //变换n
            n = n.next;
        }
    }

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

21.二叉树API设计

public class BinaryTree, Value> {
    // 记录根结点
    private Node root;
    // 记录树中元素的个数
    private int N;

    private class Node {
        //存储键
        public Key key;
        //存储值
        public Value value;
        //记录左子结点
        public Node left;
        //记录右子结点
        public Node right;

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

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

    //向树中添加元素key-value
    public void put(Key key, Value value) {
        root = put(root, key, value);
    }

    //向指定的树x中添加key-value,并返回添加元素后新的树
    private Node put(Node x, Key key, Value value) {
        //如果x子树为空
        if (x == null) {
            N++;
            return new Node(key, value, null, null);
        }

        //如果x子树不为空
        //比较x结点的键和key的大小
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {
            //如果key大于x结点的键,则继续找x结点的右子树
            x.right = put(x.right, key, value);
        } else if (cmp < 0) {
            //如果key小于x结点的键,则继续找x结点的左子树
            x.left = put(x.left, key, value);
        } else {
            //如果key等于x结点的键,则替换x结点的值为value即可
            x.value = value;
        }

        return x;
    }

    // 查询树中指定key对应的value
    public Value get(Key key) {
        return get(root, key);
    }

    // 从指定的树x中,查找key对应的值
    public Value get(Node x, Key key) {
        //x树为null
        if (x == null) {
            return null;
        }

        //x树不为null
        //比较key和x结点的键的大小
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {
            //如果key大于x结点的键,则继续找x结点的右子树
            return get(x.right, key);
        } else if (cmp < 0) {
            //如果key小于x结点的键,则继续找x结点的左子树
            return get(x.left, key);
        } else {
            //如果key等于x结点的键,就找到了键为key的结点,只需要返回x结点的值即可
            return x.value;
        }
    }

    //删除树中key对应的value
    public void delete(Key key) {
        delete(root, key);
    }

    // 删除指定树x中的key对应的value,并返回删除后的新树
    private Node delete(Node x, Key key) {
        //x树为null
        if (x == null) {
            return null;
        }

        //x树不为null
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {
            //如果key大于x结点的键,则继续找x结点的右子树
            x.right = delete(x.right, key);
        } else if (cmp < 0) {
            //如果key小于x结点的键,则继续找x结点的左子树
            x.left = delete(x.left, key);
        } else {
            //如果key等于x结点的键,完成真正地删除结点动作,要删除的结点就是x

            //让元素个数-1
            N--;
            //得找到右子树中最小的结点
            if (x.right == null) {
                return x.left;
            }

            if (x.left == null) {
                return x.right;
            }

            Node minNode = x.right;
            while (minNode.left != null) {
                minNode = minNode.left;
            }

            //删除右子树中最小的结点
            Node n = x.right;
            while (n.left != null) {
                if (n.left.left == null) {
                    n.left = null;
                } else {
                    //变换n结点即可
                    n = n.left;
                }
            }

            //让x结点的左子树成为minNode的左子树
            minNode.left = x.left;
            //让x结点的右子树成为minNode的右子树
            minNode.right = x.right;
            //让x结点的父节点指向minNode
            x = minNode;
        }
        return x;
    }

    //查找整个树中最小的键
    public Key min() {
        return min(root).key;
    }

    //在指定树x中找到最小键所在的结点
    private Node min(Node x) {
        //需要判断x还有没有左子结点,如果有,则继续向左找,如果没有,则x就是最小键所在的结点
        if (x.left != null) {
            return min(x.left);
        } else {
            return x;
        }
    }

    //查找整个树中找到最大的键
    public Key max() {
        return max(root).key;
    }

    //在指定树x中找到最大键所在的结点
    private Node max(Node x) {
        //需要判断x还有没有右子结点,如果有,则继续向右找,如果没有,则x就是最大键所在的结点
        if (x.right != null) {
            return max(x.right);
        } else {
            return x;
        }
    }

    // 获取整个树中所有的键
    public Queue preErgodic() {
        Queue keys = new Queue<>();
        preErgodic(root, keys);
        return keys;
    }

    // 获取指定树x的所有键,并放在keys队列中
    private void preErgodic(Node x, Queue keys) {
        if (x == null) {
            return;
        }

        //把x结点的key放入到keys中
        keys.enqueue(x.key);

        //递归遍历x结点的左子树
        if (x.left != null) {
            preErgodic(x.left, keys);
        }
        //递归遍历x结点的右子树
        if (x.right != null) {
            preErgodic(x.right, keys);
        }
    }

    // 使用中序遍历获取整个树中所有的键
    public Queue midErgodic() {
        Queue keys = new Queue<>();
        preErgodic(root, keys);
        return keys;
    }

    // 使用中序遍历,获取指定树x的所有的键,并放在keys队列中
    private void midErgodic(Node x, Queue keys) {
        if (x == null) {
            return;
        }

        //先递归,把左子树中的键放到keys中
        if (x.left != null) {
            midErgodic(x.left, keys);
        }
        //把当前结点x的键放到keys中
        keys.enqueue(x.key);
        //再递归,把右子树中的键放到keys中
        if (x.right != null) {
            midErgodic(x.right, keys);
        }
    }

    // 使用后序遍历获取整个树中所有的键
    public Queue afterErgodic() {
        Queue keys = new Queue<>();
        afterErgodic(root, keys);
        return keys;
    }

    // 使用后序遍历,获取指定树x的所有的键,并放在keys队列中
    private void afterErgodic(Node x, Queue keys) {
        if (x == null) {
            return;
        }

        //先递归,把左子树中的键放到keys中
        if (x.left != null) {
            afterErgodic(x.left, keys);
        }
        //再递归,把右子树中的键放到keys中
        if (x.right != null) {
            afterErgodic(x.right, keys);
        }
        //把当前结点x的键放到keys中
        keys.enqueue(x.key);
    }

    // 使用层序遍历,获取整个树中所有的键
    public Queue layerErgodic() {
        //定义两个队列,分别存储树中的键和树中的结点
        Queue keys = new Queue<>();
        Queue nodes = new Queue<>();

        //默认,往队列中放入跟结点
        nodes.enqueue(root);

        while (!nodes.isEmpty()) {
            //从队列中弹出一个结点,把key放入到keys中
            Node n = nodes.dequeue();
            keys.enqueue(n.key);
            //判断当前结点还有没有左子结点,如果有,则放入到nodes中
            if (n.left != null) {
                nodes.enqueue(n.left);
            }
            //判断当前结点还有没有右子结点,如果有,则放入到nodes中
            if (n.right != null) {
                nodes.enqueue(n.right);
            }
        }
        return keys;
    }

    //获取整个树的最大深度
    public int maxDepth() {
        return maxDepth(root);
    }

    //获取指定树x的最大深度
    private int maxDepth(Node x) {
        if (x == null) {
            return 0;
        }
        //x的最大深度
        int max = 0;
        //左子树的最大深度
        int maxL = 0;
        //右子树的最大深度
        int maxR = 0;
        //计算x结点左子树的最大深度
        if (x.left != null) {
            maxL = maxDepth(x.left);
        }
        //计算x结点右子树的最大深度
        if (x.right != null) {
            maxR = maxDepth(x.right);
        }
        //比较左子树最大深度和右子树最大深度,取较大值+1即可
        max = maxL > maxR ? maxL + 1 : maxR + 1;
        return max;
    }
}

22.二叉树折纸问题

public class PagerFoldingTest {

    public static void main(String[] args) {
        //模拟折纸过程,产生树
        Node tree = createTree(2);
        //遍历树,打印每个结点
        printTree(tree);
    }

    // 通过模拟对折N次纸,产生树
    public static Node createTree(int N) {
        //定义根节点
        Node root = null;
        for (int i = 0; i < N; i++) {
            //1.当前是第一次对折
            if (i == 0) {
                root = new Node<>("down", null, null);
                continue;
            }

            //2.当前不是第一次对折
            //定义一个辅助队列,通过层序遍历的思想,找到叶子结点,叶子结点添加子结点
            Queue queue = new Queue<>();
            queue.enqueue(root);
            //循环遍历队列
            while (!queue.isEmpty()) {
                //从队列中弹出一个结点
                Node tmp = queue.dequeue();
                //如果有左子结点,则把左子结点放入到队列中
                if (tmp.left != null) {
                    queue.enqueue(tmp.left);
                }
                //如果有右子结点,则把右子结点放入到队列中
                if (tmp.right != null) {
                    queue.enqueue(tmp.right);
                }
                //如果同时没有左子结点和右子结点,那么证明该结点是叶子结点,只需要给该结点添加左子结点和右子结点即可
                if (tmp.left == null && tmp.right == null) {
                    tmp.left = new Node("down", null, null);
                    tmp.right = new Node("up", null, null);
                }
            }
        }
        return root;
    }

    // 打印树中每个结点到控制台
    public static void printTree(Node root) {
        //需要使用中序遍历完成
        if (root == null) {
            return;
        }
        //打印左子树的每个结点
        if (root.left != null) {
            printTree(root.left);
        }
        //打印当前结点
        System.out.print(root.item + " ");
        //打印右子树的每个结点
        if (root.right != null) {
            printTree(root.right);
        }
    }


    private static class Node {
        public T item;//存储数据
        public Node left;
        public Node right;

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

23.堆API设计

public class Heap> {
    //存储堆中的元素
    private T[] items;
    //记录堆中元素的个数
    private int N;

    public Heap(int capacity) {
        this.items = (T[]) new Comparable[capacity + 1];
        this.N = 0;
    }

    // 判断堆中索引i处的元素是否小于索引j处的元素
    private boolean less(int i, int j) {
        return items[i].compareTo(items[j]) < 0;
    }

    // 交换堆中i索引和j索引处的值
    private void exch(int i, int j) {
        T temp = items[i];
        items[i] = items[j];
        items[j] = temp;
    }

    //往堆中插入一个元素
    public void insert(T t) {
        items[++N] = t;
        swim(N);
    }

    //使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
    private void swim(int k) {
        //通过循环,不断的比较当前结点的值和其父结点的值,如果发现父结点的值比当前结点的值小,则交换位置
        while (k > 1) {
            //比较当前结点和其父结点
            if (less(k / 2, k)) {
                exch(k / 2, k);
            }

            k = k / 2;
        }
    }

    // 删除堆中最大的元素,并返回这个最大元素
    public T delMax() {
        T max = items[1];

        //交换索引1处的元素和最大索引处的元素,让完全二叉树中最右侧的元素变为临时根节点
        exch(1, N);
        //最大索引处的元素删除掉
        items[N] = null;
        //元素个数-1
        N--;
        //通过下沉调整堆,让堆重新有序
        sink(1);
        return max;
    }

    //使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
    private void sink(int k) {
        //通过循环不断地对比当前k结点和其左子结点2*k以及右子结点2k+1中的较大值的元素大小,如果
        //当前结点小,则需要交换位置
        while (2 * k < N) {
            //获取当前结点的子结点中的较大结点
            int max;//记录较大结点所在的索引
            if (2 * k + 1 <= N) {
                if (less(2 * k, 2 * k + 1)) {
                    max = 2 * k + 1;
                } else {
                    max = 2 * k;
                }
            } else {
                max = 2 * k;
            }

            //比较当前结点和较大结点的值
            if (!less(k, max)) {
                break;
            }

            //交换k索引处的值和max索引处的值
            exch(k, max);
            // 变换k的值
            k = max;
        }
    }
}

24.堆排序

public class HeapSort {
    //判断heap堆中索引i处的元素是否小于索引j处的元素
    private static boolean less(Comparable[] heap, int i, int j) {
        return heap[i].compareTo(heap[j]) < 0;
    }

    //交换heap堆中i索引和j索引处的值
    private static void exch(Comparable[] heap, int i, int j) {
        Comparable tmp = heap[i];
        heap[i] = heap[j];
        heap[j] = tmp;
    }

    // 根据原数组source,构造出堆heap
    private static void createHeap(Comparable[] source, Comparable[] heap) {
        //把source中的元素拷贝到heap中,heap中的元素就形成了一个无序的堆
        System.arraycopy(source, 0, heap, 1, source.length);

        //对堆中的元素做下沉调整(从长度一半处开始,往索引1处扫描)
        for (int i = (heap.length / 2); i > 0; i--) {
            sink(heap, i, heap.length - 1);
        }
    }

    //对source数组中的数据从小到大排序
    public static void sort(Comparable[] source) {
        //构建堆
        Comparable[] heap = new Comparable[source.length + 1];
        createHeap(source, heap);
        //定义一个变量,记录未排序的元素中最大的索引
        int N = heap.length - 1;
        //通过循环,交换1索引处的元素和未排序的元素中最大的索引处的元素
        while (N != 1) {
            //交换元素
            exch(heap, 1, N);
            //未排序交换后最大元素所在的索引,让它不要参与堆的下沉调整
            N--;
            //需要对索引1处的元素进行下沉调整
            sink(heap, 1, N);
        }
        //把heap中的数据复制到原数组source中
        System.arraycopy(heap, 1, source, 0, source.length);
    }

    // 在heap堆中,对target处的元素做下沉,范围是0~range
    private static void sink(Comparable[] heap, int target, int range) {
        while (2 * target <= range) {
            //1.找出当前结点较大的子结点
            int max;
            if (2 * target + 1 <= range) {
                if (less(heap, 2 * target, 2 * target + 1)) {
                    max = 2 * target + 1;
                } else {
                    max = 2 * target;
                }
            } else {
                max = 2 * target;
            }

            //2.比较当前结点的值和较大子结点的值
            if (!less(heap, target, max)) {
                break;
            }

            exch(heap, target, max);
            target = max;
        }
    }
}

25.最大优先队列

public class MaxPriorityQueue> {
    //存储堆中的元素
    private T[] items;
    //记录堆中元素个数
    private int N;

    public MaxPriorityQueue(int capacity) {
        this.items = (T[]) new Comparable[capacity + 1];
        this.N = 0;
    }

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

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

    //判断堆中索引i处的元素是否小于索引j处的元素
    private boolean less(int i, int j) {
        return items[i].compareTo(items[j]) < 0;
    }

    //交换堆中i索引和j索引处的值
    private void exch(int i, int j) {
        T temp = items[i];
        items[i] = items[j];
        items[j] = temp;
    }

    //往堆中插入一个元素
    public void insert(T t) {
        items[++N] = t;
        swim(N);
    }

    //删除堆中最大的元素,并返回这个最大元素
    public T delMax() {
        T max = items[1];

        //交换索引1处的元素和最大索引处的元素,让完全二叉树中最右侧的元素变为临时根节点
        exch(1, N);
        //最大索引处的元素删除掉
        items[N] = null;
        //元素个数-1
        N--;
        //通过下沉调整堆,让堆重新有序
        sink(1);
        return max;
    }

    //使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
    private void swim(int k) {
        while (k > 1) {
            //比较当前结点和其父结点
            if (less(k / 2, k)) {
                exch(k / 2, k);
            }

            k = k / 2;
        }
    }

    //使用下浮算法,使索引k处的元素能在堆中处于一个正确的位置
    private void sink(int k){
        while (2 * k < N) {
            //获取当前结点的子结点中的较大结点
            int max;//记录较大结点所在的索引
            if (2 * k + 1 <= N) {
                if (less(2 * k, 2 * k + 1)) {
                    max = 2 * k + 1;
                } else {
                    max = 2 * k;
                }
            } else {
                max = 2 * k;
            }

            //比较当前结点和较大结点的值
            if (!less(k, max)) {
                break;
            }

            //交换k索引处的值和max索引处的值
            exch(k, max);
            // 变换k的值
            k = max;
        }
    }
}

26.最小优先队列

public class MinPriorityQueue> {
    //存储堆中的元素
    private T[] items;
    //记录堆中元素个数
    private int N;

    public MinPriorityQueue(int capacity) {
        this.items = (T[]) new Comparable[capacity + 1];
        this.N = 0;
    }

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

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

    //判断堆中索引i处的元素是否小于索引j处的元素
    private boolean less(int i, int j) {
        return items[i].compareTo(items[j]) < 0;
    }

    //交换堆中i索引和j索引处的值
    private void exch(int i, int j) {
        T temp = items[i];
        items[i] = items[j];
        items[j] = temp;
    }

    //往堆中插入一个元素
    public void insert(T t) {
        items[++N] = t;
        swim(N);
    }

    //删除堆中最小的元素,并返回这个最小元素
    public T delMin() {
        T min = items[1];
        exch(1, N);
        N--;
        sink(1);
        return min;
    }

    //使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
    private void swim(int k) {
        //通过循环比较当前结点和其父结点的大小
        while (k > 1) {
            if (less(k, k / 2)) {
                exch(k, k / 2);
            }
            k = k / 2;
        }
    }

    //使用下浮算法,使索引k处的元素能在堆中处于一个正确的位置
    private void sink(int k) {
        //通过循环比较当前结点和其子结点中的较小值
        while (2 * k <= N) {
            //1.找到子结点的较小值
            int min;
            if (2 * k + 1 <= N) {
                if (less(2 * k, 2 * k + 1)) {
                    min = 2 * k;
                } else {
                    min = 2 * k + 1;
                }
            } else {
                min = 2 * k;
            }

            //2.判断当前结点和较小值的大小
            if (less(k, min)) {
                break;
            }
            exch(k, min);
            k = min;
        }
    }
}

27.索引优先队列

public class IndexMinPriorityQueue> {
    //存储堆中的元素
    private T[] items;
    //保存每个元素在items数组中的索引,pa数组需要堆有序
    private int[] pq;
    //保存qp的逆序,pq的值作为索引,pq的索引作为值
    private int[] qp;
    //记录堆中元素个数
    private int N;

    public IndexMinPriorityQueue(int capacity) {
        this.items = (T[]) new Comparable[capacity + 1];
        this.pq = new int[capacity + 1];
        this.qp = new int[capacity + 1];
        this.N = 0;

        //默认情况下,队列中没有存储任何数据,让qp中的元素都为-1
        for (int i = 0; i < qp.length; i++) {
            qp[i] = -1;
        }
    }

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

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

    //判断堆中索引i处的元素是否小于索引j处的元素
    private boolean less(int i, int j) {
        return items[pq[i]].compareTo(items[pq[j]]) < 0;
    }

    //交换堆中i索引和j索引处的值
    private void exch(int i, int j) {
        //交换pq中的数据
        int tmp = pq[i];
        pq[i] = pq[j];
        pq[j] = tmp;

        //交换qp中的数据
        qp[pq[i]] = i;
        qp[pq[j]] = j;
    }

    //判断k对应的元素是否存在
    public boolean contains(int k) {
        return qp[k] != -1;
    }

    //最小元素关联的索引
    public int minIndex() {
        return pq[1];
    }

    //往队列中插入一个元素,并关联索引i
    public void insert(int i, T t) {
        // 判断i是否已经被关联,如果已经被关联,则不让插入
        if (contains(i)) {
            return;
        }
        //元素个数+1
        N++;
        //把数据存储到items对应的i位置处
        items[i] = t;
        //把i存储到pq中
        pq[N] = i;
        //通过qp记录pq中的i
        qp[i] = N;
        //通过堆上浮完成堆的调整
        swim(N);
    }

    //删除堆中最小的元素,并返回该元素关联的索引
    public int delMin() {
        //获取最小元素关联的索引
        int minIndex = pq[1];
        //交换pq中索引1处和最大索引处的元素
        exch(1, N);
        //删除qp中对应的内容
        qp[pq[N]] = -1;
        //删除pq最大索引处的内容
        pq[N] = -1;
        //删除items中对应的内容
        items[minIndex] = null;
        //元素个数-1
        N--;
        //下沉调整
        sink(1);

        return minIndex;
    }

    //删除索引i关联的元素
    public void delete(int i) {
        //找到i在pq中的索引
        int k = qp[i];
        //交换pq中索引k处的值和索引N处的值
        exch(k, N);
        //删除qp中的内容
        qp[pq[N]] = -1;
        //删除pq中的内容
        pq[N] = -1;
        //删除items中的内容
        items[k] = null;
        //元素个数-1
        N--;
        //堆的调整
        sink(k);
        swim(k);
    }

    //把与索引i关联的元素修改为t
    public void changeItem(int i, T t) {
        //修改items数组中i位置的元素t
        items[i] = t;
        //找到i在pq中出现的位置
        int k = qp[i];
        //堆调整
        sink(k);
        swim(k);
    }

    //使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
    private void swim(int k) {
        while (k > 1) {
            if (less(k, k / 2)) {
                exch(k, k / 2);
            }

            k = k / 2;
        }
    }

    //使用下浮算法,使索引k处的元素能在堆中处于一个正确的位置
    private void sink(int k) {
        while (2 * k <= N) {
            //找到子结点中的较小值
            int min;
            if (2 * k + 1 <= N) {
                if (less(2 * k, 2 * k + 1)) {
                    min = 2 * k;
                } else {
                    min = 2 * k + 1;
                }
            } else {
                min = 2 * k;
            }
            //比较当前结点和较小值
            if (less(k, min)) {
                break;
            }
            exch(k, min);
            k = min;
        }
    }
}

28.红黑树API设计

public class RedBlackTree, Value> {
    //根节点
    private Node root;
    //记录树中元素个数
    private int N;
    //红色链接
    private static final boolean RED = true;
    //黑色链接
    private static final boolean BLACK = false;

    //结点类
    private class Node {
        //存储键
        public Key key;
        //存储值
        public Value value;
        //记录左子结点
        public Node left;
        //记录右子结点
        public Node right;
        //由其父结点指向它的链接的颜色
        public boolean color;

        public Node(Key key, Value value, Node left, Node right, boolean color) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
            this.color = color;
        }
    }

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

    //判断当前结点的父指向链接是否为红色
    private boolean isRed(Node x) {
        if (x == null) {
            return false;
        }
        return x.color == RED;
    }

    //左旋
    private Node rotateLeft(Node h) {
        //获取h结点的右子结点,表示为x
        Node x = h.right;
        //让x结点的左子结点成为h结点的右子结点
        h.right = x.left;
        //让h成为x结点的左子结点
        x.left = h;
        //让x结点的color属性等于h结点的color属性
        x.color = h.color;
        //让h结点的color属性变成红色
        h.color = RED;
        return x;
    }

    //右旋
    private Node rotateRight(Node h) {
        //获取h结点的左子结点,表示为x
        Node x = h.left;
        //让x结点的右子结点成为h结点的左子结点
        h.left = x.right;
        //让h成为x结点的左子结点
        x.right = h;
        //让x结点的color属性等于h结点的color属性
        x.color = h.color;
        //让h结点的color属性变成红色
        h.color = RED;
        return x;
    }

    //颜色反转,相当于完成拆分4-结点
    private void flipColors(Node h) {
        //当前结点变成红色
        h.color = RED;
        //左子结点和右子结点变为黑色
        h.left.color = BLACK;
        h.right.color = BLACK;
    }

    //在整个树上完成操作
    public void put(Key key, Value val) {
        put(root, key, val);
        //根结点的颜色总是黑色
        root.color = BLACK;
    }

    //在指定树中,完成插入操作,并返回添加元素后新的树
    public Node put(Node h, Key key, Value val) {
        //判断h是否为为空,如果为空则直接返回一个红色的结点就可以了
        if (h == null) {
            //数量+1
            N++;
            return new Node(key, val, null, null, RED);
        }

        //比较h结点的键和key的大小
        int cmp = key.compareTo(h.key);
        if (cmp < 0) {
            //继续往左
            h.left = put(h.left, key, val);
        } else if (cmp > 0) {
            //继续往右
            h.right = put(h.right, key, val);
        } else {
            //发送值的替换
            h.value = val;
        }

        //进行左旋,当当前结点h的左子结点为黑色,右子结点为红色,需要左旋
        if (isRed(h.right) && !isRed(h.left)) {
            h = rotateLeft(h);
        }

        //进行右旋,当当前结点h的左子结点和左子结点的左子结点都为红色,需要右旋
        if (isRed(h.left) && isRed(h.left.left)) {
            h = rotateRight(h);
        }

        //颜色反转,当前结点的左子结点和右子结点都为红色时,需要颜色反转
        if (isRed(h.left) && isRed(h.right)) {
            flipColors(h);
        }

        return h;
    }

    //根据key,从树中找出对应的值
    public Value get(Key key) {
        return get(root, key);
    }

    //从指定的树x中,查找key对应的值
    public Value get(Node x, Key key) {
        if (x == null) {
            return null;
        }

        //比较x结点的键和key的大小
        int cmp = key.compareTo(x.key);
        if (cmp < 0) {
            return get(x.left, key);
        } else if (cmp > 0) {
            return get(x.right, key);
        } else {
            return x.value;
        }
    }
}

29.并查集API设计

public class UF {
    //记录结点元素和该元素所在分组的标识
    private int[] eleAndGroup;
    //记录并查集中数据的分组个数
    private int count;

    //初始化并查集
    public UF(int N) {
        //初始化分组的数量,默认情况下有N个分组
        this.count = N;

        //初始化eleAndGroup分组
        this.eleAndGroup = new int[N];
        //初始化eleAndGroup中的元素及其所在的组的标识符
        //让eleAndGroup数组的索引作为并查集的每个结点的元素,并且让每个索引处的值(该元素所在组的标识符)就是该索引
        for (int i = 0; i < eleAndGroup.length; i++) {
            eleAndGroup[i] = i;
        }
    }

    //获取当前并查集中的数据有多少个分组
    public int count() {
        return count;
    }

    //元素p所在分组的标识符
    public int find(int p) {
        return eleAndGroup[p];
    }

    //判断并查集中元素p和元素q是否在同一分组中
    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }

    //把p元素所在分组和q元素所在分组合并
    public void union(int p, int q) {
        //判断元素p和q是否已经在同一分组中,如果已经在同一分组中,则结束方法就可以了
        if (connected(p, q)) {
            return;
        }

        //找到p所在分组的标识符
        int pGroup = find(p);
        //找到q所在分组的标识符
        int qGroup = find(q);
        //合并组,让p所在组的所有元素的组标识符变成q所在分组的标识符
        for (int i = 0; i < eleAndGroup.length; i++) {
            if (eleAndGroup[i] == pGroup) {
                eleAndGroup[i] = qGroup;
            }
        }

        //分组个数-1
        this.count--;
    }

    public static void main(String[] args) {
        //创建并查集对象
        UF uf = new UF(5);
        System.out.println("默认情况下,并查集中有:" + uf.count() + "个分组");
        //从控制台录入两个要合并的元素,调用union方法合并,观察合并后并查集中的分组是否减少
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请输入第一个要合并的元素:");
            int p = sc.nextInt();
            System.out.println("请输入第二个要合并的元素:");
            int q = sc.nextInt();
            //判断这两个元素是否已经在同一组了
            if (uf.connected(p, q)) {
                System.out.println(p + "元素和" + "q" + "元素已经在同一个组中了");
                continue;
            }
            uf.union(p, q);
            System.out.println("当前并查集中还有:" + uf.count() + "个分组");
        }
    }
}

30.UF_Tree API设计

public class UF_Tree {
    //记录结点元素和该元素所在分组的标识
    private int[] eleAndGroup;
    //记录并查集中数据的分组个数
    private int count;

    //初始化并查集
    public UF_Tree(int N) {
        //初始化分组的数量,默认情况下有N个分组
        this.count = N;

        //初始化eleAndGroup分组
        this.eleAndGroup = new int[N];
        //初始化eleAndGroup中的元素及其所在的组的标识符
        //让eleAndGroup数组的索引作为并查集的每个结点的元素,并且让每个索引处的值(该元素所在组的标识符)就是该索引
        for (int i = 0; i < eleAndGroup.length; i++) {
            eleAndGroup[i] = i;
        }
    }

    //获取当前并查集中的数据有多少个分组
    public int count() {
        return count;
    }

    //判断并查集中元素p和元素q是否在同一分组中
    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }

    //元素p所在分组的标识符
    public int find(int p) {
        while (true) {
            if (p == eleAndGroup[p]) {
                return p;
            }

            p = eleAndGroup[p];
        }
    }

    //把p元素所在分组和q元素所在分组合并
    public void union(int p, int q) {
        //找到p元素和q元素所在组对应的树的根结点
        int pRoot = find(p);
        int qRoot = find(q);
        //如果p和q已经在同一分组,则不需要合并了
        if (pRoot == qRoot) {
            return;
        }

        //让p所在的树的根结点的父结点为q所在树的根结点即可
        eleAndGroup[pRoot] = qRoot;
        //组的数量-1
        this.count--;
    }

    public static void main(String[] args) {
        //创建并查集对象
        UF_Tree uf = new UF_Tree(5);
        System.out.println("默认情况下,并查集中有:" + uf.count() + "个分组");
        //从控制台录入两个要合并的元素,调用union方法合并,观察合并后并查集中的分组是否减少
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请输入第一个要合并的元素:");
            int p = sc.nextInt();
            System.out.println("请输入第二个要合并的元素:");
            int q = sc.nextInt();
            //判断这两个元素是否已经在同一组了
            if (uf.connected(p, q)) {
                System.out.println(p + "元素和" + "q" + "元素已经在同一个组中了");
                continue;
            }
            uf.union(p, q);
            System.out.println("当前并查集中还有:" + uf.count() + "个分组");
        }
    }

}

31.UF_Tree_Weighted API设计

public class UF_Tree_Weighted {
    //记录结点元素和该元素所在分组的标识
    private int[] eleAndGroup;
    //记录并查集中数据的分组个数
    private int count;

    //用来存储每一个根结点对应的树中保存的结点的个数
    private int[] sz;

    //初始化并查集
    public UF_Tree_Weighted(int N) {
        //初始化分组的数量,默认情况下有N个分组
        this.count = N;

        //初始化eleAndGroup分组
        this.eleAndGroup = new int[N];
        //初始化eleAndGroup中的元素及其所在的组的标识符
        //让eleAndGroup数组的索引作为并查集的每个结点的元素,并且让每个索引处的值(该元素所在组的标识符)就是该索引
        for (int i = 0; i < eleAndGroup.length; i++) {
            eleAndGroup[i] = i;
        }

        this.sz = new int[N];
        //默认情况下,sz中每个索引处的值都是1
        for (int i = 0; i < sz.length; i++) {
            sz[i] = 1;
        }
    }

    //获取当前并查集中的数据有多少个分组
    public int count() {
        return count;
    }

    //判断并查集中元素p和元素q是否在同一分组中
    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }

    //元素p所在分组的标识符
    public int find(int p) {
        while (true) {
            if (p == eleAndGroup[p]) {
                return p;
            }

            p = eleAndGroup[p];
        }
    }

    //把p元素所在分组和q元素所在分组合并
    public void union(int p, int q) {
        //找到p元素和q元素所在组对应的树的根结点
        int pRoot = find(p);
        int qRoot = find(q);
        //如果p和q已经在同一分组,则不需要合并了
        if (pRoot == qRoot) {
            return;
        }

        //判断proot对应的树大还是qroot对应的树大,最终需要把较小的树合并到较大的树中
        if (sz[pRoot] < sz[qRoot]) {
            eleAndGroup[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        } else {
            eleAndGroup[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }

        //组的数量-1
        this.count--;
    }

    public static void main(String[] args) {
        //创建并查集对象
        UF_Tree_Weighted uf = new UF_Tree_Weighted(5);
        System.out.println("默认情况下,并查集中有:" + uf.count() + "个分组");
        //从控制台录入两个要合并的元素,调用union方法合并,观察合并后并查集中的分组是否减少
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请输入第一个要合并的元素:");
            int p = sc.nextInt();
            System.out.println("请输入第二个要合并的元素:");
            int q = sc.nextInt();
            //判断这两个元素是否已经在同一组了
            if (uf.connected(p, q)) {
                System.out.println(p + "元素和" + "q" + "元素已经在同一个组中了");
                continue;
            }
            uf.union(p, q);
            System.out.println("当前并查集中还有:" + uf.count() + "个分组");
        }
    }
}

32.案例-畅通工程

public class Traffic_Project_Test {
    public static void main(String[] args) throws Exception {
        //构建一个缓冲读取流
        BufferedReader br = new BufferedReader(
                new InputStreamReader(Traffic_Project_Test.class.getClassLoader().getResourceAsStream("traffic_project.txt")));

        //读取第一行数据20
        int totalNumber = Integer.parseInt(br.readLine());

        //构建一个并查集对象
        UF_Tree_Weighted uf = new UF_Tree_Weighted(totalNumber);
        //读取第二行数据7
        int roadNumber = Integer.parseInt(br.readLine());
        //循环读取7条道路
        for (int i = 1; i <= roadNumber; i++) {
            String line = br.readLine();//0 1
            String[] str = line.split(" ");
            int p = Integer.parseInt(str[0]);
            int q = Integer.parseInt(str[1]);
            //调用并查集对象的union方法让两个城市相通
            uf.union(p, q);
        }

        //获取当前并查集中分组的数量-1就可以得到还需要修建的道路的数目
        int roads = uf.count() - 1;
        System.out.println("还需要建:" + roads + "条道路,才能实现畅通工程");
    }
}

你可能感兴趣的:(算法)