7. Linear list: realize various operations of single linked list

Table of contents

Define the node class:

1. Basic operation of single linked list

2. Delete duplicate data

3. Find the kth element from the bottom

Fourth, realize the inversion of the linked list

5. Output linked list from tail to head

6. Find the middle node

7. Detect whether the linked list has a ring

Method 1. Judgment of fast and slow pointer movement

Method 2: Use the Set<> collection to record Node elements, if there are repeated elements, it is considered to have a cycle

Extension: If the linked list has a ring, find out which node is the entry point of the ring

8. Delete the specified node without knowing the head pointer

9. How to judge whether two linked lists intersect and find out the intersecting nodes


Define the node class:

class Node{
    public int data;
    public Node next;
    
    public NodeList(int num){
        this.data = num;
    }
} 

1. Basic operation of single linked list

           The basic operations to implement a singly linked list include adding nodes, deleting nodes, sorting, printing, and calculating length.

/**
 * @author Administrator
 * 实现单链表的基本操作,增加节点、删除节点、排序、打印、计算长度
 */
public class MyLinkedList {
    Node head = null;//链表头的作用
    
    /**向链表中插入数据
     * @param d:插入数据的内容
     */
    public void addNode(int d){
        Node newNode=new Node(d);
        if(head==null){
            head=newNode;
            return;
        }
        Node tmp=head;
        while(tmp.next!=null){
            tmp=tmp.next;
        }
        //add Node to end
        tmp.next=newNode;
    }
    
    /**
     * @param index:删除第index个节点
     * @return 成功返回true,失败返回false
     */
    public Boolean deleteNode(int index){
        if(index<1||index>length()){
            return false;//删除元素位置不合理
        }
        //删除链表中的第一个元素
        if(index==1){
            head=head.next;
            return true;
        }
        int i=1;
        Node preNode=head;
        Node curNode=preNode.next;
        while(curNode!=null){
            if(i==index){
                preNode.next=curNode.next;
                return true;
            }
            preNode=curNode;
            curNode=curNode.next;
            i++;
        }
        return true;
    }
    /**
     * @return 返回链表的长度length
     */
    public int length(){
        int length=0;
        Node tmp=head;
        while(tmp!=null){
            length++;
            tmp=tmp.next;
        }
        return length;
    }
    /**
     * 对链表进行排序
     * @return 返回排序后的头结点
     */
    public Node orderList(){
        Node nextNode=null;
        int temp=0;
        Node curNode=head;
        while(curNode.next!=null){
            nextNode=curNode.next;
            while(nextNode!=null){
                if(curNode.data>nextNode.data){
                    temp=curNode.data;
                    curNode.data=nextNode.data;
                    nextNode.data=temp;
                }
                nextNode=nextNode.next;
            }
            curNode=curNode.next;
        }
        return head;
    }
    /**
     * 打印链表中所有数据
     */
    public void printList(){
        Node tmp=head;
        while(tmp!=null){
            System.out.print(tmp.data+" ");
            tmp=tmp.next;
        }
        System.out.println();
    }
}

2. Delete duplicate data

/**
     * 从链表中删除数据的第一种方法
     * 遍历链表,把遍历到的数据存到一个Hashtable中,在遍历过程中若当前访问的值在Hashtable
     * 中存在,则可以删除
     * 优点:时间复杂度低    缺点:需要额外的存储空间来保存已访问过得数据
     */
    public void deleteDuplecate1(){
        Hashtable<Integer,Integer> table=new Hashtable<Integer,Integer>();
        Node tmp=head;
        Node pre=null;
        while (tmp!=null) {
            if(table.containsKey(tmp.data))
                pre.next=tmp.next;
            else{
                table.put(tmp.data, 1);
                pre=tmp;
            }
            tmp=tmp.next;
        }
    }
    /**
     * 从链表中删除重复数据的第二种方法
     * 双重循环遍历
     * 优缺点很明显
     */
    public void deleteDuplecate2(){
        Node p=head;
        while (p!=null) {
            Node q=p;
            while(q.next!=null){
                if(p.data==q.next.data){
                    q.next=q.next.next;
                }else{
                    q=q.next;
                }
            }
            p=p.next;
        }
    }

3. Find the kth element from the bottom

     /**
     * @param k:找到链表中倒数第k个节点
     * @return 该节点
     * 设置两个指针p1、p2,让p2比p1快k个节点,同时向后遍历,当p2为空,则p1为倒数第k个节点
     */
    public Node findElem(Node head,int k){
        if(k<1||k>this.length())
            return null;
        Node p1=head;
        Node p2=head;
        for (int i = 0; i < k-1; i++) 
            p2=p2.next;
        while (p2.next!=null) {
            p2=p2.next;
            p1=p1.next;
        }
        return p1;
    }

