数据结构之链表
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 }
以上是关于数据结构之链表的主要内容,如果未能解决你的问题,请参考以下文章