单链表的实现 —— Java

单链表的实现 —— Java

在这里插入图片描述


每博一文案

董卿曾说过,你若好到毫无保留,对方就敢坏到。
肆无忌惮要有一颗善良的心,这样才能够让自己心安。
单凡事也都有个度,一旦太过善良,就会变得廉价。
付出只会被当作理所当然帮助,只会换来得寸进尺。
最后不是自己心汗,就是对方翻脸人要想发出光芒,必须先长出锋芒,
如果太过善良,就会丢失自己的价值和尊严,成为别人攻击你的利器
,更变成了伤害你的软肋。
三毛说过,不要害怕拒绝别人,因为当一个人开口提出要求的时候
他的心里根本预备好了两种答案。
所以给任意一个答案都是意料中的。
有时候你把别人看得太重,结果你在别人眼中什么都不是。
                                             ————————————  一禅心灵庙语




单链表的实现:

  • 单链表的结构示图:
    在这里插入图片描述

  • 我们现在所实现的 单链表 是没有,头节点的,其结构比较简单,但是实现其功能的话,会比较复杂一些


单链表的类的创建

  • 注意 Java的类的一些事项:
  • 静态方法不可以直接访问非静态的方法和非静态属性,需要实例化对象,通过对象的引用方可,访问
  • 非静态方法可以直接静态方法和非静态的方法,非静态的属性以及静态的属性
  • 首先我们需要创建一个有关 单链表结构的类出来
  • 这里我们通过该 类的构造方法 ,对创建节点的初始化,以及赋值的操作

// 创建该链表的类,同C语言中的结构体类似
class Node {
    public int data; // 节点的数值
    public Node next; // 对象的引用,就是地址,深度拷贝
    public Node(int data) {  // 该类的构造方法:通过构造方法,对节点的生成,以及初始化
        this.data = data;
        this.next = null;
    }

}

头插法

  • 两种情况
  • 1 种:只有 首节点 ,直接将插入的节点,改写成 首节点 就可以了
  • 2 种:存在多个节点
  • 头插法 的操作示图如下:
    在这里插入图片描述
 // 头插法
    public void addFirst(int data) {
        Node node = new Node(data);
        if (this.pHead == null) {
            // 1. 只有一个首节点的存在
            this.pHead = node;
            return; // 插入完毕,停止后面的继续执行
        }

        // 2.多个节点的存在
        node.next = this.pHead;
        this.pHead  = node;

    }


打印单链表

  • 通过循环遍历,该单链表,注意 :跳出循环的条件是:null
  • 图示:

在这里插入图片描述

//    打印单链表
    public void disPlay() {
        Node cur = this.pHead; // 首节点的拷贝,代替首节点的移动

        while(null != cur) {
            System.out.print(" "+cur.data);
            cur = cur.next;
        }
        System.out.println();
    }

尾插法

  • 首先判断该 单链表 是否为 ,如果为空,自定义异常
  • 循环 找到 尾节点 的地址,拼接

在这里插入图片描述


//    尾插法
    /*两种情况
    1. 该单链表为 “空”链表
    2.该单链表不为空,找尾节点
    * */
    public void addList(int data) {
        Node cur = this.pHead; // 首节点的拷贝

        if(this.pHead == null) {
            // 该单链表为空
            throw new RuntimeException("链表为空");
        }

        while(null != cur.next) {
            cur = cur.next; // 移动节点
        }
        Node node = new Node(data); // 调用构造方法,生成节点
        cur.next = node;

    }

头删法

  • 同样空单链表就不要 删除
  • 注意 对首节点进行 拷贝,复制 防止,因为 插入先后顺序 ,把整个链表丢失,掉了
  • 头删法 的操作示图:
    在这里插入图片描述

//    头删法
    public void deleteFirst() {
        Node cur = this.pHead.next; // 首节点的下一个节点的,拷贝,复制

        if(null == this.pHead) {
            System.out.println("空单链表不用删除");
            return;
        }

        this.pHead.next =cur.next;
        this.pHead = cur;
    }

