单链表的实现(单向无头不循环链表)+增删改查等功能

注意:
* 如果想弄清楚单链表的原理,可以通过画图+代码去理解。
* 我的单链表实现了11个功能,文章前面具体列出了每个方法的代码,以及详细的代码注释和个人解释。
* 如果只要整个源代码,可以直接到文章末尾。包含完整的单链表源代码和main函数测试代码,以及每个方法的测试结果。
* 
* 码字不易,也达不到深入理解,但都是经过测试可以运行的。如果有问题,欢迎提出,欢迎白嫖。
1. 实现链表中的单向无头不循环链表
  • 单向指的是每个节点只有后继节点的引用。
  • 无头指的是,不含有头节点,也就是头节点不固定。在本次单链表实现中,为了方便打印,查找等功能,我们将会定义一个可变头节点
  • 不循环指的是,尾结点指向空,整个链表不构成一个环。
2. 单链表数据结构
  1. 每个单链表由n个节点构成。一个节点有两个域,一个data值域,用来存放当前节点的值。另外一个域是引用域,用来存放后继节点的引用。因此,我们可以定义一个节点类,其中包含基本数据类型data,以及后继节点的引用的引用类型next。
/**
 * Node节点类
 */
class Node {
    public int data;
    public Node next;

    public Node(int data) {
        this.data = data;
    }
}
  1. 单链表中包含许多方法,比如插入,删除,等等。我们为了方便方法,可以在单链表类开始,定义一个全局引用head。
//单链表的实现类
public class MySinglyLinkedList {
    //头节点
    public Node head;
    }
  1. 单链表类中还有许多方法,接下来,将一一实现。
3.单链表实现增删改查等功能
1. 头插法:每次插入数据,都在链表的第一个位置。
/**
     * 头插法
     * @param data
     */
    public void addFirst(int data) {
        //将data数据变成一个节点类型
        Node node = new Node(data);
        //欲要插入的节点的后继节点指向原来头节点
        node.next = this.head;
        //头节点置为新的节点
        this.head = node;
    }
2. 尾插法:每次插入的数据放在最后,即接在原来尾结点后面。
    /**
     * 尾插法
     * @param data
     */
    public void addLast(int data) {
        Node node = new Node(data);
        //判断头节点是否为空
        if (this.head == null) {
            this.head = node;
            return;
        }
        //初始化一个节点,并指向头节点
        Node cur = this.head;
        //循环,找到尾结点
        while (cur.next != null) {
            cur = cur.next;
        }
        //将尾结点的后继节点指向欲要插入节点
        cur.next = node;
        //新插入节点成为新的尾结点,所以next域置为空
        node.next =null;
    }
3. 打印链表:因为单链表有n个节点,所以用自己的打印方法,可以很方便的打印一个单链表。
/**
     * 打印单链表
     */
    public void display() {
        //初始化一个节点
        Node cur = this.head;
            //循环,打印每一个节点的data域,即每个节点的值
            while (cur != null) {
            System.out.print(cur.data+"  ");
            cur = cur.next;
        }
        System.out.println();
    }
4. 获取单链表长度:通过循环,从头节点移动到尾结点,每移动一个节点,计数变量count加一
/**
     * 获取单链表长度
     * @return
     */
    public int size() {
        //初始化一个int类,用来计数
        int count = 0;
        Node cur = this.head;
        //循环,每移动一个节点,count加一
        while (cur != null) {
            cur = cur.next;
            count++;
        }
        return count;
    }
5. 查找index号位置节点的前驱节点。头节点从0开始计算,查找某一个位置的前一个节点。
/**
     * 查找index号位置节点的前驱节点
     * @param index
     * @return
     */
    public Node searchPrevIndex(int index) {
        //判断位置是否合法,size()方法调用的前面第4个写的求链表长度方法
        if (index <= 0 || index >= size()) {
            return null;
        }
        Node prev = this.head;
        int count = 0;
        //循环,从0开始,小于index-1的位置,即index的前驱结点
        while (count < index - 1) {
            prev = prev.next;
            count++;
        }
        //返回index的前驱结点
        return prev;
    }
6. 任意位置插入:一个下标index,一个数据data。
/**
     * 任意位置插入,下标从0开始
     * @param index
     * @param data
     */
    public void addIndex(int index,int data) {
        //如果插入第一个,则直接调用前面写的头插法方法
        if (index == 0) {
            addFirst(data);
            //如果插入位置等于单链表长度,即是尾插法,直接调用前面写的尾插方法
        } else if (index == size()) {
            addLast(data);
        } else {
            //调用通过下标查找前驱节点的方法
            Node cur = searchPrevIndex(index);
            Node node = new Node(data);
            //将要插入的节点的next域指向前驱节点的next域
            node.next = cur.next;
            //前驱节点的next域指向新节点
            cur.next = node;
        }
    }
