单链表(增删查改的详细操作---以火车为例)

目录

1.线性表的特点

2.前言(引入自定义属性--引用)

3.介绍相关属性具体的含义

4.单链表的详细操作

4.1头插法

4.2在链表中间位置插入(最核心在于找到插入位置的前驱节点)

4.3查询操作

4.4修改操作

4.5删除操作

5.无虚拟头节点单链表总结

 6.虚拟头节点(dummyHead)单链表

7.笔试题讲解

1.线性表的特点

只能从头部车厢开始遍历,依次走到尾部

也就是单向遍历,默认从前向后遍历

2.前言(引入自定义属性--引用)

这是一个自定义的节点类;可以通过它创建节点 

3.介绍相关属性具体的含义

车厢类:(具体存储元素的类)

int data;//具体存储数据

Node next;//存储下一节车厢的地址

火车类(由一系列车厢拼起来)

class Node{

int size;//车厢个数

Node head;//第一节车厢的地址

}

4.单链表的详细操作

4.1头插法

   //头插法
public  void addFirst(int val) {
        Node node = new Node(val); //创建了一个新的节点
        if (head==null) {
            head = node;
        } else {
            node.next = head;  //这里的顺序不可以反
            head = node;
        }
        size++;//添加了一个元素,所以要加1
    }

4.2在链表中间位置插入(最核心在于找到插入位置的前驱节点)

 /*
    * 在单链表的任意一个索引位置插入元素value
    */
    public void addIndex(int index,int val) {
        if (index < 0 || index > size) {
            System.err.println("不合法");
            return;
        }
        if(index==0){
            addFirst(val);
            return;
        }
        Node node = new Node(val);
  //需要找到待插入位置的前驱节点
        Node prev = head;
        for (int i = 0; i < index - 1; i++) {
            prev = prev.next;
        }
        //此时prev指向了待插入位置的前驱节点
        node.next = prev.next;
        prev.next = node;
        size++;
    }

4.3查询操作

 /*根据用户输入的index查找对应值*/
    public int get(int index) {
        if (rangeCheck(index)) {
            Node node = head;
            for (int i = 0; i < index; i++) {
                //遍历节点
                node = node.next;
            }
            return node.val;
        } else {
            System.err.println("index不合法");
            return -1;
        }
    }
 /*查询值为value的元素是否在单链表中存在*/
    public boolean contains(int value){
        for(Node temp=head;temp!=null;temp=temp.next){
            if(temp.val==value){
                return true;
            }
        }
        return false;
    }

4.4修改操作

  /*修改index位置的值为newValue,返回修改前的值*/
    public int set(int index,int newValue){
        if(rangeCheck(index)){
            Node node =head;
            for(int i=0;i<index;i++){ //这里注意不能等于
                node=node.next;
            }
            //走完则走到了索引位置
            int oldValue=node.val;
            node.val=newValue;
            return oldValue;
        }else{
            System.err.println("Index不合法");
            return -1;
        }
    }

4.5删除操作

 removeIndex(int index);//这里包括头节点的删除、中间节点的删除、尾节点的删除。

removeValueOnce(int value);//删除单链表中第一个值为val的节点。

removeValueAll(int value);//删除单链表中所有值为val的节点。

按值删除代码基本一样,但是有一个小的细节不一样,写错的话就不会全部删除了

代码如下

  public void removeIndex(int index){
        if(rangeCheck(index)){
           if(index==0){
               //删除头节点
            Node temp =head;
            head =head.next;
            temp.next =null;
            size--;
           }else {
               //index中间
               Node prev = head;
               for (int i = 0; i < index - 1; i++) {//理解不了画个图
                  prev= prev.next ;
               }
               //待删除结点
               Node cur =prev.next;
               prev.next=cur.next;
               cur.next=null;
           }
        }else{
            System.out.println("index不合法");
        }
    }
    //删除头节点
    public void removeFirst(){
        removeIndex(0);
    }
    //删除尾节点
    public void removeLast(){
        removeIndex(size-1);
    }
    /*
    * 按值删除
    * */
    public void removeValueOnce(int val){
        //遍历链表找到值为val的节点
        //找到后删除(正常都要找的前驱,只有头节点没有前驱)
        if(head!=null&&head.val==val){
            //头节点就是但删除节点
            removeFirst();
        }else {
            Node prev =head;
            //此时head一定不是待删除的节点
            while(prev.next!=null){
                if(prev.next.val==val){
                    //当前节点是待删除节点
                    Node cur =prev.next;
                    prev.next=cur.next;
                    cur.next=null;
                    size--;
                    return;
                }
                //prev不是待删除结点的前驱,prev向后移动
                prev=prev.next;
            }
        }
    }
    public void removeValueAll(int val) {
        //判断头节点是否为待删除节点
        while (head != null && head.val == val) {
            head = head.next;
            size--;
        }
        if (head == null) {
            return ;
        } else {
            //此时head不是待删除结点,链表中还有节点
            Node prev = head;
            while (prev.next != null) {
                if (prev.next.val == val) {
                    //当前节点是待删除节点
                    Node cur = prev.next;
                    prev.next = cur.next;
                    cur.next = null;
                    size--;
                } else {
                    //prev不是待删除结点的前驱,prev向后移动
                    prev = prev.next;
                }
            }
        }
    }

