集合类(数组和链表)

数组和链表

1.数组

数组的长度是固定的
数组只能存储同一种数据类型的元素

注意:在Java中只有一维数组的内存空间是连续多维数组的内存空间不一定连续

那么数组又是如何实现随机访问的呢?
寻址公式:i_address = base_address + i * type_length
为什么数组的效率比链表高?
数组是连续存储, 链表是非连续存储
数组的基本操作

添加 (保证元素的顺序)
最好情况:O(1)
最坏情况:移动n个元素,O(n)
平均情况:移动 n/2 个元素,O(n)

删除 (保证元素的顺序)
最好情况:O(1)
最坏情况:移动n-1个元素,O(n)
平均情况:移动(n-1)/2个元素,O(n)

查找
a. 根据索引查找元素:O(1)
b. 查找数组中与特定值相等的元素
①大小无序:O(n)
②大小有序:O(log2n)

总结: 数组增删慢,查找快。

2.链表

形象地说,链表就是用一串链子将结点串联起来。

结点:包含数据域和指针域。
数据域:数据
指针域:下一个结点的地址
在这里插入图片描述
在这里插入图片描述
链表及循环链表示例:
Node.java

public class Node {
    
    
    String value;
    Node next;

    public Node(String value, Node next) {
    
    
        this.value = value;
        this.next = next;
    }
}

LinkedDemo01.java

public class LinkedDemo01 {
    
    
    public static void main(String[] args) {
    
    
        Node node1 = new Node("20", null);
        Node node2 = new Node("40", node1);
        Node node3 = new Node("70", node2);
        node1.next=node3;//创建循环链表
        System.out.println("输出节点node1的next节点node3的值:"+node1.next.value);
        //

        //node3->node2->node1
        //node3.next.next等价于->node1
        System.out.println("输出节点node3的next的next节点的地址:"+node3.next.next.next);

      Node nexttest=  node2.next;//nexttest即为node1的地址对象
        System.out.println("输出nexttest即为node1的地址对象:"+nexttest);

        String str="zs";
        System.out.println(str);

    }
}

结果输出:

输出节点node1的next节点node3的值:70
输出节点node3的next的next节点的地址:com.ketang.day28_20210131.Node@1b6d3586
输出nexttest即为node1的地址对象:com.ketang.day28_20210131.Node@4554617c
zs

双向链表及双向循环链表示例:
DBNode.java

public class DBNode {
    
    
    String value;
    DBNode pre;
    DBNode next;

    public DBNode(String value, DBNode pre, DBNode next) {
    
    
        this.value = value;
        this.pre = pre;
        this.next = next;
    }
}

DBNodeDemo.java

public class DBNodeDemo {
    
    
    public static void main(String[] args) {
    
    
        DBNode dbNode1 = new DBNode("1", null, null);
        DBNode dbNode2 = new DBNode("2", dbNode1, null);
        DBNode dbNode3 = new DBNode("3", dbNode2, null);
        DBNode dbNode4 = new DBNode("4", dbNode3, null);
//  //实现1-->2-->3-->4的双向链表
//       //     1<--2<--3<--4
        dbNode1.next=dbNode2;
        dbNode2.next=dbNode3;
        dbNode3.next=dbNode4;

        System.out.println(dbNode3.pre.pre.value);
//实现双向循环链表
        dbNode1.pre=dbNode4;
        dbNode4.next=dbNode1;
        System.out.println(dbNode4.next.value);
        System.out.println(dbNode1.pre.value);

    }
}

DBNodeDemo.java