7. 查找是否包含某个关键字key。
/**
     * 查找是否包含某个关键字
     * @param key
     * @return
     */
    public boolean contains(int key) {
        //判断头节点是否为空
        if (this.head == null) {
            return false;
        } else {
            Node cur = this.head;
            //循环
            while (cur != null) {
                //判断当前节点的data域是否等于key
                if (cur.data == key) {
                    return true;
                }
                //cur节点后移一位
                cur = cur.next;
            }
            return false;
        }
    }
8. 查找关键字key的前驱节点:通过查找单链表中是否含有关键字key,没有返回null,有则返回key节点的前驱节点。
/**
     * 查找关键字key的前驱
     * @param key
     * @return
     */
    public Node searchPrevKey(int key) {
        Node prev = this.head;
        //循环
        while (prev.next != null) {
            //判断当前节点的下一个节点的data域是否等于关键字key
            if (prev.next.data == key) {
                return prev;
            }
            //当前节点后移一位
            prev = prev.next;
        }
        return null;
    }
9. 删除第一次出现的关键字:只删除一次,没有关键字则不删除。
/**
     * 删除第一个出现的关键字key
     * @param key
     */
    public void removeKey(int key) {
        //判断头节点是否为空
        if (this.head == null) {
            return;
        }
        //调用前面写的,通过关键字key查找前驱节点的方法
        Node del = searchPrevKey(key);
        //如果删除的节点的前驱节点为空,即删除单链表的头节点
        if (del == null) {
            //直接将头节点指向头节点的下一个节点
            this.head = this.head.next;
            //判断,如果删除节点的前驱节点,它的下一个的下一个节点为空,即删除尾结点
        } else if (del.next.next == null) {
            //直接将删除节点的前驱节点的next域置为空
            del.next = null;
        } else {
            //否则,删除节点的前驱节点,它的next指向前驱节点的下一个的下一个
            del.next = del.next.next;
        }
    }
10. 删除单链表中,所有关键字为key的节点。此方法不用循环调用前面删除一个关键字的方法,而是遍历单链表一次,删除所有关键字节点。
  • 首先定义一个无用节点,值随便给,让它指向头节点。
  • 然后从这个节点的下一个节点开始判断,这样可以方便的解决,需要删除头节点关键字而带来的麻烦。
/**
     * 删除所有关键字为key
     * @param key
     */
    public void removeKeyAll(int key) {
        //判断头节点
        if (this.head == null) {
            return;
        }
        //初始化一个无用的头节点
        Node myHead = new Node(-1);
        //再定义一个节点用来保存自己初始化的无用节点,后面会用到
        Node temp = myHead;
        //让这个无用节点的next域指向头节点
        myHead.next = this.head;

        //循环
        while (myHead.next != null) {
            //从无用节点的下一个节点的data域判断是否等于关键字
            if (myHead.next.data == key) {
                //判断,如果当前节点的下一个节点的下一个next指向null,即代表将要删除尾结点
                if (myHead.next.next == null) {
                    //直接将当前节点额next域置为空
                    myHead.next = null;
                    //并返回,因为最后一个节点都遍历完了,不再需要执行下面代码
                    return;
                } else {
                    //否则,即删除的不是尾结点且不等于关键字,那么后移
                    myHead.next = myHead.next.next;
                }
            }
            //无用节点后移
            myHead = myHead.next;
        }
        //因为无用节点和头节点都乱了,所以欲要重置head,指向刚刚保存无用节点的下一个节点
        this.head = temp.next;
    }
11. 清空链表。如果直接将头节点置为空,那么头节点后面的节点的next域还有指向关系,这些存在指向关系的节点,因为我们不知道他们的引用(因为我们已经把头节点的引用置为空了),而引用又不为空,所以JVM不会自动回收。就会造成内存泄露。为此,我们必须利用循环,将每一个节点的next域都置为空,这样才会被系统自动回收。
/**
     * 清空单链表,防止内存泄漏
     * 将单链表每个next都置为空
     */
    public void clear() {
        //循环,将每一个节点的next域都置为空
        while (this.head != null) {
            Node cur = this.head.next;
            this.head = null;
            this.head = cur;
        }
    }
4. 完整源代码
  • 单链表类(包含单链表的方法+节点Node类)
package day9_10_20;

/**
 * @author:fover
 * @version:1.0
 * @function:单链表的实现
 */
public class MySinglyLinkedList {
    //头节点
    public Node head;