Fourth, realize the inversion of the linked list

/**
     * 实现链表的反转
     * @param head链表的头节点
     */
    public void reverseIteratively(Node head){
        Node pReversedHead=head;
        Node pNode=head;
        Node pPrev=null;
        while (pNode!=null) {
            Node pNext=pNode.next;
            if(pNext==null)
                pReversedHead=pNode;
            pNode.next=pPrev;
            pPrev=pNode;
            pNode=pNext;        
        }
        this.head=pReversedHead;
    }

5. Output linked list from tail to head

   /**
     * 通过递归从尾到头输出单链表
     * @param head
     */
    public void printListReversely(Node head){
        if(head!=null){
            printListReversely(head.next);
            System.out.print(head.data+" ");
        }
    }

6. Find the middle node

/**
     * 查询单链表的中间节点
     * 定义两个指针,一个每次走一步,一个每次走两步...
     * @param head
     * @return q为中间节点
     */
    public Node searchMid(Node head){
        Node q=head;
        Node p=head;
        while (p!=null&&p.next!=null&&p.next.next!=null) {
            q=q.next;
            p=p.next.next;
        }
        return q;
    }

7. Detect whether the linked list has a ring

Create a linked list:

//初始化一个有环的链表Node
Node nodeA = new Node("A");
Node nodeB = new Node("B");
Node nodeC = new Node("C");
Node nodeD = new Node("D");
Node nodeE = new Node("E");
Node nodeF = new Node("F");

nodeA.next = nodeB;
nodeB.next = nodeC;
nodeC.next = nodeD;
nodeD.next = nodeE;
nodeE.next = nodeF;
nodeF.next = nodeD;//此时F节点又指向了D节点,已经产生了环状

Method 1. Judgment of fast and slow pointer movement

First of all, how to judge whether the linked list has a ring. At this time, we first need to know whether the linked list is empty. If it is not empty, continue to judge.

Idea: First define two variables, one fast and one slow, let fast take two steps at a time, and slow take one step at a time. When fast and slow meet, it means that the linked list has a ring structure. If the linked list is an acyclic structure, it will not meet when the linked list is traversed. That is, return false.

The legend is as follows:

In order to illustrate the situation in the figure,

The fast pointer is initially marked as f0, and it is incremented by 1 every time it is moved, such as f1, f2, f3....

The slow pointer is initially marked as s0, and increases by 1 every time it moves, such as s1, s2, s3....

/**
     * 判断链表是否有环 (快慢指针的方式)
     *                                       <-----------------
     *                                      |                 |
     *          [A]  ->  [B]  ->  [C]  ->  [D]  ->  [E]  ->  [F]
     *
     * 初始指针  f0
     *          s0
     * 第一次            s1       f1
     * 第二次                     s2                f2
     * 第三次                             s3/f3
     * 本例中即第三次遍历就判断出链表有环
     * @param node
     * @return
     */
    private boolean hasCycle(Node node) {
        if (node == null) {
            return false;
        }
        Node fast = node;
        Node slow = node;
        //此字段仅用来记录遍历次数
        int traverseCount = 0;
        while (fast != null && fast.next != null && slow != null) {
            fast = fast.next.next;//移动2步
            slow = slow.next;//移动1步
            traverseCount ++;
            if (fast == slow) {
                //如果碰面,就代表有环
                Log.d(TAG, "hasCycle==>有环...traverseCount="+traverseCount);
                return true;
            }
            Log.d(TAG, "hasCycle==>已经遍历次数...traverseCount="+traverseCount);
        }
        Log.d(TAG, "hasCycle==>无环");
        return false;
    }

Method 2: Use the Set<> collection to record Node elements, if there are repeated elements, it is considered to have a cycle

/**
     * 通过Set集合记录值的方式,如果有重复的数据,就代表有环
     * @param node
     * @return
     */
    private boolean hasCycle2(Node node) {
        Set<Node> nodeSet = new HashSet<>();
        //此字段仅用来记录遍历次数
        int traverseCount = 0;
        while (node != null) {
            if (nodeSet.contains(node)) {
                Log.d(TAG, "hasCycle2==>有环...traverseCount="+traverseCount);
                return true;
            }
            traverseCount ++;
            Log.d(TAG, "hasCycle2==>traverseCount="+traverseCount);
            nodeSet.add(node);
            node = node.next;
        }
        Log.d(TAG, "hasCycle2==>无环");
        return false;
    }

