数据结构之链表

Posted oweiziqiango

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构之链表相关的知识,希望对你有一定的参考价值。

 

1.链表

 链表作为最基本的数据结构,其存储特点如下:可以用任意一组存储单元来存储链表中的数据元素(存储单元可以是不连续的),而且除了存储每个数据元素ai 值以外,还必须存储指示其直接后继元素的信息。

  在Java语言中,可以定义如下的数据类来存储节点信息。

1 class Node{
2     Node next = null;
3     int data;
4     public Node(int data){
5         this.data = data;
6     }
7 }    

   

2.链表的操作

  链表最重要的操作就是向链表中插入元素和从链表中删除元素。

       示例给出链表的基本操作:

  1 package wzq.test.algorithm.linkedlist;
  2 
  3 public class MyLinkedList {
  4     static Node head = null;
  5     
  6     public static void main(String[] args) {
  7         MyLinkedList link = new MyLinkedList();
  8         link.addLinkedList(3);
  9         link.addLinkedList(1);
 10         link.addLinkedList(5);
 11         link.addLinkedList(3);
 12         link.addLinkedList(2);
 13         link.print();
 14         System.out.println();
 15         
 16         //设置环
 17 //        Node z =link.findKPost(1);
 18         Node r = link.findKPost(3);
 19 //        if(z.next==null)
 20 //            z.next = r;
 21         //判断两条单向链表是否相交
 22         boolean b = link.isIntersect(link.head, r);
 23         System.out.println(b);
 24         //找到第一次相交的节点
 25         Node f = link.getFirstMeetNode(link.head, r);
 26         System.out.println("相交的节点值是:"+f.data);
 27         //判断链表中是否有环
 28 //        boolean b = link.isLoop(link.head);
 29 //        System.out.println(b);
 30         
 31         //找到环入口
 32 //        Node r0 = link.isLoopNode(link.head);
 33 //        if(r0!=null)
 34 //            System.out.println("环入口值为:"+r0.data);
 35 //        
 36         //删除第index个元素
 37         /*link.deleteLinkedList(1);
 38         link.print();
 39         System.out.println();*/
 40         //找到倒数第k个元素
 41         //Node  k =link.findKPost(3);
 42         //System.out.println(k.data);
 43         //删除重复数据
 44         //link.deleteRepeat();
 45         //输出链表的长度
 46         //System.out.println("length :"+link.getLength());
 47         //从尾到头输出链表
 48         //link.postPrint(link.head);
 49         
 50         //寻找单链表中的中间节点
 51         //Node mid = link.findMidNode();
 52         //System.out.println(mid.data);
 53         //从小到大排序
 54         //link.sortLinkedList();
 55         //link.print();
 56 
 57     }
 58     //如果两个链表相交,找到相交的节点
 59     public Node getFirstMeetNode(Node h1,Node h2){
 60         if(h1==null || h2 == null)
 61             return null;
 62         
 63         int len1 = 1;
 64         Node tail1 = h1;
 65         while(tail1.next!=null){
 66             len1++;
 67             tail1=tail1.next;
 68         }
 69         
 70         
 71         int len2 = 1;
 72         Node tail2 = h2;
 73         while(tail2.next!=null){
 74             len2++;
 75             tail2=tail2.next;
 76         }
 77         
 78         Node t1 = h1;
 79         Node t2 = h2;
 80         if(len1>len2){
 81             int i = len1-len2;
 82             while(i!=0){
 83                 i--;
 84                 t1 = t1.next;
 85             }
 86         }else{
 87             int i = len2-len1;
 88             while(i!=0){
 89                 i--;
 90                 t2 = t2.next;
 91             }
 92         }
 93         while(t1!=null&&t2!=null){
 94             if(t1==t2)
 95                 break;
 96             t1 = t1.next;
 97             t2 = t2.next;
 98         }
 99         
100         return t1;
101     }
102     //判断两个链表是否相交
103     /*
104      * 思路:如果两个单向链表相交,则它们的尾节点肯定相同,如果不相同,则说明不相交
105      */
106     public boolean isIntersect(Node h1,Node h2){
107         if(h1==null || h2==null)
108             return false;
109         
110         Node tail1 = h1;
111         Node tail2 = h2;
112         
113         while(tail1.next!=null)
114             tail1 = tail1.next;
115         while(tail2.next!=null)
116             tail2 = tail2.next;
117         
118         return tail1 == tail2;
119     }
120     
121     //找到环入口
122     /*
123      * 思路:首先通过快慢指针获取到第一次相遇的节点,
124      *     然后再一个从head开始,一个从相遇的点开始,单步走,必定相遇,相遇第一次的点就是环入口点
125      */
126     public Node isLoopNode(Node head){
127         if(head==null)
128             return null;
129         Node fast = head;
130         Node slow = head;
131         Node first = null;
132         while(fast!=null && fast.next!=null){
133             fast = fast.next.next;
134             slow = slow.next;
135             if(fast == slow){
136                 first = fast;
137                 break;
138             }
139         }
140         
141         if(fast==null || fast.next==null || first == null){
142             return null;
143         }
144         Node start = head;
145         while(start != first){
146             start = start.next;
147             first = first.next;
148         }
149         
150         return start;
151     }
152     //判断链表中是否有环
153     /*
154      * 思路:使用一个快指针和慢指针,同时遍历,如果能得到快指针等于了慢指针,说明有环;
155      *                                   如果快指针等于了null,说明没有环
156      */
157     public  boolean isLoop(Node head) {
158         if(head==null)
159             return false;
160         Node fast = head;//快指针 +2
161         Node slow = head;//慢指针 +1
162         while(fast!=null&&fast.next!=null){
163             
164             fast=fast.next.next;
165             slow=slow.next;
166             
167             if(fast==slow){
168                 return true;
169             }
170         }
171         return false;
172     }
173 
174     //寻找单链表的中间节点
175     /*
176      * 思路:不用遍历两次得到length求出mid再遍历
177      *         可以使用两个指针,同时遍历,一个快 走2步,一个慢 走1步
178      *         当快的走到尾部时,慢的正好在中间
179      *         (如果链表为偶数,中间两个都算)
180      */
181     public Node findMidNode(){
182         if(head==null){
183             return null;
184         }
185         Node fast = head;//+2
186         Node slow = head;//+1
187         while(fast!=null){
188             if(fast.next==null){
189                 break;
190             }
191             fast = fast.next.next;
192             slow = slow.next;
193         }
194         return slow;
195     }
196     //从尾到头输出单链表
197     /*
198      * 思路:可以用栈,但是要开辟多余的空间
199      * 想到栈 就能想到递归,递归的本质就是一个栈结构
200      * 使用递归输出后面的节点,再输出本节点
201      */
202     public void postPrint(Node node){
203         if(node!=null){
204             postPrint(node.next);
205             System.out.print(node.data+" ");
206         }
207         
208     }
209     
210     //找到倒数第k个元素
211     /*
212      * 思路:指定两个指针,一个p1,一个p2,
213      * p1需要向前移动k-1个位置
214      * 然后p1,p2同时遍历链表,只到p1->next指向null,p2正好指向倒数第k个元素
215      */
216     public Node findKPost(int k) {
217         if(k<1)
218             return null;
219         Node p1 = head;
220         Node p2 = head;
221         for(int i=0 ; i<k-1;i++){
222             p1 = p1.next;
223         }
224         if(p1==null){
225             System.out.println("k值有问题");
226             return null;
227         }
228         while(p1.next!=null){
229             p1 = p1.next;
230             p2 = p2.next;
231         }
232         return p2;
233     }
234 
235     //删除重复的数
236     /*
237      * 思路:用两个循环,外循环正常遍历链表,内循环从当前cur节点向后遍历,遇到重复的节点就删除。
238      */
239     public void deleteRepeat() {
240         if(head == null)
241             return ;
242         Node p = head;
243         
244         while(p!=null){
245             Node q = p;
246             while(q.next!=null){
247                 if(p.data==q.next.data){
248                     q.next = q.next.next;
249                     //q = q.next;
250                 }else
251                     q = q.next;
252             }
253             p = p.next;
254         }
255     }
256     //向链表添加
257     /*
258      * 思路:从head节点开始遍历,遇到cur->next为null时,添加新节点
259      */
260     public void addLinkedList(int d){
261         Node newNode = new Node(d);
262         if(head == null){
263             head = newNode;
264             return;
265         }
266         //Node preNode = null;
267         Node curNode = head;
268         while(curNode!=null){
269             //preNode = curNode;
270             if(curNode.next==null){
271                 curNode.next = newNode;
272                 return;
273             }
274             curNode = curNode.next;
275         }
276     }
277     //删除链表指定位置的节点
278     /*
279      * 思路:先判断是否为头结点,很容易去掉头结点
280      *         标量i为2,开始从第二个节点遍历,判断i==index,如果是则删除第i节点
281      */
282     public  boolean deleteLinkedList(int index){
283         if(index==1)
284         {
285             head = head.next;
286             return true;
287         }
288         int i =2;
289         Node preNode = head;
290         Node curNode = head.next;
291         while(curNode!=null){
292             //preNode = curNode;
293             if(index==i){
294                 preNode.next = curNode.next;
295                 return true;
296             }
297             i++;
298             preNode = curNode;
299             curNode = curNode.next;
300         }
301         return false;
302     }
303     //打印链表
304     public  void print(){
305         if(head==null)
306             return;
307         Node curNode = head;
308         while(curNode!=null){
309             System.out.print(curNode.data+" ");
310             curNode = curNode.next;
311         }
312     }
313     //链表排序  从小到大
314     /*
315      * 用两个循环,外循环正常遍历链表,内循环从当前cur节点开始与后面的节点值进行比较,
316      * 如果小于cur节点的值,便进行值得交换
317      * 然后,再继续遍历下一个节点进行比较
318      */
319     public  void sortLinkedList(){
320         if(head==null){
321             return;
322         }
323         Node curNode = head;
324         Node preNode;
325         while(curNode!=null){
326             preNode = curNode;
327             while(curNode.next!=null){
328                 int temp ;
329                 if(preNode.data > curNode.next.data){
330                     temp = preNode.data;
331                     preNode.data = curNode.next.data;
332                     curNode.next.data = temp;
333                 }
334                 curNode = curNode.next;
335             }
336             curNode = preNode.next;
337             
338         }
339         
340     }
341     //输出链表长度
342     public  int getLength(){
343         int length=0;
344         if(head == null)
345             return length;
346         Node curNode = head;
347         while(curNode!=null){
348             length++;
349             curNode = curNode.next;
350         }
351         return length;
352     }
353     
354 }
355 class Node{
356     int data;
357     Node next = null;
358     Node(int data){
359         this.data = data;
360     }
361 }