    /**
     * 头插法
     * @param data
     */
    public void addFirst(int data) {
        //将data数据变成一个节点类型
        Node node = new Node(data);
        /*
        * 可以不判断头节点是否为空
        if (head == null) {
            head = node;
            return;
        }
         */
        //欲要插入的节点的后继节点指向原来头节点
        node.next = this.head;
        //头节点置为新的节点
        this.head = node;
    }

    /**
     * 尾插法
     * @param data
     */
    public void addLast(int data) {
        Node node = new Node(data);
        //判断头节点是否为空
        if (this.head == null) {
            this.head = node;
            return;
        }
        //初始化一个节点,并指向头节点
        Node cur = this.head;
        //循环,找到尾结点
        while (cur.next != null) {
            cur = cur.next;
        }
        //将尾结点的后继节点指向欲要插入节点
        cur.next = node;
        //新插入节点成为新的尾结点,所以next域置为空
        node.next =null;
    }

    /**
     * 任意位置插入,下标从0开始
     * @param index
     * @param data
     */
    public void addIndex(int index,int data) {
        //如果插入第一个,则直接调用前面写的头插法方法
        if (index == 0) {
            addFirst(data);
            //如果插入位置等于单链表长度,即是尾插法,直接调用前面写的尾插方法
        } else if (index == size()) {
            addLast(data);
        } else {
            //调用通过下标查找前驱节点的方法
            Node cur = searchPrevIndex(index);
            Node node = new Node(data);
            //将要插入的节点的next域指向前驱节点的next域
            node.next = cur.next;
            //前驱节点的next域指向新节点
            cur.next = node;
        }
    }

    /**
     * 打印单链表
     */
    public void display() {
        //初始化一个节点
        Node cur = this.head;
            //循环,打印每一个节点的data域,即每个节点的值
            while (cur != null) {
            System.out.print(cur.data+"  ");
            cur = cur.next;
        }
        System.out.println();
    }

    /**
     * 获取单链表长度
     * @return
     */
    public int size() {
        //初始化一个int类,用来计数
        int count = 0;
        Node cur = this.head;
        //循环,每移动一个节点,count加一
        while (cur != null) {
            cur = cur.next;
            count++;
        }
        return count;
    }

    /**
     * 查找index号位置节点的前驱节点
     * @param index
     * @return
     */
    public Node searchPrevIndex(int index) {
        //判断位置是否合法,size()方法调用的前面第4个写的求链表长度方法
        if (index <= 0 || index >= size()) {
            return null;
        }
        Node prev = this.head;
        int count = 0;
        //循环,从0开始,小于index-1的位置,即index的前驱结点
        while (count < index - 1) {
            prev = prev.next;
            count++;
        }
        //返回index的前驱结点
        return prev;
    }

    /**
     * 查找关键字key的前驱
     * @param key
     * @return
     */
    public Node searchPrevKey(int key) {
        Node prev = this.head;
        //循环
        while (prev.next != null) {
            //判断当前节点的下一个节点的data域是否等于关键字key
            if (prev.next.data == key) {
                return prev;
            }
            //当前节点后移一位
            prev = prev.next;
        }
        return null;
    }

    /**
     * 查找是否包含某个关键字
     * @param key
     * @return
     */
    public boolean contains(int key) {
        //判断头节点是否为空
        if (this.head == null) {
            return false;
        } else {
            Node cur = this.head;
            //循环
            while (cur != null) {
                //判断当前节点的data域是否等于key
                if (cur.data == key) {
                    return true;
                }
                //cur节点后移一位
                cur = cur.next;
            }
            return false;
        }
    }

    /**
     * 删除第一个出现的关键字key
     * @param key
     */
    public void removeKey(int key) {
        //判断头节点是否为空
        if (this.head == null) {
            return;
        }
        //调用前面写的,通过关键字key查找前驱节点的方法
        Node del = searchPrevKey(key);
        //如果删除的节点的前驱节点为空,即删除单链表的头节点
        if (del == null) {
            //直接将头节点指向头节点的下一个节点
            this.head = this.head.next;
            //判断,如果删除节点的前驱节点,它的下一个的下一个节点为空,即删除尾结点
        } else if (del.next.next == null) {
            //直接将删除节点的前驱节点的next域置为空
            del.next = null;
        } else {
            //否则,删除节点的前驱节点,它的next指向前驱节点的下一个的下一个
            del.next = del.next.next;
        }
    }

