Singly linked list data structure and related algorithm problems

1 Explanation of singly linked list

(1) The composition of a singly linked list

Singly linked list: A singly linked list is a linear table composed of various nodes (Node). The data stored in the memory is not continuous. The data stored in it is scattered in the memory. Each node can only and only it can Know the storage location of the next node. A singly linked list is composed of N nodes (Node), and each Node records the data (data) of this Node and the address (pointer, reference) of the next Node . There is only one head node (Head) exposed to the outside. All operations on the linked list are performed directly or indirectly through its head node, and the last node pointer field points to NULL.

From the above information, we know that a linked list consists of the following elements:

  • Composition: data field + pointer field
  • The data field stores specific data, and the pointer field stores the address that points to the next node. Simply put, it stores the address of the next node. Note that the location information of the next node is stored here, not the address information of the data field.
  • In Java, it can be simply understood that a node is an object, and an object includes data attributes and reference attributes, and the reference points to the next object (node). When you define a variable of a reference type in java, you actually define the address where the object is located.
  • The tail node of the singly linked list points to NULL

The difference between java references and pointers can be referred to as follows:

引用才是真正含义上的地址,指针不是地址是内存地址,两者关注对象不一样,引用关注的是对象本身,地址随对

象的改变而改变,因为在创建对象的时候,既存数据本身,也将该对象的地址保存其来,和普通变量的地址不一

样,普通变量的地址就指的是内存地址,C中的指针关注的对象就是内存地址,他不关心该地址存储的是什么,只

关系该地址本身,如果该地址内存空间中东西搬走了,指针不会自动改变指向,java中对象重建了地址也变了)

The specific definition refers to the following figure:

Java is understood in terms of objects as shown in the figure below:

2 Realization of singly linked list

3 Singly linked list inversion

(1) Iterative method implementation

First look at the schematic diagram, take the linked list 1->2->3->4->5as an example:

The problem-solving ideas of the linked list:

(1) Known information head node

(2) Define the predecessor and successor nodes

(3) Construct auxiliary nodes

(4) Build a virtual head node (some problems with handling head nodes can be avoided)

 

package jttl.jxresearch.com.hive.udf.test;

import java.util.HashSet;
import java.util.TreeSet;

public class MyLink {

//   // Node head = null;
//    class Node {
//
//       Node next = null;
//       int data;
//
//       public Node(int data){
//
//           this.data = data;
//
//       }
//   }

    public static void main(String[] args) {


        MyLink list = new MyLink();
        Node node = new Node(1);
        list.insert(1,node);
        list.insert(2,node);
        list.insert(6,node);
        list.insert(3,node);
        list.insert(4,node);
        list.insert(5,node);
        list.insert(6,node);
        list.display(node);
        Node head = list.removeElements(node, 6);
        //LinkNode head = list.removeElementsByStack(list.head, 6);
        //LinkNode head = list.removeRepeat(list.head);
        list.display(head);
    }


    /**
     * 向链表中插入数据
     *
     * @param data
     */

    public void addNode(int data,Node head){

        Node node = new Node(data);
        if (head == null) {
            head = node;
            return;
        }
       //构建辅助节点
        Node cur = head;
        while(cur.next != null){
            cur = cur.next;

        }
        //遍历结束时,此时tmp节点地址指向为null说明是最后一个节点,只需要将tmp.next赋值为添加节点地址即可
        cur.next = node;

    }
    public void insert(int val,Node head){
        if(head==null){
            head = new Node(val);
        }else{
            Node cur = head;
            while(cur.next!=null){
                cur = cur.next;
            }
            cur.next = new Node(val);
        }
    }

    public void display(Node head){
        System.out.print("list:");
        Node cur = head;
        while(cur!=null){
            System.out.print(cur.data+"->");
            cur = cur.next;
        }
        System.out.print("null");
        System.out.println();
    }