尾删法

  • 存在着 中情况
  • 1 链表为 “空”
  • 只有 首节点 这一个节点的存在,我们不可以把,首节点给删除了
  • 多个节点存在
  • 尾删法 的操作示图:

在这里插入图片描述


 // 尾删法
    /*
    三种情况
    1.空链表
    2.只有一个首节点的存在
    3.多个节点存在
    * */
    public void deleteList() {
        Node node = this.pHead; // 首节点的复制,拷贝
        Node tmp = null;

//        1.空链表
        if(null == this.pHead) {
            throw new RuntimeException("空链表,无法操作");
        }

//        2.只有一个首节点的存在
        if(null == this.pHead.next) {
            this.pHead = null;
            return ;
        }

//        3.多个节点存在
        while(node.next != null) {
            tmp = node;
            node = node.next; // 移动节点
        }

        tmp.next = null;
    } 

查找是否包含关键字 key 是否在单链表当中

  • 循环判断是否存在该数值的节点
  • 空链表就不用看了
//    查找是否包含关键字 key 是否在单链表当中
    public boolean contains(int key) {
        Node node = this.pHead; // 首节点的复制拷贝

        if(null == this.pHead) {
         throw new RuntimeException("该链表为空");
        }

        while(node != null) {
            if(key == node.data) {
                return true; // 找到了,
            }
            node = node.next; // 移动节点
        }
        return false; // 该数值不存在
    }

计算该单链表的长度

  • 循环遍历计数
  • 空链表就不用看了

 //    该单链表的长度
    public int size() {
        Node node = this.pHead; // 首节点复制,拷贝
        int count = 0;

        if(this.pHead == null) {
            System.out.println("空链表");
            return 0;
        }

        while(node != null) {
            count++;
            node = node.next;  // 移动节点
        }
        return count;
    }

查找数值为 x 的的地址

  • 循环遍历
  • 空链表就不用看了


 //    查找数值为 x 的的地址
    public Node searchIndex(int x) {

        Node cur = this.pHead;
        if (this.pHead == null) {
            throw new RuntimeException("该链表为空");
        }

        while (null != cur) {
            if (x == cur.data) {
                return cur;
            }
            cur = cur.next; // 移动节点
        }
        return null;
    }

在数值为 x 的后面插入数据

  • 三种情况
  • 1.该数值在 首节点 中,复用 尾插法,因为插入的位置是在 该数值后面
  • 2.该数值在 尾节点 中,复用 尾插法
  • 3.该数值在中间
  • 其操作示图:

在这里插入图片描述


// 在数值为 x 的后面插入数据
  /*  * 三种情况
    1.在开头,可以复用 尾插法
    2.在尾部,可以复用 尾插法
    3.在中间,*/
    public void addIndex(int index,int data) {
        // 不存在
        if(null == searchIndex(index)){  // 该数值的节点的地址
            System.out.println("该数值的节点不存在,返回不执行");
            return;
        }

//       1. 在开头
        if(this.pHead == searchIndex(index)) {  // 该数值的节点的地址
            addList(data);
            return; // 执行完毕,
        }

//        2.在尾部
        if((searchIndex(index).next == null)) { // 该数值的节点的地址
            addList(data);
            return; // 执行完毕
        }

        Node cur = searchIndex(index); // 该数值节点地址的,拷贝
        Node tmpNext = cur.next;  // 该数值节点的后面节点的地址
        Node node = new Node(data);  // 生成节点

        cur.next = node;
        node.next = tmpNext;

    } 

删除第一个数值为 key 的节点

  • 四种情况
  • 1 . 链表为空,不用操作了
  • 2 . 该删除的数值为 首节点
  • 3 .该删除的数值为 尾节点 上 ,复用尾删法
  • 4 该删除的数值的节点,在中间
  • 操作示图如下:

在这里插入图片描述