    /**
     * 删除所有关键字为key
     * @param key
     */
    public void removeKeyAll(int key) {
        //判断头节点
        if (this.head == null) {
            return;
        }
        //初始化一个无用的头节点
        Node myHead = new Node(-1);
        //再定义一个节点用来保存自己初始化的无用节点,后面会用到
        Node temp = myHead;
        //让这个无用节点的next域指向头节点
        myHead.next = this.head;

        //循环
        while (myHead.next != null) {
            //从无用节点的下一个节点的data域判断是否等于关键字
            if (myHead.next.data == key) {
                //判断,如果当前节点的下一个节点的下一个next指向null,即代表将要删除尾结点
                if (myHead.next.next == null) {
                    //直接将当前节点额next域置为空
                    myHead.next = null;
                    //并返回,因为最后一个节点都遍历完了,不再需要执行下面代码
                    return;
                } else {
                    //否则,即删除的不是尾结点且不等于关键字,那么后移
                    myHead.next = myHead.next.next;
                }
            }
            //无用节点后移
            myHead = myHead.next;
        }
        //因为无用节点和头节点都乱了,所以欲要重置head,指向刚刚保存无用节点的下一个节点
        this.head = temp.next;
    }

    /**
     * 清空单链表,防止内存泄漏
     * 将单链表每个next都置为空
     */
    public void clear() {
        //循环,将每一个节点的next域都置为空
        while (this.head != null) {
            Node cur = this.head.next;
            this.head = null;
            this.head = cur;
        }
    }

}

/**
 * Node节点类
 */
class Node {
    public int data;
    public Node next;

    public Node(int data) {
        this.data = data;
    }
}

  • main方法测试类
package day9_10_20;

import org.w3c.dom.Node;

/**
 * @author:fover
 * @date:2020/10/20 10:31
 * @version:1.0
 * @function:单链表测试
 */
public class MySinglyLinkedListTest {
    public static void main(String[] args) {
        MySinglyLinkedList myList = new MySinglyLinkedList();

        System.out.println("1、头插法1,2,3,4,5测试:");
        myList.addFirst(1);
        myList.addFirst(2);
        myList.addFirst(3);
        myList.addFirst(4);
        myList.addFirst(5);
        myList.display();

        System.out.println("2、打印测试:");
        myList.display();

        System.out.println("3、尾插法6,7,8,9,10测试:");
        myList.addLast(6);
        myList.addLast(7);
        myList.addLast(8);
        myList.addLast(9);
        myList.addLast(10);
        myList.display();

        System.out.println("4、查找3号位置前驱节点测试:");
        System.out.println(myList.searchPrevIndex(3).data);

        System.out.println("5、获取单链表长度测试:");
        System.out.println(myList.size());

        System.out.println("6、在10号位置插入元素99测试:");
        myList.addIndex(10,99);
        myList.display();

        System.out.println("7、是否包含关键字99测试:");
        System.out.println(myList.contains(99));

        System.out.println("8、查找关键字99的前驱测试:");
        System.out.println(myList.searchPrevKey(99).data);

        System.out.println("9、删除第一个出现的关键字99测试:");
        myList.removeKey(99);
        myList.display();

        System.out.println("10、删除所有关键字8测试:");
        myList.addIndex(4,8);
        myList.addIndex(1,8);
        myList.addIndex(0,8);
        System.out.print("删除前:");
        myList.display();
        System.out.print("删除后:");
        myList.removeKeyAll(8);
        myList.display();

        System.out.println("11、清空单链表测试(防止内存泄露):");
        myList.clear();
        myList.display();
        System.out.println("-----------结束------------");
    }
}
  • 测试结果展示
    在这里插入图片描述
欢迎白嫖,感谢提出问题^ - ^
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单向链表是由若干个节点组成的,每个节点都包含一个数据域和一个指向下一个节点的指针域。在单向链表中,只能从头节点开始遍历链表,每个节点只能访问其后继节点。下面是用单向链表实现增删改查的代码实现: ```python # 定义单向链表节点类 class ListNode: def __init__(self, val): self.val = val self.next = None # 定义单向链表类 class LinkedList: def __init__(self): self.head = None # 添加节点 def add_node(self, val): new_node = ListNode(val) if not self.head: self.head = new_node else: curr = self.head while curr.next: curr = curr.next curr.next = new_node # 删除节点 def delete_node(self, val): if not self.head: return if self.head.val == val: self.head = self.head.next else: curr = self.head while curr.next: if curr.next.val == val: curr.next = curr.next.next return curr = curr.next # 修改节点 def update_node(self, old_val, new_val): curr = self.head while curr: if curr.val == old_val: curr.val = new_val return curr = curr.next # 查找节点 def search_node(self, val): curr = self.head while curr: if curr.val == val: return True curr = curr.next return False ``` 在上面的代码中,我们定义了一个单向链表节点类 ListNode ,包含一个数据域 val 和一个指向下一个节点的指针域 next 。然后定义了单向链表类 LinkedList ,包含一个头节点 head 。在 LinkedList 中,我们实现了 add_node() 方法用于添加节点,delete_node() 方法用于删除节点,update_node() 方法用于修改节点,search_node() 方法用于查找节点。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值