1、概述:java
本文主要總結單鏈表常見操做的實現,包括鏈表結點添加、刪除;鏈表正向遍歷和反向遍歷、鏈表排序、判斷鏈表是否有環、是否相交、獲取某一結點等。node
2、概念:算法
鏈表:數據結構
一種重要的數據結構,HashMap等集合的底層結構都是鏈表結構。鏈表以結點做爲存儲單元,這些存儲單元能夠是不連續的。每一個結點由兩部分組成:存儲的數值+前序結點和後序結點的指針。即有前序結點的指針又有後序結點的指針的鏈表稱爲雙向鏈表,只包含後續指針的鏈表爲單鏈表,本文總結的均爲單鏈表的操做。測試
單鏈表結構:ui
Java中單鏈表採用Node實體類類標識,其中data爲存儲的數據,next爲下一個節點的指針:this
package com.algorithm.link; /** * 鏈表結點的實體類 * @author bjh * */ public class Node { Node next = null;//下一個結點 int data;//結點數據 public Node(int data){ this.data = data; } }
3、鏈表常見操做:spa
package com.algorithm.link; import java.util.Hashtable; /** * 單鏈表常見算法 * @author bjh * */ public class MyLinkedList { /**鏈表的頭結點*/ Node head = null; /** * 鏈表添加結點: * 找到鏈表的末尾結點,把新添加的數據做爲末尾結點的後續結點 * @param data */ public void addNode(int data){ Node newNode = new Node(data); if(head == null){ head = newNode; return; } Node temp = head; while(temp.next != null){ temp = temp.next; } temp.next = newNode; } /** * 鏈表刪除結點: * 把要刪除結點的前結點指向要刪除結點的後結點,即直接跳過待刪除結點 * @param index * @return */ public boolean deleteNode(int index){ if(index<1 || index>length()){//待刪除結點不存在 return false; } if(index == 1){//刪除頭結點 head = head.next; return true; } Node preNode = head; Node curNode = preNode.next; int i = 1; while(curNode != null){ if(i==index){//尋找到待刪除結點 preNode.next = curNode.next;//待刪除結點的前結點指向待刪除結點的後結點 return true; } //當先結點和前結點同時向後移 preNode = preNode.next; curNode = curNode.next; i++; } return true; } /** * 求鏈表的長度 * @return */ public int length(){ int length = 0; Node curNode = head; while(curNode != null){ length++; curNode = curNode.next; } return length; } /** * 鏈表結點排序,並返回排序後的頭結點: * 選擇排序算法,即每次都選出未排序結點中最小的結點,與第一個未排序結點交換 * @return */ public Node linkSort(){ Node curNode = head; while(curNode != null){ Node nextNode = curNode.next; while(nextNode != null){ if(curNode.data > nextNode.data){ int temp = curNode.data; curNode.data = nextNode.data; nextNode.data = temp; } nextNode = nextNode.next; } curNode = curNode.next; } return head; } /** * 打印結點 */ public void printLink(){ Node curNode = head; while(curNode !=null){ System.out.print(curNode.data+" "); curNode = curNode.next; } System.out.println(); } /** * 去掉重複元素: * 須要額外的存儲空間hashtable,調用hashtable.containsKey()來判斷重複結點 */ public void distinctLink(){ Node temp = head; Node pre = null; Hashtable<Integer, Integer> hb = new Hashtable<Integer, Integer>(); while(temp != null){ if(hb.containsKey(temp.data)){//若是hashtable中已存在該結點,則跳過該結點 pre.next = temp.next; }else{//若是hashtable中不存在該結點,將結點存到hashtable中 hb.put(temp.data, 1); pre=temp; } temp = temp.next; } } /** * 返回倒數第k個結點, * 兩個指針,第一個指針向前移動k-1次,以後兩個指針共同前進, * 當前面的指針到達末尾時,後面的指針所在的位置就是倒數第k個位置 * @param k * @return */ public Node findReverNode(int k){ if(k<1 || k>length()){//第k個結點不存在 return null; } Node first = head; Node second = head; for(int i=0; i<k-1; i++){//前移k-1步 first = first.next; } while(first.next != null){ first = first.next; second = second.next; } return second; } /** * 查找正數第k個元素 */ public Node findNode(int k){ if(k<1 || k>length()){//不合法的k return null; } Node temp = head; for(int i = 0; i<k-1; i++){ temp = temp.next; } return temp; } /** * 反轉鏈表,在反轉指針錢必定要保存下個結點的指針 */ public void reserveLink(){ Node curNode = head;//頭結點 Node preNode = null;//前一個結點 while(curNode != null){ Node nextNode = curNode.next;//保留下一個結點 curNode.next = preNode;//指針反轉 preNode = curNode;//前結點後移 curNode = nextNode;//當前結點後移 } head = preNode; } /** * 反向輸出鏈表,三種方式: * 方法1、先反轉鏈表,再輸出鏈表,須要鏈表遍歷兩次 * 方法2、把鏈表中的數字放入棧中再輸出,須要維護額外的棧空間 * 方法3、依據方法2中棧的思想,經過遞歸來實現,遞歸起始就是將先執行的數據壓入棧中,再一次出棧 */ public void reservePrt(Node node){ if(node != null){ reservePrt(node.next); System.out.print(node.data+" "); } } /** * 尋找單鏈表的中間結點: * 方法1、先求出鏈表的長度,再遍歷1/2鏈表長度,尋找出鏈表的中間結點 * 方法2、: * 用兩個指針遍歷鏈表,一個快指針、一個慢指針, * 快指針每次向前移動2個結點,慢指針一次向前移動一個結點, * 當快指針移動到鏈表的末尾,慢指針所在的位置即爲中間結點所在的位置 */ public Node findMiddleNode(){ Node slowPoint = head; Node quickPoint = head; //quickPoint.next == null是鏈表結點個數爲奇數時,快指針已經走到最後了 //quickPoint.next.next == null是鏈表結點數爲偶數時,快指針已經走到倒數第二個結點了 //鏈表結點個數爲奇數時,返回的是中間結點;鏈表結點個數爲偶數時,返回的是中間兩個結點中的前一個 while(quickPoint.next != null && quickPoint.next.next != null){ slowPoint = slowPoint.next; quickPoint = quickPoint.next.next; } return slowPoint; } /** * 判斷鏈表是否有環: * 設置快指針和慢指針,慢指針每次走一步,快指針每次走兩步 * 當快指針與慢指針相等時,就說明該鏈表有環 */ public boolean isRinged(){ if(head == null){ return false; } Node slow = head; Node fast = head; while(fast.next != null && fast.next.next != null){ slow = slow.next; fast = fast.next.next; if(fast == slow){ return true; } } return false; } /** * 返回鏈表的最後一個結點 */ public Node getLastNode(){ Node temp = head; while(temp.next != null){ temp = temp.next; } return temp; } /** * 在不知道頭結點的狀況下刪除指定結點: * 刪除結點的重點在於找出其前結點,使其前結點的指針指向其後結點,即跳過待刪除結點 * 一、若是待刪除的結點是尾結點,因爲單鏈表不知道其前結點,沒有辦法刪除 * 二、若是刪除的結點不是尾結點,則將其該結點的值與下一結點交換,而後該結點的指針指向下一結點的後續結點 */ public boolean deleteSpecialNode(Node n){ if(n.next == null){ return false; }else{ //交換結點和其後續結點中的數據 int temp = n.data; n.data = n.next.data; n.next.data = temp; //刪除後續結點 n.next = n.next.next; return true; } } /** * 判斷兩個鏈表是否相交: * 兩個鏈表相交,則它們的尾結點必定相同,比較兩個鏈表的尾結點是否相同便可 */ public boolean isCross(Node head1, Node head2){ Node temp1 = head1; Node temp2 = head2; while(temp1.next != null){ temp1 = temp1.next; } while(temp2.next != null){ temp2 = temp2.next; } if(temp1 == temp2){ return true; } return false; } /** * 若是鏈表相交,求鏈表相交的起始點: * 一、首先判斷鏈表是否相交,若是兩個鏈表不相交,則求相交起點沒有意義 * 二、求出兩個鏈表長度之差:len=length1-length2 * 三、讓較長的鏈表先走len步 * 四、而後兩個鏈表同步向前移動,沒移動一次就比較它們的結點是否相等,第一個相等的結點即爲它們的第一個相交點 */ public Node findFirstCrossPoint(MyLinkedList linkedList1, MyLinkedList linkedList2){ //鏈表不相交 if(!isCross(linkedList1.head,linkedList2.head)){ return null; }else{ int length1 = linkedList1.length();//鏈表1的長度 int length2 = linkedList2.length();//鏈表2的長度 Node temp1 = linkedList1.head;//鏈表1的頭結點 Node temp2 = linkedList2.head;//鏈表2的頭結點 int len = length1 - length2;//鏈表1和鏈表2的長度差 if(len > 0){//鏈表1比鏈表2長,鏈表1先前移len步 for(int i=0; i<len; i++){ temp1 = temp1.next; } }else{//鏈表2比鏈表1長,鏈表2先前移len步 for(int i=0; i<len; i++){ temp2 = temp2.next; } } //鏈表1和鏈表2同時前移,直到找到鏈表1和鏈表2相交的結點 while(temp1 != temp2){ temp1 = temp1.next; temp2 = temp2.next; } return temp1; } } }
4、測試類:指針
package com.algorithm.link; /** * 單鏈表操做測試類 * @author bjh * */ public class Test { public static void main(String[] args){ MyLinkedList myLinkedList = new MyLinkedList(); //添加鏈表結點 myLinkedList.addNode(9); myLinkedList.addNode(8); myLinkedList.addNode(6); myLinkedList.addNode(3); myLinkedList.addNode(5); //打印鏈表 myLinkedList.printLink(); /*//測試鏈表結點個數 System.out.println("鏈表結點個數爲:" + myLinkedList.length()); //鏈表排序 Node head = myLinkedList.linkSort(); System.out.println("排序後的頭結點爲:" + head.data); myLinkedList.printLink(); //去除重複結點 myLinkedList.distinctLink(); myLinkedList.printLink(); //鏈表反轉 myLinkedList.reserveLink(); myLinkedList.printLink(); //倒序輸出/遍歷鏈表 myLinkedList.reservePrt(myLinkedList.head); //返回鏈表的中間結點 Node middleNode = myLinkedList.findMiddleNode(); System.out.println("中間結點的數值爲:"+middleNode.data); //判斷鏈表是否有環 boolean isRinged = myLinkedList.isRinged(); System.out.println("鏈表是否有環:" + isRinged); //將鏈表的最後一個結點指向頭結點,製造有環的效果 Node lastNode = myLinkedList.getLastNode(); lastNode.next = myLinkedList.head; isRinged = myLinkedList.isRinged(); System.out.println("鏈表是否有環:" + isRinged); //刪除指定結點 Node nk = myLinkedList.findReverNode(3); System.out.println(nk.data); myLinkedList.deleteSpecialNode(nk); myLinkedList.printLink(); //鏈表是否相交 //新鏈表 MyLinkedList myLinkedList1 = new MyLinkedList(); myLinkedList1.addNode(1); myLinkedList1.addNode(2); myLinkedList1.printLink(); System.out.println("鏈表一和鏈表二是否相交"+myLinkedList.isCross(myLinkedList.head, myLinkedList1.head)); //把第二個鏈表從第三個結點開始接在第二個鏈表的後面,製造相交的效果 myLinkedList1.findNode(2).next = myLinkedList.findNode(3); myLinkedList1.printLink(); System.out.println("鏈表一和鏈表二是否相交"+myLinkedList.isCross(myLinkedList.head, myLinkedList1.head)); */ //若是兩個鏈表相交求鏈表相交的結點的值 MyLinkedList myLinkedList1 = new MyLinkedList(); myLinkedList1.addNode(1); myLinkedList1.addNode(2); myLinkedList1.findNode(2).next = myLinkedList.findNode(3); myLinkedList1.printLink(); Node n = myLinkedList1.findFirstCrossPoint(myLinkedList, myLinkedList1); if(n == null){ System.out.println("鏈表不相交"); }else{ System.out.println("兩個鏈表相交,第一個交點的數值爲:" + n.data); } } }