Java数据结构——链表

108 阅读4分钟

链表

在刷代码随想录的同时,能够同步完成Java数据结构的相关学习。并且能够尽量自己实现相关数据结构。

书籍《Java数据结构和算法》

基本知识

  • 数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。在链表中,如果想要寻找一个特定的元素,唯一方法就是沿着这个链表的链一直向下寻找。要从第一项开始,到第二项、第三项……直到找到需要的那个数据项。

  • Java中,链表也是定义在类中的,一般来说,其模板如下:

    public class ListNode {
        // 结点的值
        int val;
        // 下一个结点
        ListNode next;
        //构造函数以及其他函数这里暂不体现
    }
    
    • 每个ListNode对象都包含一个对下一个链节点引用的字段,上面代码中是next。这种类定义有时也称为“自引用式”。
  • 在链表的实现过程中,根据选择的不同,一般有两种。一种是带有虚拟头结点——即头结点中不存储任何数据,仅用于指向链表;另一种就是头结点实实在在存储了数据,是真实的。

    • 个人理解上,感觉虚拟头结点更为方便。
  • 以下代码实现,为保持和书籍一致,采用的是后者,真实的头结点。

单链表

单链表具体代码实现

    //linkNode类
    class linkNode {
        public int iData;
        public double dData;
        public linkNode next;
        public linkNode(){
    
        }
        public linkNode(int iData,double dData){
            this.iData=iData;
            this.dData=dData;
        }
        public void displayLinknode(){
            System.out.println("iData="+this.iData+",dData"+this.dData);
        }
    
        @Override
        public String toString() {
            return "linkNode{" +
                    "iData=" + iData +
                    ", dData=" + dData +
                    ", next=" + next +
                    '}';
        }
    }

代码分割线

 //linkList类
    //按照书上说法,因为一个链表中有许多类似的链节点,所以有必要用一个不同于链表的类来表示链结点。
    //个人理解就是listNode类仅用于表示结点,而链表(包括头结点)应该用一个额外的类来存储。
    //(其实还挺有道理的,有点符合面向对象编程那种感觉)
    import java.util.ArrayList;
    public class linkList {
        private linkNode first;
        public linkList(){
    
        }
        public boolean isEmpty(){
            //first==null的话 return true 说明当前链表是空的  否则就非空
            return (first==null);
        }
    
        /**
         *
         * @param iData 新插入节点的iData数据
         * @param dData 新插入节点的dData数据
         * @param insertIndex 节点插入到第几个节点 如insertIndex=1 表示插入到第一个节点,即变成头结点。=2 表示插入到第二个节点
         * @return  true表示插入成功  false表示插入失败
         */
        public boolean insertNodes(int iData,double dData,int insertIndex){
            linkNode insertNode=new linkNode(iData,dData);
            //如果当前链表都是空的
            if(this.isEmpty()){
                this.first=insertNode;
                first.next=null;
                return true;
            }
            else{
                //如果要插入的位置都大于了我们的这个链表的长度,那么就插入到最后
                if(insertIndex>this.getLinkListLength()){
                    linkNode temp=first;
                    System.out.println("被插入位置已大于当前链表长度,将其插入称为尾节点。");
                    while(temp.next!=null){
                        temp=temp.next;
                    }
                    temp.next=insertNode;
                    return true;
                }
                //如果插入的位置小于等于1
                else if(insertIndex<=1){
                    //插入到头结点
                    System.out.println("被插入位置已<=1,插入到头结点");
                    insertNode.next=first;
                    first=insertNode;
                    return true;
                }
                else{
                //如果要插入的位置我们这个链表的长度之间
                    linkNode pre =first;
                    int count=1;   //因为不是虚拟头结点,所以头结点要算入 count=1
                    while(count!=insertIndex-1){
                        pre=pre.next;
                    }
                    insertNode.next=pre.next;
                    pre.next=insertNode;
                    return true;
    
                }
            }
        }
    
        /**
         * 时间有限,就暂时完成删除所有this.iData=iData的节点
         * @param iData
         * @return ArrayList<linkNode> 因为可能iData可能有重复的,即可能有大量节点被删除
         */
        public ArrayList<linkNode> deleNodes(int iData){
            ArrayList<linkNode> deletedNodes=new ArrayList<>();//存储被删除的节点
            //如果链表为空,就直接返回null ,并sout链表为空
            if(this.isEmpty()){
                System.out.println("链表为空");
                return null;
            }
            linkNode pre=first;
            linkNode temp=pre.next;
            //因为我想一次性遍历完,所以还是暂时先从first的next开始。
            //但是这样就避开了first的判断,所以循环结束还要把first再判断一次
            while(temp!=null){
                if(temp.iData==iData){
                    deletedNodes.add(temp);
                    pre.next=temp.next;
                    temp=pre.next;
                }
                else{
                    pre=temp;
                    temp=pre.next;
                }
            }
            //这里就是 first没有判断过,所以要判断一下
            if(first.iData==iData){
                deletedNodes.add(first);
                first=first.next;
            }
            return deletedNodes;
        }
        public int getLinkListLength(){
            if (isEmpty()){
                return 0;
            }
            linkNode temp=first;
            int length=1;
            while(temp.next!=null){
                temp=temp.next;
                length++;
            }
            return length;
        }
    
        public void displayWholeLinklist(){
            linkNode temp=first;
            if(isEmpty()){
                System.out.println("当前链表为空");
                return;
            }
            while(temp!=null){
                temp.displayLinknode();
                temp=temp.next;
            }
        }
    
    }

//个人测试用

import java.util.ArrayList;
    
    public class testLinklist {
        public static void main(String[] args) {
            linkList first=new linkList();
            first.insertNodes(4,1.5,1);
            first.insertNodes(4,2.5,1);
            first.insertNodes(4,2.5,2);
            first.insertNodes(4,4.5,4);
            first.insertNodes(4,4.5,10);
            first.displayWholeLinklist();
            ArrayList<linkNode> deletedNodes=first.deleNodes(4);
            System.out.println("被删除的节点为:"+deletedNodes.toString());
            first.displayWholeLinklist();
        }
    }