2.线性表

一、顺序表

顺序表是计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素,使得线性表中在逻辑结构上相邻的元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。

1.顺序表的实现

1)顺序表API的设计:
类名SequenceList
构造方法SequenceList(int capacity):创建容量为capacity的SequenceList对象
成员方法1.public void clear():空置线性表
2.public boolean isEmpty():判断线性表是否为空,是返回true,否返回false
3.public int length():获取线性表中元素的个数
4.public T get(int i):读取并返回线性表中的第 i 个元素的值
5.public void insert(int i, T t):在线性表的第 i 个元素之前插入一个值为t的数据元素
6.public void insert(T t):向线性表中添加一个元素t
7.public T remove(int i):删除并返回线性表中第i个数据元素
8.public int indexOf(T t):返回线性表中首次出现的指定的数据元素的序号,若不存在,则返回-1
成员变量1.private T[] eles :存储元素的数组
2.private int N :当前线性表的长度
2)SequenceList.java
package List.No1_sequenceList;

public class SequenceList<T> {
    //存储元素的数组
    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){
        eles[N++] = t;
    }
    public void insert(int i,T t){
        //先把i索引处的元素及其后面的元素依次向后移动一位
        for(int index=N;index>i;index--){
            eles[index] = eles[index-1];
        }
        //再把t元素放到i索引处即可
        eles[i] = t;

        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];
        }
        N--;
        return current;
    }
    //查找t元素第一次出现的位置
    public int indexOf(T t){
        for (int i=0;i<N;i++){
            if(eles[i].equals(t)){
                return i;
            }
        }
        return -1;
    }
}
3)test.java
package List.No1_sequenceList;

public class test {
    public static void main(String[] args) {
        SequenceList<String> s1 = new SequenceList<>(10);
        s1.insert("xiaoguan");
        s1.insert("peiling");
        s1.insert("dandan");
        s1.insert(1,"maojiang");

        String getResult = s1.get(1);
        System.out.println("获取索引1处的结果为:"+getResult);

        String removeResut = s1.remove(0);
        System.out.println("删除的元素是"+removeResut);

        s1.clear();
        System.out.println("清空后的线性表中的元素为"+s1.length());
    }
}

2.顺序表的遍历

一般作为容器存储数据,都需要向外部提供遍历的方式,因此我们需要给顺序表提供遍历方式

在java中,遍历集合的方式一般都是用的foreach循环,如果想让我们的SequenceList也能支持foreach循环,则需要做如下操作:

  1. 让SequenceList实现Iterable接口,重写iterator()方法
  2. 在SequenceList内部提供一个内部类SIterator,实现Iterator接口,重写hasNext方法和next方法
package List.No1_sequenceList;

import java.util.Iterator;

public class SequenceList<T> implements Iterable<T>{
    //省略在上面
    @Override
    public Iterator<T> iterator() {
        return new SIterator();
    }
    private class SIterator implements Iterator{
        private int cusor;
        public SIterator(){
            this.cusor = 0;
        }
        @Override
        public boolean hasNext() {
            return cusor<N;
        }

        @Override
        public Object next() {
            return eles[cusor++];
        }
    }
}

test

SequenceList<String> s1 = new SequenceList<>(10);
        for (String s : s1) {
            System.out.println(s);
        }

3.顺序表的扩容

  //向线性表中添加元素t
    public void insert(T t){
        if (N==eles.length){
            resize(2*eles.length);
        }
        eles[N++] = t;
    }
    //在i元素处插入元素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;

        N++;
    }
	//根据参数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];
        }
    }

二、链表

1.链表的实现