    /**
     *
     * @return 返回节点长度
     */
    public int length(Node head){

        int length = 0 ;

        Node tmp = head;

        while(tmp != null){

            length ++;
            tmp = tmp.next;

        }

        return length;

    }

    /**
     * 删除链表中指定值的节点
     *
     * @param head,dataue
     * @return
     */
   public Node removeElements(Node head,int data){


      //如果是头节点需要删除

       while(head != null){

           if(head.data != data){

               break;

           }

           head = head.next;


       }

       //如果不是头节点需要删除

       Node pre = head;
       Node cur = head.next;
       while (cur != null){

           if (cur.data != data){
               pre = cur;
               
           }
           pre.next = cur.next;

           cur = cur.next;
       }

       return head;
   }
   //这道题还有一个变种,就是删除重复的节点,不是将重复的所有节点删除,而是保留第一个节点,后面如果重复,就删除。这里也给出代码:
   //先给出一道其他算法去重的题目理解一下
    /**
     * 把字符串去重,并升序排序
     * @param str
     * @return
     */
    public static String sort2(String str) {

        //把String变成单一字符数组(获取字符数组)
        String[] chars = str.split("");

        //把字符串数组放入TreeSet中,根据set元素不重复的特性去掉重复元素。根据treeSet的有序性排序
        TreeSet<String> treeSet = new TreeSet();
        for (String s : chars) {
            treeSet.add(s);
        }

        //把treeSet拼接成字符串
        str = "";
        for (String s : treeSet) {
            str += s;
        }
        return str;
    }

    public Node removeRepeat(Node head){
        if(head==null){
            return null;
        }
        HashSet<Integer> set = new HashSet<Integer>();
        set.add(head.data);
        //定义前驱节点
        Node pre = head;
        //定义当前节点
        Node cur = head.next;
        while(cur!=null){
            //如果当前节点不为null则判断当前节点的数据是否在集合中,存在就删除,不存在就继续遍历
            if(set.contains(cur.data)){
                //删除当前节点
                pre.next = cur.next;
            }else{
                set.add(cur.data);
                pre = cur;
            }
            cur = cur.next;
        }
        return head;
    }


    /**
     * 单链表的反转
     *
     * @param head,dataue
     * @return
     */

    public Node reverseLink(Node head){

        //定义前驱节点,初始化为NULL。链表反转后头节点会指向NULL所以将前驱节点定义为NULL
        Node pre = null;
        //定义当前节点
        Node cur = head;
       //当当前节点不为NULL的时候,进行遍历
        while(cur != null){
           //定义下一节点。将当前节点的的指向信息保存起来,因为在下一步,当前节点的指向信息被覆盖了
            Node tmp = cur.next;
            //将当前节点的中指向下一个节点的地址修改为前驱节点
            cur.next = pre;
            //在遍历的过程中,游标(指针)移动。每遍历一次,前驱节点向前移动一次,当前节点向前移动一次。
            //也就是将当前节点的地址给前驱节点,当前节点移动到下一个节点的位置,如此循环
            pre = cur;
            cur = tmp;

        }
        //此时pre是头节点
      return pre;
    }

    /**
     * 单链表的反转:反转从位置m到n的链表(1=<m<=n<=链表长度)。虚拟头节点
     *
     * @param head,dataue
     * @return
     *
     * 解析:本题,还有下一个要介绍的困难级别的题目,算是一种类型题,它们都是要对链表中特定区间中的节点进行操作。面对这种题目,有固定的套路可以帮你简化解题思路,套路如下:
     *
     *     给链表添加虚拟头节点 dummy,这样就不需要再单独考虑头节点了,可以省去很多麻烦;
     *     找到需要操作的链表区间,区间起始节点用 start 表示,结束节点用 end 表示;
     *     对区间上的链表进行操作;
     *     将操作后的链表重新接回原链表,这里我们需要另外两个变量,前驱节点 prev 和后继节点 successor。
     */



}

Linked lists deal with problems through virtual head nodes