5.无虚拟头节点单链表总结

在插入时应该注意结点之间代码转换的顺序 

头插法时的顺序 

尾插法时的顺序

以及除了添加类型之外所有的合法性都不能取到size,因为三个操作的相同点,我们可以直接整合一个方法,在方法内部直接调用即可

测试类:

单链表在插入和删除时,只要是中间节点,都会找前驱节点,只有头节点需要做特殊处理。 

 6.虚拟头节点(dummyHead)单链表

为了不用单独处理头节点,创建了一个虚拟头节点,不存储元素的值

虚拟头节点在找索引位置的时候,是i<index,而不是index-1,是因为加入了头节点,所以多走一步。

有了头节点所有的插入、删除操作就都当作中间节点来处理。

代码如下:

public class SingleLinkedWithHead {
    // 带头单链表
        // 当前存储的元素个数
        private int size;
        // 虚拟头节点
        private Node dummyHead = new Node(-1);

        // 添加方法
        public void addIndex(int index,int val) {
            // 判断index的合法性
            if (index < 0 || index > size) {
                System.err.println("add index illegal!");
                return;
            }
            // 插入全都是中间节点
            Node node = new Node(val);
            // 找到待插入位置的前驱
            Node prev = dummyHead;//刚开始前驱指向了虚拟头节点
            for (int i = 0; i < index; i++) {   //i<index是因为多走了一步虚拟头节点,而这个虚拟头节点不计算在index里面,单链表插入排序里面是index-1
                prev = prev.next;       //不理解画一下图
            }
            // prev指向待插入位置的前驱
            //node是插入的节点
            node.next = prev.next;  //这个和单链表一样,顺序注意
            prev.next = node;
            size ++;   //添加一次记得长度要加一
        }
        //头插
        public void addFirst(int val) {//插入在头部
            addIndex(0,val);
        }
        //尾插
        public void addLast(int val) {//插入在尾部
            addIndex(size,val);
        }

        public String toString() { //用于把单链表以字符串的形式输出
            String ret = "";
            Node node = dummyHead.next;//从头节点之后的第一个节点开始遍历,这里是创建了一个
            while (node != null) {
                ret += node.val;
                ret += "->";
                node = node.next;
            }
            ret += "NULL";
            return ret;
        }
/*
* 删除索引位置的节点
* */
        public void removeIndex(int index) {//删除节点,有虚拟头节点所以都相当于中间插入
         //判断索引的合法性,也可以合成一个方法
            if (index < 0 || index >= size) {
                System.err.println("remove index illegal!");
                return;
            }
            // 删除中间节点
            Node prev = dummyHead;
            // 找到待删除位置的前驱
            for (int i = 0; i < index; i++) {
                prev = prev.next;
            }
            // prev指向待删除位置的前驱
            prev.next = prev.next.next;
            size --;
        }

    }

7.笔试题讲解

7.1力扣82删除链表中的所有重复节点

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode dummyHead =new ListNode(101);
        dummyHead.next=head;
        ListNode prev =dummyHead;
        ListNode cur =prev.next;
        while(cur!=null){
            ListNode next =cur.next;
            if(next==null){
                return dummyHead.next;
            }else{
                //当前链表至少有俩个节点
                    if(cur.val!=next.val){
                        //三个引用同时向后走
                    prev=prev.next;
                    cur=cur.next;
                    next在循环里会走一步的
                    }else{
                        while(next!=null&&cur.val==next.val){
                            //让naxt一直向后走,直到cur与next不相等,或者next为空
                            //prev指向第一个重复节点的前驱
                            next=next.next;
                        }
                        //prev-指向重复节点的前驱
                        //next-指向重复节点的后继,然后把中间这一块全部删除
                        prev.next=next;
                        //更新cur指向
                        cur=next;
                    }
                } 
            }
         return dummyHead.next;
    }
}