1 主要有链表的基本操作,添加和删除结点

 1 //向链表添加
 2     /*
 3      * 思路:从head节点开始遍历,遇到cur->next为null时,添加新节点
 4      */
 5     public void addLinkedList(int d){
 6         Node newNode = new Node(d);
 7         if(head == null){
 8             head = newNode;
 9             return;
10         }
11         //Node preNode = null;
12         Node curNode = head;
13         while(curNode!=null){
14             //preNode = curNode;
15             if(curNode.next==null){
16                 curNode.next = newNode;
17                 return;
18             }
19             curNode = curNode.next;
20         }
21     }
 1 //删除链表指定位置的节点
 2     /*
 3      * 思路:先判断是否为头结点,很容易去掉头结点
 4      *         标量i为2,开始从第二个节点遍历,判断i==index,如果是则删除第i节点
 5      */
 6     public  boolean deleteLinkedList(int index){
 7         if(index==1)
 8         {
 9             head = head.next;
10             return true;
11         }
12         int i =2;
13         Node preNode = head;
14         Node curNode = head.next;
15         while(curNode!=null){
16             //preNode = curNode;
17             if(index==i){
18                 preNode.next = curNode.next;
19                 return true;
20             }
21             i++;
22             preNode = curNode;
23             curNode = curNode.next;
24         }
25         return false;
26     }

 