/*

    删除第一个数值为 key 的节点
    三种情况:
    1.空链表,不用删了
    2.位于首节点
    3.在中间位置
*/
    public void remove(int key) {
        // 1.空链表,不用删了
        if(null == searchIndex(key)) { // 该数值节点的地址
            System.out.println("该数值的节点不存在,返回不执行");
            return; // 执行完毕
        }

        // 2.位于尾节点
        if(null == searchIndex(key).next) {  // 该数值节点的地址
            deleteList(); // 尾删法,复用
            return; // 执行完毕
        }

        // 3.为与首节点
        if(searchIndex(key) == this.pHead) {
            deleteFirst(); // 头删法,复用
            return; // 执行完毕

        }

        // 4.中间
        Node cur = searchIndex(key);
        Node copyPHead = this.pHead;

        while(copyPHead!=cur) { // 找到该数值节点的前一个节点
            copyPHead = copyPHead.next; // 移动节点
        }
      copyPHead.next = cur.next;

    }

删除所有数值为 key 的节点

  • 因为该单链表的缺陷:无法直接拿到该数值节点的 前一个节点 的位置
  • 所以这里我们使用:前后两个引用的方式,删除节点
  • 注意 :首节点我们最后判断是否为该数值的节点,是删除,因为这样比较简单,防止,漏了节点的判断,而没有完全删除该数值节点
  • 操作示图:
    在这里插入图片描述

//    删除所有数值为 key 的节点
    public void removeKey(int key) {
        Node tmp = this.pHead;
        Node cur = this.pHead.next;

        while(cur != null) {
            if(key == cur.data) {  // 是该数值节点,删除
                tmp = cur.next;
                cur = cur.next;  // 移动节点
            } else {  // 不是该数值节点,移动
                tmp = cur;
                cur = cur.next;
            }

            if(key == this.pHead.data) {
                // 判断首节点是否为该数值的节点,是山粗
                this.pHead = this.pHead.next;
            }

        }
   }

清空链表

  • Java是有自动回收内存空间的机制的,该变量只有没有被引用,JVM 虚拟机才会回收该空间的,
  • 当有人引用它的时候,是不会被回收的
  • 当我们把首节点置为 NULL 的时候,也就不会有对下面的节点的引用了,JVM 虚拟机就会自动回收该内存空间

// 清空单链表
    public void clear() {
        this.pHead = null;
    }

查看Java程序运行的进程

  • 进程,进程 ,需要程序是在一个 运行 的状态

  • 所以让我们的 Java程序卡在一个调试 的状态,不要让程序停止了,停止了,哪里有进程了

  • 按键盘上的 win + R 键 在弹出中输入 cmd 进入 运行命令窗口

  • 运行命令窗口中输入

jps


  • 显示如下:下面红色框框中的就是我们正在运行的一个,Java 程序
  • 左边的数值是:该程序的进程号, 右边的表示的是:我们程序的名称

在这里插入图片描述


  • 最后根据 进程号 输入以下命令:

jmap -histo:live 24328 >E:\临时文件\log.txt

24328:表示的是我们所对应程序的进程

> 表示的将内容重定向到,就是拷贝,复制到后面我们所定义到的文件中去:E:\临时文件\log.txt

如果没有重定 > 内容就会直接显示在我们的 运行对话窗口中

  • 最后我们通过 调试 结束程序的运行:
  • 内容就会 重定向 到我们定义的路径的文件当中去了
  • 效果如下:

在这里插入图片描述


在这里插入图片描述


MyLinkList.java

  • 单链表完整实现:
package Project.LinkList;


class Node {               // 创建单链表的类,和C语言中的构造方法类似
    public int data;
    public Node next;      // 对应的单链表的对象的引用,就是地址

    public Node(int data) {
        // 使用构造方法对其单链表的初始化,创建节点
        this.data = data;
        this.next = null;
    }

}


public class MyLinkList {

