Java實現單鏈表常見操作
阿新 • • 發佈:2020-10-22
一、概述:
本文主要總結單鏈表常見操作的實現,包括連結串列結點新增、刪除;連結串列正向遍歷和反向遍歷、連結串列排序、判斷連結串列是否有環、是否相交、獲取某一結點等。
二、概念:
一種重要的資料結構,HashMap等集合的底層結構都是連結串列結構。連結串列以結點作為儲存單元,這些儲存單元可以是不連續的。每個結點由兩部分組成:儲存的數值+前序結點和後序結點的指標。即有前序結點的指標又有後序結點的指標的連結串列稱為雙向連結串列,只包含後續指標的連結串列為單鏈表,本文總結的均為單鏈表的操作。
單鏈表結構:
Java中單鏈表採用Node實體類類標識,其中data為儲存的資料,next為下一個節點的指標:
package com.algorithm.link; /** * 連結串列結點的實體類 */ public class Node { Node next = null;//下一個結點 int data;//結點資料 public Node(int data){ this.data = data; } }
三、連結串列常見操作:
package com.algorithm.link; import java.util.Hashtable; /** * 單鏈表常見演算法 */ 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; } /** * 反向輸出連結串列,三種方式: * 方法一、先反轉連結串列,再輸出連結串列,需要連結串列遍歷兩次 * 方法二、把連結串列中的數字放入棧中再輸出,需要維護額外的棧空間 * 方法三、依據方法2中棧的思想,通過遞迴來實現,遞迴起始就是將先執行的資料壓入棧中,再一次出棧 */ public void reservePrt(Node node){ if(node != null){ reservePrt(node.next); System.out.print(node.data+" "); } } /** * 尋找單鏈表的中間結點: * 方法一、先求出連結串列的長度,再遍歷1/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; } /** * 在不知道頭結點的情況下刪除指定結點: * 刪除結點的重點在於找出其前結點,使其前結點的指標指向其後結點,即跳過待刪除結點 * 1、如果待刪除的結點是尾結點,由於單鏈表不知道其前結點,沒有辦法刪除 * 2、如果刪除的結點不是尾結點,則將其該結點的值與下一結點交換,然後該結點的指標指向下一結點的後續結點 */ 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; } /** * 如果連結串列相交,求連結串列相交的起始點: * 1、首先判斷連結串列是否相交,如果兩個連結串列不相交,則求相交起點沒有意義 * 2、求出兩個連結串列長度之差:len=length1-length2 * 3、讓較長的連結串列先走len步 * 4、然後兩個連結串列同步向前移動,沒移動一次就比較它們的結點是否相等,第一個相等的結點即為它們的第一個相交點 */ 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; } } }
四、測試類:
package com.algorithm.link; /** * 單鏈表操作測試類 */ 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); } } }