1)结点API设计:
类名Node
构造方法Node(T t,Node next):创建Node对象
成员变量T item :存储数据
Node next:指向下一个结点
2)结点类的实现:
public class Node<T>{
    //存储元素
    public T item;
    //存储下一个结点
    public Node next;
    public Node(T item,Node netx){
        this.item = item;
        this.next = next;
    }
}
3)生成链表:
public static void main(String[] args)throws Exception{
    //构造结点
    Node<Integer> first = new Node<Integer>(11,null);
    Node<Integer> second = new Node<Integer>(13,null);
    Node<Integer> third = new Node<Integer>(12,null);
    Node<Integer> fourth = new Node<Integer>(8,null);
    Node<Integer> fifth = new Node<Integer>(9,null);
    //生成链表
    first.next = second;
    second.next = third;
    third.next = fourth;
    fourth.next = fifth;
}

2.单向链表

1)单向链表API设计
类名LinkList
构造方法LinkList():创建LinkList对象
成员方法1.public void clear():空置线性表
2.public boolean IsEmpty():判断线性表是否为空,是返回true,否返回false
3.public int length():获取线性表中元素的个数
4.public T get(int i):读取并返回线性表中的第 i 个元素的值
5.public void insert(T t):往线性表中添加一个元素
6.public void insert(int i,T t):在线性表的第 i 个元素之前插入一个值为t的数据元素
7.public T remove(int i):删除并返回线性表中第 i 个数据元素
8.public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
成员内部类private class Node:结点类
成员变量1.private Node head:记录首结点
2.private int N:记录链表的长度
2)LinkList.java
package List.linear;

import java.util.Iterator;

public class LinkList<T> implements Iterable<T>{
    //记录头结点
    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 LinkList(){
        //初始化头结点
        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;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;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;
    }

    @Override
    public Iterator<T> 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;
        }
    }
}
3)test.java
package List.linear;

import List.No1_sequenceList.SequenceList;

public class test {
    public static void main(String[] args) {
        LinkList<String> s1 = new LinkList<>();
        s1.insert("xiaoguan");
        s1.insert("peiling");
        s1.insert("dandan");
        s1.insert(1,"maojiang");

        for (String s : s1) {
            System.out.println(s);
        }
        System.out.println("--------");
        String getResult = s1.get(1);
        System.out.println("获取索引1处的结果为:"+getResult);

        String removeResut = s1.remove(0);
        System.out.println("删除的元素是"+removeResut);

        s1.clear();
        System.out.println("清空后的线性表中的元素为"+s1.length());
    }
}

3.双向链表

1)结点API设计
类名Node
构造方法Node(T t,Node pre,Node next):创建Node对象
成员变量T item:存储数据
Node next:指向下一个结点
Node pre:指向上一个结点
2)双向链表API设计
类名TowWayLinkList
构造方法TowWayLinkList():创建TowWayLinkList对象
成员方法1.public void clear():空置线性表
2.public boolean IsEmpty():判断线性表是否为空,是返回true,否返回false
3.public int length():获取线性表中元素的个数
4.public T get(int i):读取并返回线性表中的第 i 个元素的值
5.public void insert(T t):往线性表中添加一个元素
6.public void insert(int i,T t):在线性表的第 i 个元素之前插入一个值为t的数据元素
7.public T remove(int i):删除并返回线性表中第 i 个数据元素
8.public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
9.public T getFirst():获取第一个元素
10.public T getLast():获取最后一个元素
成员内部类private class Node:结点类
成员变量1.private Node first:记录首结点
2.private Node last:记录尾结点
3.private int N:记录链表的长度
3)TowWayLinkList.java
package List.linear;