Extension: If the linked list has a ring, find out which node is the entry point of the ring

As shown in the figure above, the entry point is node D, and we need to find this node below

Idea: s=vt (distance=velocity*time) use equation thinking to solve equations. Because the distance is known, the speed is known (double relationship), and the time is also known (equal), so the relationship can be obtained through equations. Then use the code to reflect the relationship, and you can solve this problem.

As shown in the figure: Assuming that the linked list is Link fast is the fast pointer, Slow is the slow pointer, blue is the distance traveled by fast, green is the distance traveled by slow K is the entrance of the ring, P is the meeting between the fast and slow pointers Where, a, b, c represent three lengths respectively.

So the graph can become:

Then, let the pointer traverse from the meeting point p and the starting point first at the same time, so that since c = a, p and first meet at k, and k is the entry point.

/**
     * 如果有环,获取相遇点的node
     * @param node
     * @return
     */
    private Node getMeetNode(Node node) {
        if (node == null) {
            return null;
        }
        Node fast = node;
        Node slow = node;
        //此字段仅用来记录遍历次数
        int traverseCount = 0;
        while (fast != null && fast.next != null && slow != null) {
            fast = fast.next.next;//移动2步
            slow = slow.next;//移动1步
            traverseCount ++;
            if (fast == slow) {
                //如果碰面,就代表有环
                Log.d(TAG, "hasCycle==>有环...traverseCount="+traverseCount);
                return fast;
            }
            Log.d(TAG, "hasCycle==>已经遍历次数...traverseCount="+traverseCount);
        }
        return null;
    }

    /**
     * 如果有环,获取环出现的入口点
     * @return
     */
    public Node getCycleEntry(Node node) {
        Node meetNode = getMeetNode(node);
        Node p = meetNode;//相遇点元素的指针
        Node first = node;//链表第一个元素的指针
        while(p != first) {
            //两个指针都进行移动,当相等的时候就找到了入口点
            first = first.next;
            p = p.next;
        }
        return p;
    }

   /**
    * 将入口点打印出来:
    */
    Node entryNode = getCycleEntry(nodeA);
    Log.d(TAG, "有环的链表入口点Node Value="+entryNode.value);

8. Delete the specified node without knowing the head pointer

/**
     * 在不知道头指针的情况下删除指定节点
     * 链表尾节点无法删除,因为删除后无法使其前驱节点的next指针置为空
     * 其他节点,可以通过交换这个节点与其后继节点的值,然后删除后继节点
     * @param n
     * @return
     */
    public boolean deleteNode(Node n){
        if(n==null||n.next==null)
            return false;
        int tmp=n.data;
        n.data=n.next.data;
        n.next.data=tmp;
        n.next=n.next.next;
        return true;
    }

9. How to judge whether two linked lists intersect and find out the intersecting nodes

 /**
     * 判断两个链表是否相交
     * 如果两个链表相交,则肯定有相同的尾节点,遍历两个链表,记录尾节点,看是否相同
     * @param h1链表1的头节点
     * @param h2链表2的头结点
     * @return 是否相交
     */
    public boolean isIntersect(Node h1,Node h2){
        if(h1==null||h2==null)
            return false;
        Node tail1=h1;
        while (tail1.next!=null){ 
            tail1=tail1.next;
        }
        Node tail2=h2;
        while(tail2.next!=null){
            tail2=tail2.next;
        }
        return tail1==tail2;
    }
    /**
     * 找出相交的第一个节点
     * @param h1
     * @param h2
     * @return
     */
    public Node getFirstMeetNode(Node h1,Node h2){
        if(h1==null||h2==null)
            return null;
        Node tail1=h1;
        int len1=1;
        while (tail1.next!=null){ 
            tail1=tail1.next;
            len1++;
        }
        Node tail2=h2;
        int len2=1;
        while(tail2.next!=null){
            tail2=tail2.next;
            len2++;
        }
        if(tail1!=tail2){
            return null;
        }
        Node t1=h1;
        Node t2=h2;
        //找出较长的链表先遍历
        if(len1>len2){
            int d=len1-len2;
            while(d!=0){
                t1=t1.next;
                d--;
            }    
        }
        if(len1<len2){
            int d=len2-len1;
            while(d!=0){
                t2=t2.next;
                d--;
            }    
        }
        while(t1!=t2){
            t1=t1.next;
            t2=t2.next;
        }
        return t1;
    }

Previous:

6. Linear table: the advantages and disadvantages of sequence table and linked list speedwalkman/article/details/131512304

Guess you like

Origin blog.csdn.net/speedwalkman/article/details/131527006