class DBNodeDemo {
    
    
    public static void main(String[] args) {
    
    
        DBNode dbNode1 = new DBNode("1", null, null);
        DBNode dbNode2 = new DBNode("2", dbNode1, null);
        DBNode dbNode3 = new DBNode("3", dbNode2, null);
        DBNode dbNode4 = new DBNode("4", dbNode3, null);
//  //实现1-->2-->3-->4的双向链表
//       //     1<--2<--3<--4
        dbNode1.next=dbNode2;
        dbNode2.next=dbNode3;
        dbNode3.next=dbNode4;

        System.out.println("输出dbNode3节点pre的pre节点dbNode1的值:"+dbNode3.pre.pre.value);
//实现双向循环链表
        dbNode1.pre=dbNode4;
        dbNode4.next=dbNode1;
        System.out.println("输出dbNode4节点next节点dbNode1的值:"+dbNode4.next.value);
        System.out.println("输出dbNode1节点pre节点dbNode4的值:"+dbNode1.pre.value);

    }
}

结果输出:

输出dbNode3节点pre的pre节点dbNode1的值:1
输出dbNode4节点next节点dbNode1的值:1
输出dbNode1节点pre节点dbNode4的值:4

循环链表我们用的一般比较少,但是当处理的数据具有环形结构时,就特别适合用循环链表,比如约瑟夫问题。

单链表

增加(在某个结点后面添加)

 //添加方法
    public boolean add(String str) {
    
    
        //判断链表是否为空
        if (top == null || size == 0) {
    
    
            //代表一个空链表
            top = new Node(str, null);//创建头节点
            size++;//长度加一
            return true;
        }
//查找尾节点
        Node mid = top;
        while (mid.next != null) {
    
    
            mid = mid.next;
        }

        //上述循环的跳出条件,就是mid是尾节点
        mid.next = new Node(str, null);//从尾节点开始添加节点
        size++;
        return true;

    }

删除(在某个结点后面删除)

 //删除方法
    public String delete(String str) {
    
    
        // TODO:参数验证
        if (top == null || size == 0) throw new RuntimeException("linked is null");
        //如果要删除的是头结点
        if (top.value.equals(str)) {
    
    
            //要删除的节点是头结点
            top = top.next;
            size--;
            return str;
        }
        //如果要删除的不是头结点
        Node mid = top;
        while (mid.next != null && !mid.next.value.equals(str)) {
    
    //一直向后查找
            mid = mid.next;
        }

        //意味着
        //1.mid.next=null,没有删除的元素
        //2.mid,next就是要删除的点
        if (mid.next == null) {
    
    
            //没找到
            return null;
        }
//mid.next要找的节点
        mid.next = mid.next.next;//覆盖删除
        size--;
        return str;
    }

修改:

//修改方法
    public boolean set(String oldStr, String newStr) {
    
    
//TODO:参数验证:
        if (top == null || size == 0) throw new RuntimeException("likend is null");
        //修改是否是头结点
        if (top.value.equals(oldStr)) {
    
    
            //就是要修改头结点
            top.value = newStr;
            return true;
        }

        //修改的不是头结点,一直向下寻找
        Node mid = top;
        //查找要替换的元素
        while (mid.next != null && !mid.next.value.equals(oldStr)) {
    
    
            mid = mid.next;
        }
//没找到
        if (mid.next == null) {
    
    
            return false;
        }
//必然找到
        mid.next.value = newStr;
        return true;
    }

查找:
a. 根据索引查找元素
b. 查找链表中与特定值相等的元素
①元素大小有序
②元素大小无序
总结:链表增删快,查找慢。

单链表增删改示例
MyLinked.java

public class MyLinked {
    
    
    //单链表
    Node top;//头结点
    int size;

    @Override
    public String toString() {
    
    
        return "MyLinked{" +
                "top=" + top +
                ", size=" + size +
                '}';
    }

    //添加方法
    public boolean add(String str) {
    
    
        //判断链表是否为空
        if (top == null || size == 0) {
    
    
            //代表一个空链表
            top = new Node(str, null);//创建头节点
            size++;//长度加一
            return true;
        }
//查找尾节点
        Node mid = top;
        while (mid.next != null) {
    
    
            mid = mid.next;
        }

        //上述循环的跳出条件,就是mid是尾节点
        mid.next = new Node(str, null);//从尾节点开始添加节点
        size++;
        return true;

    }