Construct a virtual head node for the linked list, as shown in the figure below

 Construct a dummy head node as NULL and call it dummyHead

After constructing the dummyHead node, it is actually to make the predecessor node point to NULL instead of head

public class LinkedList<E> {

    private class Node{
        public E e;
        public Node next;

        public Node(E e, Node next){
            this.e = e;
            this.next = next;
        }

        public Node(E e){
            this(e, null);
        }

        public Node(){
            this(null, null);
        }

        @Override
        public String toString(){
            return e.toString();
        }
    }

}

public class LinkedList<E> {

    private class Node{
        public E e;
        public Node next;

        public Node(E e, Node next){
            this.e = e;
            this.next = next;
        }

        public Node(E e){
            this(e, null);
        }

        public Node(){
            this(null, null);
        }

        @Override
        public String toString(){
            return e.toString();
        }
    }

    private Node head;
    private int size;

    public LinkedList(){
        head = null;
        size = 0;
    }

    // 获取链表中的元素个数
    public int getSize(){
        return size;
    }

    // 返回链表是否为空
    public boolean isEmpty(){
        return size == 0;
    }

    // 在链表头添加新的元素e
    public void addFirst(E e){
//        Node node = new Node(e);
//        node.next = head;
//        head = node;

        head = new Node(e, head);
        size ++;
    }



    // 在链表的index(0-based)位置添加新的元素e
    // 在链表中不是一个常用的操作,练习用:)
    public void add(int index, E e){

        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Illegal index.");

        if(index == 0)
            addFirst(e);
        else{
            Node prev = head;
            for(int i = 0 ; i < index - 1 ; i ++){

                prev = prev.next;

             }
                

//            Node node = new Node(e);
//            node.next = prev.next;
//            prev.next = node;
           //优雅的写法
            prev.next = new Node(e, prev.next);
            size ++;
        }
    }

    // 在链表的index(0-based)位置添加新的元素e
    // 在链表中不是一个常用的操作,练习用:)
   //优雅的写法,通过虚拟头结点,避免处理头节点的问题
    public void add(int index, E e){

        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Illegal index.");

        Node dummyHead  = new Node(null,null);

        Node prev = dummyHead;
        for(int i = 0 ; i < index ; i ++)
            prev = prev.next;

        prev.next = new Node(e, prev.next);
        size ++;
    }


    // 在链表末尾添加新的元素e
    public void addLast(E e){
        add(size, e);
    }
}

public class LinkedList<E> {

    private class Node{
        public E e;
        public Node next;

        public Node(E e, Node next){
            this.e = e;
            this.next = next;
        }

        public Node(E e){
            this(e, null);
        }

        public Node(){
            this(null, null);
        }

        @Override
        public String toString(){
            return e.toString();
        }
    }

    private Node dummyHead;
    private int size;

    public LinkedList(){
        dummyHead = new Node();
        size = 0;
    }

    // 获取链表中的元素个数
    public int getSize(){
        return size;
    }

    // 返回链表是否为空
    public boolean isEmpty(){
        return size == 0;
    }

    // 在链表的index(0-based)位置添加新的元素e
    // 在链表中不是一个常用的操作,练习用:)
    public void add(int index, E e){

        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Illegal index.");

        Node prev = dummyHead;
        for(int i = 0 ; i < index ; i ++)
            prev = prev.next;

        prev.next = new Node(e, prev.next);
        size ++;
    }

    // 在链表头添加新的元素e
    public void addFirst(E e){
        add(0, e);
    }

    // 在链表末尾添加新的元素e
    public void addLast(E e){
        add(size, e);
    }

    // 获得链表的第index(0-based)个位置的元素
    // 在链表中不是一个常用的操作,练习用:)
    public E get(int index){

        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Get failed. Illegal index.");

        Node cur = dummyHead.next;
        for(int i = 0 ; i < index ; i ++)
            cur = cur.next;
        return cur.e;
    }

    // 获得链表的第一个元素
    public E getFirst(){
        return get(0);
    }

