链式结构

链式储存结构:逻辑上连续;物理上不一定连续;比如老师点名确定人数情况;2号同学在一号后面;但是他两并不需要按照顺序坐

链表

前面顺序表缺陷
1:当我需要放101个元素;扩容扩到150不就浪费49个空间吗
2:每次删除和插入都要移动很多元素。所以就不适合用于频繁插入好删除的场景;查找比较适合;通过下标可以达到O(1)

链表:随去随用;插入删除不需要移动元素。链表有八种结构。
在这里插入图片描述
单向链表:

带头节点:第一个节点的数据是没意义的,永远标志这个链表的头。头插也是插在这个后面。

不带头节点:头插一次就变新的头节点
在这里插入图片描述
循环:
单向循环不带头:最后一个null变成头节点的地址;头是会在头插时改变;头的值是有效的

单向循环带头:最后一个null变成指向头节点的地址;
在这里插入图片描述

实现单向不带头链表

结构分析:如果我们使用类表示链表;那么里面的节点使用内部类的方式表示就很适合。:
先是一个类表示链表;然后里面一个内部类(有val;next;提供一个初始化的构造方法;我们可以指定值添加节点);外面是一个头指针。

class MyLink{
    
    

    class Node{
    
    
        public int val;
        //节点类型正好用来存放下一个节点;现在是对象代表节点
        public Node next;
        public Node(int val) {
    
    
            this.val = val;
        }
    }

    Node head=null;




}

1:提供一个初始化方法;一调用;马上给你创建5个节点

  //初始化方法;我在想我们在main方法怎么初始化呢;死去的回忆在攻击我;内部类的实例化
    public void createList(){
    
    
        Node node1=new Node(25);
        Node node2=new Node(250);
        Node node3=new Node(2500);
        Node node4=new Node(25000);
        Node node5=new Node(250000);
     //还需要绑定节点
        node1.next=node2;
        node2.next=node3;
        node3.next=node4;
        node4.next=node5;
        head=node1;

    }

2:打印的方法

  public void print(){
    
    
        Node cur=head;
//        使用cur是因为头指针可不能走
  //  打印链表; 这里遍历是注意是使用cur.next==null还是cur==null
//想想如果是cur.next;那么最后一个val就打印不了;循环进不去。还有问题就是空的时候没有cur.next
     while (cur!=null){
    
    


         System.out.println(cur.val);
           cur=cur.next;
     }


    }

3:获取链表长度方法


//    获取链表长度;
public int Size(){
    
    
    Node cur=head;
    int count=0;
    while (cur!=null){
    
    


          count++;
        cur=cur.next;
    }
return count;

}

4:查找是否包含关键字k

    //    获取链表长度;
    public boolean contains(int key){
    
    
        Node cur=head;

        while (cur!=null){
    
    

        if(cur.val==key){
    
    
//    如果是引用类型就不能用==;字符串得用equles方法。
            return true;
        }

            cur=cur.next;
        }
    return false;

    }

5:头插和尾插法
画图分析:
在这里插入图片描述
第二种方案:遇到空链表的情况也是适用的。因为head就是null了。而第一种的head.next就空指针异常。
技巧:链表插入数据时;一定得先绑定后面的节点。再绑定前面的节点。


//    头插法;我们无头;所以头插是直接放到头去;
//    头插需要注意什么细节。如果没有元素?
//    public void head(Node N){
    
    
//直接传节点有点不友好;可以选择传个数进来;你要头插这个数;我们在里面给创建成节点然后插入就好了
        public void addFirst(int data){
    
    
        Node N=new Node(data); 
        if(Size()==0){
    
    
            head=N;
            head.next=null;
            return;
        }
        N.next=head;
        head=N;

    }
//    尾插法
//    需要注意什么细节;如果没有元素;怎么找到最后个元素呢
    public void addLast(int data) {
    
    
     Node N=new Node(data); 
        Node cur=head;
// 让cur去往下遍历; 直到这个就是我们要找的最后个元素
       
        if(Size()==0){
    
    //或者这里写head==null;cur==null更高效。
            head=N;
            head.next=null;
            return;
        }
//找位置;尾插找到位置只需要把原先最后节点的next改成N的节点就好了;N的next本来就是null的
//怎么找;按之前的遍历方法行不行呢;不行的;因为这样子走过了;走到最后一个节点;再进行这一次的循环执行cur=cur.next;cur就变成空了。
//所以我们得让它在倒数的上一次循环执行就应该出来了;经过执行cur=cur.next;就恰好是我们想要的最后位置。所以这里得用cur.next!=null;
    //while(cur!=null){
    
    
  //       cur=cur.next;
//}
        while(cur.next!=null){
    
    
        cur=cur.next;
        }
        N.next=null;
       cur.next=N;



    }