    //删除方法
    public String delete(String str) {
    
    
        // TODO:参数验证
        if (top == null || size == 0) throw new RuntimeException("linked is null");
        //如果要删除的是头结点
        if (top.value.equals(str)) {
    
    
            //要删除的节点是头结点
            top = top.next;
            size--;
            return str;
        }
        //如果要删除的不是头结点
        Node mid = top;
        while (mid.next != null && !mid.next.value.equals(str)) {
    
    //一直向后查找
            mid = mid.next;
        }

        //意味着
        //1.mid.next=null,没有删除的元素
        //2.mid,next就是要删除的点
        if (mid.next == null) {
    
    
            //没找到
            return null;
        }
//mid.next要找的节点
        mid.next = mid.next.next;//覆盖删除
        size--;
        return str;
    }

    //修改方法
    public boolean set(String oldStr, String newStr) {
    
    
//TODO:参数验证:
        if (top == null || size == 0) throw new RuntimeException("likend is null");
        //修改是否是头结点
        if (top.value.equals(oldStr)) {
    
    
            //就是要修改头结点
            top.value = newStr;
            return true;
        }

        //修改的不是头结点,一直向下寻找
        Node mid = top;
        //查找要替换的元素
        while (mid.next != null && !mid.next.value.equals(oldStr)) {
    
    
            mid = mid.next;
        }
//没找到
        if (mid.next == null) {
    
    
            return false;
        }
//必然找到
        mid.next.value = newStr;
        return true;
    }

    //查找方法
public boolean find(String str){
    
    
    //TODO:参数验证:
    if (top == null || size == 0) throw new RuntimeException("likend is null");
    //头结点是否是要查找的元素
    if (top.value.equals(str)) {
    
    
        return true;
    }

    Node mid=top;
    //查找指定的元素
    while (mid.next != null && !mid.next.value.equals(str)) {
    
    
        mid = mid.next;
    }
//没找到
    if (mid.next == null) {
    
    
        return false;
    }else//否则找到
        return true;
}


    class Node {
    
    
        String value;
        Node next;

        public Node(String value, Node next) {
    
    
            this.value = value;
            this.next = next;
        }

        @Override
        public String toString() {
    
    
            return "Node{" +
                    "value='" + value + '\'' +
                    ", next=" + next +
                    '}';
        }
    }
}

MyLinkedtest.java

public class MyLinkedtest {
    
    
    public static void main(String[] args) {
    
    
        MyLinked myLinked = new MyLinked();
        myLinked.add("dd");
        myLinked.add("gg");
        myLinked.add("ss");

        System.out.println(myLinked.toString());

        String gg = myLinked.delete("gg");
        System.out.println("删除gg后的链表:" + myLinked);

        myLinked.set("dd", "aa");
        myLinked.set("ss", "agrnzb");
        System.out.println("修改后的链表:" + myLinked);


        System.out.println("是否找到指定元素:" +myLinked.find("dd"));
        System.out.println("是否找到指定元素:" +myLinked.find("gg"));
        System.out.println("是否找到指定元素:" +myLinked.find("ss"));
        System.out.println("是否找到指定元素:" +myLinked.find("agrnzb"));

    }
}

结果输出;

MyLinked{
    
    top=Node{
    
    value='dd', next=Node{
    
    value='gg', next=Node{
    
    value='ss', next=null}}}, size=3}
删除gg后的链表:MyLinked{
    
    top=Node{
    
    value='dd', next=Node{
    
    value='ss', next=null}}, size=2}
修改后的链表:MyLinked{
    
    top=Node{
    
    value='aa', next=Node{
    
    value='agrnzb', next=null}}, size=2}
是否找到指定元素:false
是否找到指定元素:false
是否找到指定元素:false
是否找到指定元素:true