    // 获得链表的最后一个元素
    public E getLast(){
        return get(size - 1);
    }

    // 修改链表的第index(0-based)个位置的元素为e
    // 在链表中不是一个常用的操作,练习用:)
    public void set(int index, E e){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Set failed. Illegal index.");

        Node cur = dummyHead.next;
        for(int i = 0 ; i < index ; i ++)
            cur = cur.next;
        cur.e = e;
    }

    // 查找链表中是否有元素e
    public boolean contains(E e){
        Node cur = dummyHead.next;
        while(cur != null){
            if(cur.e.equals(e))
                return true;
            cur = cur.next;
        }
        return false;
    }

    // 从链表中删除index(0-based)位置的元素, 返回删除的元素
    // 在链表中不是一个常用的操作,练习用:)
    public E remove(int index){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Remove failed. Index is illegal.");

        Node prev = dummyHead;
        for(int i = 0 ; i < index ; i ++)
            prev = prev.next;

        Node retNode = prev.next;
        prev.next = retNode.next;
        retNode.next = null;
        size --;

        return retNode.e;
    }

    // 从链表中删除第一个元素, 返回删除的元素
    public E removeFirst(){
        return remove(0);
    }

    // 从链表中删除最后一个元素, 返回删除的元素
    public E removeLast(){
        return remove(size - 1);
    }

    // 从链表中删除元素e
    public void removeElement(E e){

        Node prev = dummyHead;
        while(prev.next != null){
            if(prev.next.e.equals(e))
                break;
            prev = prev.next;
        }

        if(prev.next != null){
            Node delNode = prev.next;
            prev.next = delNode.next;
            delNode.next = null;
        }
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();

        Node cur = dummyHead.next;
        while(cur != null){
            res.append(cur + "->");
            cur = cur.next;
        }
        res.append("NULL");

        return res.toString();
    }
}

Detailed explanation of leetcode exercises

public class ListNode {

    int val;
    ListNode next;

    public ListNode(int val) {
        this.val = val;
    }

    //链表节点的构造函数
    //使用arr为参数,创建一个链表,当前的ListNode为链表的头节点
    public ListNode(int[] arr){

        if(arr.length == 0 || arr == null){

            throw new IllegalArgumentException("arr can not be empty");

        }

        this.val = arr[0];
        ListNode cur = this;
        for (int i = 1; i <arr.length ; i++) {

            cur.next = new ListNode(arr[i]);
            cur=cur.next;

        }

    }
    @Override
    public String toString(){

        StringBuilder res = new StringBuilder();

        ListNode cur = this;
        while(cur != null){
            res.append(cur.val + "->");
            cur=cur.next;
        }
        res.append("NULL");
        return res.toString();

    }
}
public class testLeetcode {
     //虚拟头节点解法
    public ListNode removeElements(ListNode head,int val){

        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
        ListNode prev=dummyHead;
        ListNode cur=prev.next;

        while(cur!=null){

            if(cur.val == val){

                prev.next=cur.next;

            }
            //不能写在else语句块中,否则while条件执行不了,while条件更新的状态一定在while语句块中
            prev = cur;
            cur=cur.next;
        }
        //返回头节点
        return dummyHead.next;


    }

    public ListNode removeElements2(ListNode head,int val){


        //如果是头节点需要删除

        while(head != null){

            if(head.val != val){

                break;

            }

            head = head.next;


        }

        //如果不是头节点需要删除

        ListNode pre = head;
        ListNode cur = head.next;
        while (cur != null){

            if (cur.val == val){
                pre.next = cur.next;

            }
            pre = cur;
            cur = cur.next;
        }

        return head;
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 4, 6, 5, 6};
        ListNode head = new ListNode(nums);
        System.out.println(head);
        ListNode res = (new testLeetcode()).removeElements(head, 1);
        System.out.println(res);
        
    }
}

 

Guess you like

Origin blog.csdn.net/godlovedaniel/article/details/115121236