    public Node head;

//    头插法
    /*
    两种情况 1.空链表,不是空链表
    **/
    public void addFirst(int data) {
        Node node = new Node(data);  // 创建节点

        if(this.head == null) {
            // 1. 为空链表,直接置换
            this.head = node;
            return; // 程序执行完停止
        }

        // 2.不是空链表
        node.next = this.head;
        this.head = node;

    }



//    打印单链表
    public void disPlay() {
        Node cur = this.head;   // 首节点的拷贝,代替移动

        while(cur != null) {    // 循环遍历到尾节点 null
            System.out.print(cur.data+" ");
            cur = cur.next;     // 移动节点
        }
        System.out.println();
    }



//    尾插法
    /*
    1.链表为空
    2,链表不为空,null ,找尾节点
    * */
    public void addList(int data) {
        Node node = new Node(data);   // 创建节点
        Node cur =  this.head;        // 对首节点拷贝

        // 链表为空
        if(this.head == null) {
            this.head = node;
            return;  // 程序执行完停止
        }

        while(cur.next != null) {
            cur = cur.next;
        }
        cur.next = node;
    }



//    尾删法
    /*
    三种情况
    1.空链表,不用删除
    2.只有首节点,首节点置为null
    3.多个节点的存在
    * */
    public void deleteList() {
        Node cur = this.head;
        Node tmp = this.head.next;

        //     1.空链表,不用删除
        if(this.head == null) {
            throw new RuntimeException("空链表不用删了");
        }

        // 2.只有首节点,首节点置为null
        if(this.head.next == null) {
            this.head = null;
            return; // 把首节点删除了
        }

        // 3.多个节点的存在
        while(tmp.next != null) {
            cur = tmp;
            tmp = tmp.next;
        }
        cur.next = null;
    }



//    查找是否包含关键字 key 是否在单链表当中
    public boolean contains(int key) {
        Node node = this.head;   // 首节点的拷贝

        while(node != null) {
            if(key == node.data) {
                return true;     // 存在该数值的节点
            }
            node = node.next;    // 移动节点
        }
        return false; // 不存在该数值的节点
    }



//    该单链表的长度
    public int size() {
        if(null == this.head) {
            throw new RuntimeException("该单链表为空");
        }

        int count = 0;
        Node cur = this.head;  // 首节点的拷贝,代替首节点的,移动
        while(cur != null) {   // 循环遍历,单链表
            count ++;
            cur = cur.next;    // 移动节点
        }
        return count;
    }



//    查找 x 序号的地址
    private Node searchIndex(int x) {
        Node cur = this.head;     // 对首节点的拷贝,代替移动
        if(x < 0 || x > size() ) {
            throw new RuntimeException("该序号不合理");
        }

        while((x-1) != 0) {       // 注意序号是从0 开始的 所以减 1;
            cur = cur.next;       // 移动节点
            x--;

        }
        return cur;               // 返回该节点的地址
    }



// 在某个 序号后面插入数据节点
    /* 三种情况
    1.在开头,可以复用 头插法
    2.在尾部,可以复用 尾插法
    3.在中间,
    * */
    public void addIndex(int index,int data) {

        //1. 在开头
        if(index == 0 ) {
            addFirst(data);
            return;             // 完成插入,程序停止执行
        }

        //2. 在尾部
        if(index == size()) {
            addList(data);
            return;             // 完成插入,程序停止执行
        }
        //3. 在中间
        Node node = new Node(data);  // 创建节点
        Node cur = searchIndex(index);  // 对应序号的节点的地址
        node.next = cur.next;  // 注意:其中的顺序,不要把链表丢了
        cur.next  = node;

    }

//    查找数值 key 的地址
    private Node searchPrev(int key) {
        Node cur = this.head;      // 首节点的拷贝,代替首节点,移动
        if(this.head == null) {
            throw new RuntimeException("链表为空,不用找了");
        }

        while(cur.next != null) {
            if(cur.next.data == key) {
                return cur;        // 找到,返回该数值的前驱节点
            } else {
                cur = cur.next;    // 移动节点
            }

        }
        return null; // 没有找到,返回 null
    }



//    删除第一个数值为 key 的节点
    /*
    三种情况:
    1.空链表,不用删了
    2.位于首节点
    3.在中间位置
    * */
    public void remove(int key) {
        // 1. 空链表
        if(this.head == null) {
            return;      // 空链表,不用删了
        }

        // 该数值的节点,位于首节点
        if(key == this.head.data) {
            this.head = this.head.next;
            return ;
        }
        Node prev = searchPrev(key);  // 该数值的前驱节点
        if(null == prev) {
            System.out.println("该数值的节点,不存在");
            return;
        }
        prev.next = prev.next.next;

    }



//    删除所有数值为 key 的节点
    public void removeKey(int key) {
        Node prev = this.head;
        Node cur = this.head.next;

        while(cur != null) {

            if(key == cur.data) {   // 对应数值节点的删除
                prev.next = cur.next;
                cur = cur.next;
            } else {
                prev = cur;
                cur = cur.next;
            }
            // 首节点删除
            if(key == this.head.data) {
                this.head = this.head.next;
            }


        }

    }



//    清空单链表
    public void clear() {
        this.head = null;
    }
    /*
    注意:明白一点:JVM是有自动回收空间的时候,但是只有没有对象
    引用它的时候,JVM虚拟机才会,回收该空间的,在有人引用了它的时候是不会
    被回收的,当我们把首节点置为null的时候,也就不会有对下面的节点,引用了
    JVM虚拟机就会自动回收内存了
    * */

}