承接以上代码实现:(根据下标删除元素)

  /* 根据下标删除元素*/
    public boolean deleteindex(int index) {
    
    
        if (top == null || size == 0) throw new RuntimeException("likend is null");
        if (index >= size) throw new RuntimeException("越界异常:indexOutOfBoundsException!");

        Node mid = top;
        //如果节点为第一个节点
        if (index == 0) {
    
    
            System.out.println("第一个节点已删除!");
            top = top.next;//将头结点指向第二个节点
            size--;
            return true;
        }

        //找到删除节点的前一个节点
        int j = 0;
        while (mid.next != null && j < index - 1) {
    
    
            top = top.next;
            j++;
        }
        top.next = top.next.next;
        size--;
        return true;
    }

结果输出;

MyLinked{
    
    top=Node{
    
    value='dd', next=Node{
    
    value='gg', next=Node{
    
    value='ss', next=null}}}, size=3}
是否删除下标为1的元素:true
删除下标为1元素后的单链表:MyLinked{
    
    top=Node{
    
    value='dd', next=Node{
    
    value='ss', next=null}}, size=2}
Exception in thread "main" java.lang.RuntimeException: 越界异常:indexOutOfBoundsException!
	at com.ketang.day28_20210131.MyLinked.MyLinked.deleteindex(MyLinked.java:123)
	at com.ketang.day28_20210131.MyLinked.MyLinkedtest.main(MyLinkedtest.java:32)

双向链表
双向链表和单链表的时间复杂度是一样的,但双向链表有单链表没有的独特魅力——它有一条指向前驱结点的链接。(双向链表更常用)
增加 (在某个结点前面添加元素)
删除 (删除该结点)
查找
a. 查找前驱结点
b. 根据索引查找元素
c. 查找链表中与特定值相等的元素
① 元素大小无序
② 元素大小有序

    //查找方法
public boolean find(String str){
    
    
    //TODO:参数验证:
    if (top == null || size == 0) throw new RuntimeException("likend is null");
    //头结点是否是要查找的元素
    if (top.value.equals(str)) {
    
    
        return true;
    }

    Node mid=top;
    //查找指定的元素
    while (mid.next != null && !mid.next.value.equals(str)) {
    
    
        mid = mid.next;
    }
//没找到
    if (mid.next == null) {
    
    
        return false;
    }else//否则找到
        return true;
}

总结:虽然双向链表更占用内存空间,但是它在某些操作上的性能是优于单链表的。

思想:用空间换取时间

练习:

1.求链表的中间元素
解题思路
定义两个指针fast和slow。slow一次遍历一个节点,fast一次遍历两个节点,由于fast的速度是slow的两倍,所以当fast遍历完链表时,slow所处的节点就是链表的中间节点。

public class practise01 {
    
    
    public static void main(String[] args) {
    
    
        Node node1 = new Node("1", null);
        Node node2 = new Node("2", node1);
        Node node3 = new Node("3", node2);
        Node node4 = new Node("4", node3);
        Node node5 = new Node("5", node4);
        Node z=middleNode(node5);
        System.out.println(z);

    }

    public static Node middleNode(Node head) {
    
    
        if (head == null || head.next == null) {
    
    
            return head;
        }

        Node fast = head.next;//快指针
        Node low = head;//慢指针

        //快指针走两步,慢指针走一步
        while (fast != null && fast.next != null) {
    
    
            fast = fast.next.next;
            low = low.next;
        }
        
        return fast == null ? low : low.next;
    }
}

结果输出:

Node{
    
    value='3', next=Node{
    
    value='2', next=Node{
    
    value='1', next=null}}}

2.判断链表中是否有环(circle)

3.反转单链表

3.总结

在这里插入图片描述
1.数组是连续内存,CPU的高速缓存可预读数据(链表不能)。若数组过大无连续空间,会抛出OOM、
2.数组的缺点是大小固定。数组太大,浪费内存空间;数组太小,需重新申请更大数组,并将数据拷贝过去,耗时。
3.若对内存的使用苛刻,数组更适合。因结点有指针域,更耗内存。且对链表的频繁插入和删除,会导致结点对象的频繁创建和销毁,有可能会导致频繁的GC活动。

猜你喜欢

转载自blog.csdn.net/gy99csdn/article/details/113410646