import java.util.Iterator;

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

    //结点类
    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;
    }

    public TowWayLinkList(){
        //初始化头结点和尾结点
        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<i;index++){
            pre = pre.next;
        }
        //找到i位置的结点
        Node curr = pre.next;
        //创建新结点
        Node newNode = new Node(t,pre,curr);
        //让 i 位置的前一个结点的下一个结点变为新结点
        pre.next = newNode;
        //让 i 位置的前一个结点变为新结点
        curr.pre = newNode;
        //元素个数+1
        N++;
    }
    //获取指定位置 i 处的元素
    public T get(int i){
        Node n = head.next;
        for (int index=0;index<i;index++){
            n = n.next;
        }
        return n.item;
    }
    //找到元素 t 在链表中第一次出现的位置
    public int indexOf(T t){
        Node n =head;
        for (int i=0;n.next!=null;i++){
            n=n.next;
            if (n.next.equals(t)) {
                return i;
            }
        }
        return -1;
    }
    //删除位置 i 处的元素,并返回该元素
    public T remove(int i){
        //找到i位置的前一个结点
        Node pre =head;
        for (int index=0;index<i;index++){
            pre = pre.next;
        }
        //找到i位置的结点
        Node curr = pre.next;
        //找到i位置的下一个结点
        Node nextNode = curr.next;
        //让i位置的前一个结点的下一个结点变为i位置的下一个结点
        pre.next = nextNode;
        //让i位置的下一个结点的上一个结点变为i位置的前一个结点
        nextNode.pre = pre;
        //元素个数-1
        N--;
        return curr.item;
    }

    @Override
    public Iterator<T> iterator() {
        return new TIterator();
    }
    private class TIterator implements Iterator{
        private Node n;
        public TIterator(){
            this.n = head;
        }
        @Override
        public boolean hasNext() {
            return n.next!=null;
        }

        @Override
        public Object next() {
            n = n.next;
            return n.item;
        }
    }
}
4)test.java
package List.linear;

public class TowWayTest {
    public static void main(String[] args) {
        TowWayLinkList<String> s1 = new TowWayLinkList<>();
        s1.insert("xiaoguan");
        s1.insert("peiling");
        s1.insert("dandan");
        s1.insert(1,"maojiang");

        for (String s : s1) {
            System.out.println(s);
        }
        System.out.println("--------");
        String getResult = s1.get(1);
        System.out.println("获取索引1处的结果为:"+getResult);

        String removeResut = s1.remove(0);
        System.out.println("删除的元素是"+removeResut);


        System.out.println("--------");
        System.out.println("第一个元素是:"+s1.getFirst());
        System.out.println("最后一个元素是:"+s1.getLast());
        s1.clear();
        System.out.println("清空后的线性表中的元素为"+s1.length());

        System.out.println("--------");
        System.out.println("第一个元素是:"+s1.getFirst());
        System.out.println("最后一个元素是:"+s1.getLast());
    }
}

4.链表的时间复杂度分析

get(int i):每一次查询,都需要从链表的头部开始,依次向后查找,随着数据元素N的增多,比较的元素越多,时间复杂度为O(n)

insert(int i,T t):每一次插入,需要先找到 i 位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n)

remove(int i):每一次移除,需要先找到 i 位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n)

相比较顺序表,链表插入和删除的时间复杂度虽然一样,但任然有很大的优势,因为链表的物理地址是不连续的,它不需要预先指定存储空间大小,或者在存储过程中涉及到扩容等操作,同时它并没有涉及到元素的交换

相比顺序表,链表的查询操作性能会比较低。因此,如果我们的程序查询操作比较多,建议使用顺序表,增删操作比较多,建议使用链表。

5.链表反转

需求:

原链表中数据为:1->2->3->4

反转后链表中数据为:4->3->2->1

反转API:

public void reverse():对整个链表反转
public Node reverse(Node curr):反转链表中的某个结点curr,并把反转后的curr结点返回

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

    //用来反转整个链表
    public void reverse(){
        //判断当前链表是否为空
        if (isEmpty()){
            return;
        }
        reverse(head.next);
    }
    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;
    }

6.快慢指针

快慢指针指的是定义两个指针,这两个指针的移动一块一慢,以此来制造出自己想要的差值,这个差值可以让我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍。

1)中间值问题

需求:找出链表的中间元素并返回。

利用快慢指针,我们把一个链表看成一个跑道,假设a的速度是b的两倍,那么当a跑完全程后,b刚好跑一半,以此来达到找到中间结点的目的。