7.2力扣83 删除排序链表中的重复元素

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode dummyHead =new ListNode(101);
        dummyHead.next=head;
        ListNode prev =dummyHead;
        ListNode cur=prev.next;
        while(cur!=null){
            if(prev.val==cur.val){
                prev.next=cur.next;    
            }else {
                //prev和cur不是重复元素,都向后移动
                prev=prev.next;
            }
            cur=prev.next;
        }
        return dummyHead.next;
        }
    }

7.3力扣203

class Solution {
   public ListNode reverseList(ListNode head) {
        //头插法
        if(head==null||head.next==null){
            return head;
        }
        ListNode dummyHead =new ListNode(5001);
        //遍历原数组,边头插新链表
        while(head!=null) {
            ListNode node =new ListNode(head.val);
            node.next = dummyHead.next;
            dummyHead.next =node;
            head =head.next;
        }
        return dummyHead.next;
    }
}

力扣21合并两个有序列表

 public ListNode mergeTwoLists(ListNode l1,ListNode l2){
        if(l1==null){
            return l2;
        }
        if(l2==null){
            return l1;
        }
        //此时l1、l2都不为空
        ListNode dummyhead =new ListNode(-1);
        //尾插
        //last是新链表的尾节点
        ListNode last=dummyhead;
        //开始代码拼接的代码
        while(l1!=null&&l2!=null){
            if(l1.val<= l2.val){
                last.next=l1;
                last=l1;
                l1=l1.next;
            }else{
                last.next=l2;
                last=l2;
                l2=l2.next;
            }
        }
        if(l1==null){
            last.next=l2;
        }
        if(l2==null){
            last.next=l1;
        }
        return dummyhead.next;
    }

在这部分要记得多画图哦!!! 

8.双向链表

双向链表中合成删除结点的方法:

引入了分治的思想,先处理左边的情况,处理完之后再处理右边的情况。

 private void unlink(Node node){
        //分治思想
        Node prev =node.prev;
        Node next =node.next;
        //先处理前驱节点
        if(prev==null){
            //没有前驱,此时是个头节点
            head=next;
        }else{
            //有前驱
            prev.next=next;
            node.prev=null;
        }
        //此时接着处理后半边的情况
        if(next==null){
            //此时node是个尾节点
            tail=prev;
        }else{
            next.prev=prev;
            node.next=null;
        }
        size--;
    }
}

 8.1removeVaueAll的方法

  public void removeValueAll(int val){
        for(Node x=head;x!=null;){
            if(x.val==val){
                //x是待删除结点
                //暂存x(下一个结点的)的地址,方便下一次循环
                Node next =x.next;
                //删除x这个节点
                unlink(x);
                x=next;
            }else{
                //x不是待删除节点,继续向后走
                x=x.next;
            }
        }
    }

 9.类和对象的作业题

 因为hello是静态方法

test.hello()相当于是不规范的空对象调用静态方法

实际上,写为Test.hello();

所以可以正确编译,并且运行


局部变量是全部没有默认值的,所以编译不通过 ,选C

局部遍历必须初始化后使用,所以默认值必须定义在类中。


 编译失败,不能在普通方法(必须由对象调用)中定义静态遍历(没对象也可使用),自相矛盾了


B、静态方法访问静态变量,是可以的

静态方法可以访问静态变量,不能调用成员变量(有对象才能用)

成员方法可以调用静态变量,但是不能定义静态变量


 不能访问p.name,因为父类的name是私有属性,出了它的括号就不能被使用,会编译出错


 最后是102


B,类加载只是把它加载到JVM中进来,并没有产生对象,只有new 的时候才会赋值

A、是反射, java中所有的类都有它的class对象


 

 可以运行,最后输出88


这个问题比较复杂

变成了一个大的静态代码块,为什么下面的代码块先执行

因为静态代码块优先于主方法,在类加载的时候就执行

 

 答案  5


输出aaabbb 

bbb是主方法打印的


选D

n是静态变量,this表示当前对象,是矛盾的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值