TestDemo.java

  • main
package Project.LinkList;


public class TestDemo {
    public static void main(String[] args) {
        test1();      // 测试一
        System.out.println("************************");
        test2();      // 测试二
        System.out.println("************************");
        test3();      // 测试三



    }

    public static void test1() {
        // 创建单链表的实例
        MyLinkList myLinkList = new MyLinkList();
        myLinkList.addFirst(0);      // 头插法
        myLinkList.deleteList();           // 尾删法
        myLinkList.disPlay();              // 打印单链表
        myLinkList.addFirst(9);      // 头插法
        myLinkList.addFirst(99);
        myLinkList.addFirst(999);
        myLinkList.disPlay();              // 打印单链表
        myLinkList.addList(10);      // 头插法
        myLinkList.addList(100);
        myLinkList.addList(1000);
        myLinkList.disPlay();              // 打印单链表
        myLinkList.deleteList();           // 尾删法
        myLinkList.disPlay();              // 打印单链表
    }

    public static void test2() {
        // 创建单链表的实例
        MyLinkList myLinkList = new MyLinkList();

        myLinkList.addFirst(9);                // 头插法
        myLinkList.addFirst(99);
        myLinkList.addFirst(999);
        myLinkList.disPlay();                        // 打印单链表
        System.out.println(myLinkList.contains(0));  // 查找是否含有对应数值的节点
        System.out.println(myLinkList.contains(999)); //
        System.out.println(myLinkList.size());        // 查找单链表中含有多少了节点
        myLinkList.addIndex(3,0);         // 在某个序号中插入节点
        myLinkList.addIndex(0,10);
        myLinkList.addIndex(1,1000);
        myLinkList.disPlay();                          // 打印单链表
        myLinkList.remove(0);                      // 删除第一个该数值的节点
        myLinkList.remove(10);
        myLinkList.disPlay();                           // 打印单链表
    }

    public static void test3() {
        // 创建节点
        MyLinkList myLinkList = new MyLinkList();

        myLinkList.addList(3);                   // 尾插法
        myLinkList.addList(6);
        myLinkList.addList(6);
        myLinkList.addList(3);
        myLinkList.addList(3);
        myLinkList.addList(3);
        myLinkList.addList(3);
        myLinkList.addList(2);
        myLinkList.addList(3);
        myLinkList.disPlay();                          // 打印该单链表
        myLinkList.removeKey(3);                       // 删除所有该数值的节点
        myLinkList.disPlay();                          // 打印该单链表
        myLinkList.clear();                            // 清空单链表
        System.out.println();
        System.out.println();
    }
}

最后限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家!

后会有期,江湖再见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值