6:插到中间位置


//插入到中间节点;以第一个节点为0下标;插入在index下标
//  有什么细节呢;如果是没有元素;如果是只有一个元素;头插尾插
//  index如果是负数呢。如果是0和链表长度那就调用上述的头插和尾插方法。然后就找位置;找到再进行普通的插入
//    还有不能隔着插入;比如只有5个元素;你要插入到10位置
    public void add(int index,int data){
    
    
        Node N=new Node(data);
       if(index<0||index>Size()){
    
    
           System.out.println("位置不合法");
           return;
       }
//       data等于0就包括没有元素的插入;data==Size()尾插的情况
       if(index==0){
    
    
           addFirst(data);

       }
       if(index==Size()){
    
    
           addLast(data);

        }
       //走到这里就是真正的中间插入;先找位置。使用index计数找位置;之前找尾节点也能用这种方法
       //或者倒着走更好;不用创建count变量;走index-1步就找到cur。
        //    条件(index-1 !=0)  cur=cur.next; index--;
        int count=0;
       Node cur=head;
        while (count<index){
    
    

            cur=cur.next;
            count++;
        }
//        cur找到位置;注意先绑定后面节点;画图就能很好看出;哪个地方要改成哪里的值
        N.next=cur.next;
        cur.next=N;


    }

7:删除val的方法

//    删除值为key的元素;是删除出现的第一个吗 是的
//    删除有什么细节呢;空的时候没法删除;如果删除的是头节点(head往后移即可);尾节点。只有一个元素的情况是属于删除尾节点。
    public Node remove(int key){
    
    
        if(Size()==0){
    
    
            System.out.println("没元素可以删除");
            return null;

        }
          // 删除头节点
        if(head.val==key){
    
    
            head=head.next;


        }
//        删除尾节点;那也得找尾节点;
//        删除只是跳过一个节点;假设cur是删除的前一个节点;
//        让cur.next=cur.next.next.当是尾节点cur.next.next就是null;这种情况也是符合普通情况
         Node cur=  selectago(key);
         if(cur==null){
    
    
             System.out.println("没有这个元素");
             return null;

         }
        //以删除节点为基准;前面一个节点的next等于;下一个节点的地址;因为下一个节点的地址就是这个删除节点的next。
        cur.next=cur.next.next;
         return cur;


    }

    private Node selectago(int key) {
    
    
//        找删除的前一个节点
        Node cur=head;
        while (cur!=null){
    
    

             if(cur.val==key){
    
    

                 return cur;
             }

            cur=cur.next;
        }
        return null;
    }

8:删除所有的key

//删除所有的key;想法是什么;注意细节:首先空是不能删;头不能先删;避免删了后面还是要删头
  public  void removeAllkey(int key){
    
    
//    方法1;循环遍历调用刚才的删除方法;但是;时间复杂度太大了。可达到O(n^2)
//        删除头节点和没有元素情况下还是少不了的;
        if(Size()==0){
    
    
            System.out.println("没元素可以删除");

        }
//先不考虑头的情况;先删除后面的;因为你比如删除5;第一个是;删完第一个万一第二个还是5呢
//删除得要特别注意;要找到前驱才能删除这个节点;cur从头开始;cur等于head.next就不好搞了;还需要记录一下前驱是什么。
          Node cur=head;
        while (cur.next!=null){
    
    
            if(cur.next.val==key) {
    
    
                cur.next = cur.next.next;
            }
            else {
    
    
                cur=cur.next;
            }
        }





//        最后后面删完再看看头是否要删
        if(head.val==key){
    
    
            head=head.next;

        }

    }

9:清空方法

//    清空链表的方法
    public void clean(){
    
    

        head=null;
//   标记删除;插入就覆盖进去;并不是到回收内存的那步;
    }

猜你喜欢

转载自blog.csdn.net/m0_64254651/article/details/130174108