2 从链表中删除重复的数据

 1 //删除重复的数
 2     /*
 3      * 思路:用两个循环,外循环正常遍历链表,内循环从当前cur节点向后遍历,遇到重复的节点就删除。
 4      */
 5     public void deleteRepeat() {
 6         if(head == null)
 7             return ;
 8         Node p = head;
 9         
10         while(p!=null){//外循环
11             Node q = p;
12             while(q.next!=null){//内循环
13                 if(p.data==q.next.data){
14                     q.next = q.next.next;
15                     //q = q.next;
16                 }else
17                     q = q.next;
18             }
19             p = p.next;
20         }
21     }

 

3 如果找出单链表中的倒数第k个元素

 1 //找到倒数第k个元素
 2     /*
 3      * 思路:指定两个指针,一个p1,一个p2,
 4      * p1需要向前移动k-1个位置
 5      * 然后p1,p2同时遍历链表,只到p1->next指向null,p2正好指向倒数第k个元素
 6      */
 7     public Node findKPost(int k) {
 8         if(k<1)
 9             return null;
10         Node p1 = head;
11         Node p2 = head;
12         for(int i=0 ; i<k-1;i++){
13             p1 = p1.next;
14         }
15         if(p1==null){
16             System.out.println("k值有问题");
17             return null;
18         }
19         while(p1.next!=null){
20             p1 = p1.next;
21             p2 = p2.next;
22         }
23         return p2;
24     }

 