package List.linear;

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

        public Node(T item,Node next){
            this.item = item;
            this.next = next;
        }
    }
    public static void main(String[] args) {

        //创建结点
        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);
    }
    /*
    * @param firt 链表的首结点
    * @return 链表的中间结点的值
    * */
    public static String getMid(Node<String> first){
        //定义两个指针
        Node<String> fast = first;
        Node<String> slow = first;
        //使用两个指针遍历链表,当快指针指向的结点没有下一个结点了,就可以结束了
        //结束之后,慢指针指向的结点就是MID
        while (fast!=null&&fast.next!=null){
            //变化fast和slow的值
            fast = fast.next;
            if (fast.next==null){
                break;
            }
            fast = fast.next;
            slow = slow.next;
        }
        return slow.item;
    }
}
2)单向链表是否有环问题
package List.linear;

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

        public Node(T item,Node next){
            this.item = item;
            this.next = next;
        }
    }
    public static void main(String[] args) {

        //创建结点
        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;
        //产生环
        seven.next = third;

        //判断是否有环
        boolean circle = isCircle(first);
        System.out.println("first链表中是否有环"+circle);
    }
    /*
    *判断链表中是否有环
    * @param firt 链表的首结点
    * @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;
    }
}
3)有环链表入口问题

需求:查找有环链表中环的入口结点

当快慢指针相遇时,我们可以判断链表中有环,这时重新设定一个新指针指向链表的起点,且步长与快慢指针一样为1,则慢指针与“新”指针相遇的地方就是换的入口。

7.循环链表

循环链表的构建:
public class Test{
    public static void main(String[] args) throws Exception{
        //构建结点
        Node<Integer> first = new Node<Integer>(1,null);
        Node<Integer> seconde = new Node<Integer>(2,null);
        Node<Integer> third = new Node<Integer>(3,null);
        Node<Integer> fourth = new Node<Integer>(4,null);
        Node<Integer> fifth = new Node<Integer>(5,null);
        Node<Integer> six = new Node<Integer>(6,null);
        Node<Integer> seven = new Node<Integer>(7,null);
        
        //构建单链表
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;
        
        //构建循环链表,让最后一个结点指向第一个结点
        seven.next = first;
    }
}

8.约瑟夫问题

问题描述:

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

问题转换:

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

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

解题思路:

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

public class JosephTest {
    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> newNode = new Node<>(i,null);
            pre.next = newNode;
            pre = newNode;
            //如果是最后一个结点,那么需要让最后一个结点的下一个结点变为first
            if (i==41){
                pre.next = first;
            }
        }
        //2.需要count计数器,模拟报数
        int count = 0;
        //3.遍历循环链表
        //记录每次遍历拿到的结点,默认从首结点开始
        Node<Integer> n = first;
        //记录当前结点的上一个结点
        Node<Integer> before = null;
        while (n!=n.next){
            //模拟报数
            count++;
            //判断报数是否为3
            if (count==3){
                //如果是3,则把当前结点删除调用,打印当前结点,重置count=0,让当前结点n后移
                before.next = n.next;
                System.out.print(n.item+",");
                count = 0;
                n=n.next;
            }else {
                //如果不是3,让before变为当前结点,让当前结点后移
                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;
        }
    }
}

三、栈

1.栈的实现

1)栈API设计
类名Stack
构造方法Stack():创建Stack对象
成员方法1.public boolean isEmpty():判断栈是否为空
2.public int size():获取栈中元素的个数
3.public T pop():弹出栈顶元素
4.public void push():向栈中压入元素t
成员变量1.private Node head:记录首结点
2.private int N:当前栈的元素个数
成员内部类private class Node:结点类

Stack.java

package List.stack;

import java.util.Iterator;

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

    private class Node{
        private T item;
        private 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;
    }

    @Override
    public Iterator iterator() {
        return new SIterator();
    }
    private class SIterator implements Iterator{
        private Node n;
        public SIterator(){
            this.n = head;
        }
        @Override
        public boolean hasNext() {
            return n.next!=null;
        }

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

StackTest.java

package List.stack;

public class StackTest {
    public static void main(String[] args) {
        //创建栈对象
        Stack<String> stack = new Stack<>();
        //测试压栈
        stack.push("a");
        stack.push("b");
        stack.push("c");
        stack.push("d");
        stack.push("e");
        stack.push("f");

        for (String item : stack) {
            System.out.println(item);
        }
        System.out.println("--------");
        //测试弹栈

        String result = stack.pop();
        System.out.println("弹出的元素是"+result);
        System.out.println("剩余的元素个数:"+stack.size());
    }
}

2.案例

1)括号匹配问题
package List.stack;

public class BracketsMatchTest {
    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> 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,证明没有括号匹配,如果不为null,则证明有匹配的左括号
                String pop = chars.pop();
                if (pop==null){
                    return false;
                }
            }

        }
        //5.判断栈中还有没有剩余的左括号,如果有,则证明括号不匹配
        if (chars.size()==0){
            return true;
        }else {
            return false;
        }
    }
}
2)逆波兰表达式求值问题

中缀表达式

二元运算符总是置于两个操作数中间

逆波兰表达式(后缀表达式)

运算符总是放在跟它相关的操作数之后

需求:给定一个只包含加减乘除四种运算的逆波兰表达式的数组表示方式,求出该逆波兰表达式的结果。

package List.stack;

public class ReversePolishNotationTest {
    public static void main(String[] args) {
        //中缀表达式 3*(17-15)+18/6的逆波兰表达式如下
        String[] notation = {"3","17","15","-","*","18","6","/","+"};
        int result = caculate(notation);
        System.out.println("逆波兰表达式的结果为:"+result);
    }
    /*
    * @param notation 逆波兰表达式的数组表示方式
    * @return 逆波兰表达式的计算结果
    * */
    public static int caculate(String[] notation){
        //1.定义一个栈,用来存储操作数
        Stack<Integer> 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){
                //4.运算符,从栈中弹出两个操作数,运算完的结果再压入栈中
                case "+":
                    o1 = oprands.pop();
                    o2 = oprands.pop();
                    result = o1+o2;
                    oprands.push(result);
                    break;
                case "-":
                    o1 = oprands.pop();
                    o2 = oprands.pop();
                    result = o2-o1;
                    oprands.push(result);
                    break;
                case "*":
                    o1 = oprands.pop();
                    o2 = oprands.pop();
                    result = o1*o2;
                    oprands.push(result);
                    break;
                case "/":
                    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;
    }
}

四、队列

1.队列的API设计

类名Queue
构造方法Queue():创建Queue对象
成员方法1.public boolean isEmpty():判断队列是否为空,
2.public int size():获取队列中元素的个数
3.public T dequeue():从队列中拿出一个元素
4.public void enqueue():往队列中插入一个元素
成员变量1.private Node head:记录首结点
2.private int N:当前栈的元素个数
3.private Node last:记录最后一个结点
成员内部类private class Node:结点类

Queue.java

package List.stack;

import java.util.Iterator;

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为null
            last = new Node(t,null);
            head.next = last;
        }else {
            //当前尾结点last不为null
            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();
    }
    public 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;
        }
    }
}

test.java

package List.stack;

public class QueueTest {
    public static void main(String[] args) {
        //创建队列对象
        Queue<String> q = new Queue<>();
        //测试队列的enQueue方法
        q.enQueue("a");
        q.enQueue("b");
        q.enQueue("c");
        q.enQueue("d");
        q.enQueue("e");

        for (String str : q) {
            System.out.println(str);
        }
        System.out.println("--------");
        //测试队列的deQueue方法
        String result = q.deQueue();
        System.out.println("出队列的元素是:"+result);
        System.out.println("剩余的元素个数是:"+q.size());

    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值