4 如何实现链表反转

 

 1 //反转链表
 2     public void ReverseLink(Node head){
 3         //Node isRevereLink = null;
 4         Node pNode = head;
 5         Node pPrev = null;//倒序链表的头部
 6         while(pNode!=null){
 7             Node pNext = pNode.next;
 8         
 9             pNode.next = pPrev;//从正序链表上截取一个结点 ,前插法放入倒序中
10             pPrev = pNode;// 指向倒序链表的头部
11             pNode = pNext;//正序链表中剩余部分
12         }
13         this.head = pPrev;
14     }

 

5 如何从尾到头输出链表

 

 1 //从尾到头输出单链表
 2     /*
 3      * 思路:可以用栈,但是要开辟多余的空间
 4      * 想到栈 就能想到递归,递归的本质就是一个栈结构
 5      * 使用递归输出后面的节点,再输出本节点
 6      */
 7     public void postPrint(Node node){
 8         if(node!=null){
 9             postPrint(node.next);
10             System.out.print(node.data+" ");
11         }
12         
13     }

 

6 如何寻找单链表的中间结点

 

//寻找单链表的中间节点
    /*
     * 思路:不用遍历两次得到length求出mid再遍历
     *         可以使用两个指针,同时遍历,一个快 走2步,一个慢 走1步
     *         当快的走到尾部时,慢的正好在中间
     *         (如果链表为偶数,中间两个都算)
     */
    public Node findMidNode(){
        if(head==null){
            return null;
        }
        Node fast = head;//+2
        Node slow = head;//+1
        while(fast!=null){
            if(fast.next==null){
                break;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }

 

7 如何检验一个链表是否有环

 1 //判断链表中是否有环
 2     /*
 3      * 思路:使用一个快指针和慢指针,同时遍历,如果能得到快指针等于了慢指针,说明有环;
 4      *                                   如果快指针等于了null,说明没有环
 5      */
 6     public  boolean isLoop(Node head) {
 7         if(head==null)
 8             return false;
 9         Node fast = head;//快指针 +2
10         Node slow = head;//慢指针 +1
11         while(fast!=null&&fast.next!=null){
12             
13             fast=fast.next.next;
14             slow=slow.next;
15             
16             if(fast==slow){
17                 return true;
18             }
19         }
20         return false;
21     }

 

找到环入口

可以参考  https://blog.csdn.net/u011373710/article/details/54024366

 1     //找到环入口
 2     /*
 3      * 思路:首先通过快慢指针获取到第一次相遇的节点,
 4      *     然后再一个从head开始,一个从相遇的点开始,单步走,必定相遇,相遇第一次的点就是环入口点
 5      *  
 6      */
 7     public Node isLoopNode(Node head){
 8         if(head==null)
 9             return null;
10         Node fast = head;
11         Node slow = head;
12         Node first = null;//第一次相遇的结点
13         //通过快慢指针获取到第一次相遇的节点
14         while(fast!=null && fast.next!=null){
15             fast = fast.next.next;
16             slow = slow.next;
17             if(fast == slow){
18                 first = fast;
19                 break;
20             }
21         }
22         
23         if(fast==null || fast.next==null || first == null){
24             return null;
25         }
26         //一个从head开始,一个从相遇的点开始,单步走,必定相遇
27         Node start = head;
28         while(start != first){
29             start = start.next;
30             first = first.next;
31         }
32         
33         return start;
34     }

 

8 如何在不知道头指针的情况下删除指定结点

 分两种情况1.如果是链表尾部结点,无法删除,因为找不到前继结点

     2.将后继结点的值 复制到 要删除的结点值上,然后将后继结点 再链表上删除

 1 public boolean deleteLinkNode(Node n) {
 2         // TODO Auto-generated method stub
 3         if(n==null || n.next == null){
 4             return false;
 5         }
 6         Node nextNode = n.next;
 7         n.data = nextNode.data;
 8         n.next = nextNode.next;
 9         return true;
10     }

 

9 如何判断两个链表是否相交

 

 1 //判断两个链表是否相交
 2     /*
 3      * 思路:如果两个单向链表相交,则它们的尾节点肯定相同,如果不相同,则说明不相交
 4      */
 5     public boolean isIntersect(Node h1,Node h2){
 6         if(h1==null || h2==null)
 7             return false;
 8         
 9         Node tail1 = h1;
10         Node tail2 = h2;
11         
12         while(tail1.next!=null)
13             tail1 = tail1.next;
14         while(tail2.next!=null)
15             tail2 = tail2.next;
16         
17         return tail1 == tail2;
18     }

如果两个链表相交,找到相交的节点

 1 //如果两个链表相交,找到相交的节点
 2     public Node getFirstMeetNode(Node h1,Node h2){
 3         if(h1==null || h2 == null)
 4             return null;
 5         //遍历得到链表1的长度
 6         int len1 = 1;
 7         Node tail1 = h1;
 8         while(tail1.next!=null){
 9             len1++;
10             tail1=tail1.next;
11         }
12         
13         //遍历得到链表2的长度
14         int len2 = 1;
15         Node tail2 = h2;
16         while(tail2.next!=null){
17             len2++;
18             tail2=tail2.next;
19         }
20         //判断哪个链表更长,长几个结点,然后长的链表先遍历几个结点
21         Node t1 = h1;
22         Node t2 = h2;
23         if(len1>len2){
24             int i = len1-len2;
25             while(i!=0){
26                 i--;
27                 t1 = t1.next;
28             }
29         }else{
30             int i = len2-len1;
31             while(i!=0){
32                 i--;
33                 t2 = t2.next;
34             }
35         }
36         //同步遍历,直到结点相同为止
37         while(t1!=null&&t2!=null){
38             if(t1==t2)
39                 break;
40             t1 = t1.next;
41             t2 = t2.next;
42         }
43         
44         return t1;
45     }

 

以上是关于数据结构之链表的主要内容,如果未能解决你的问题,请参考以下文章

Java数据结构线性表之链表

Java数据结构线性表之链表

Java数据结构线性表之链表

JavaScript数据结构之链表

JavaScript数据结构之链表

数据结构之链表