1. 程式人生 > >支援泛型的連結串列(實現連結串列反轉方法)

支援泛型的連結串列(實現連結串列反轉方法)

連結串列的概念

連結串列是由一系列非連續節點組成的線性表,連結串列的組成單元是節點,每個節點都有資料域和引用域,節點之間就是通過引用域聯絡起來,連結串列的第一個節點是頭結點,最後一個節點是尾節點,我們可以通過頭結點逐個找到它之後的節點,一個節點總是指向下一個節點,故連結串列是線性的。記憶體空間包括堆和棧,連結串列在它們之中的儲存方式是:頭結點的引用放在棧中,連結串列的其餘部分都放在堆中(我們可以這樣理解堆和棧,棧是一種線性的儲存形式,而堆中資料的儲存是沒有規律的,就像是在一個空間中擺法物品,它們不一定是整齊擺放的,堆中資料之間是通過引用來建立聯絡的)。連結串列是通過引用聯絡起來的,那麼它就會有很多種實現形式,比如單鏈表、雙向連結串列、迴圈連結串列,它們的含義通過名稱不難理解。

泛型

連結串列中可以儲存任意型別的資料,如果我們在定義連結串列的時候以某種具體的類來定義,那麼就只能以這種資料型別來輸入,這是很不靈活的。這裡我們可以用到泛型,什麼是泛型?泛型就是當你在寫這段程式碼的時候不確定輸入的引數是什麼型別,就可以使用泛型,一般我們用 < T > 的形式來表示,表明我們這個類支援泛型,或者說我們這個類中某個變數的型別我們不確定,需要根據輸入引數的型別來決定。當一個方法有傳入引數的時候就可以定義泛型。在泛型表示的連結串列中我們就是用Node< T >表示節點類。需要注意的是:泛型表示java的基本資料型別,它只是一個符號,泛型的存在使得程式有很大的靈活性。

單鏈表的實現思路

我們先寫一個節點類,節點的包括資料域和引用域,我們就定義這兩個屬性,其他的我們可以交給連結串列的定義中去完成。
我們再寫一個連結串列類,定義它的頭結點,尾節點,連結串列的長度這些屬性。關於頭結點,有兩種實現思路,一種是頭結點儲存資料,另一種是頭結點僅僅儲存下一個節點的地址而不儲存資料。我們這裡討論不儲存資料的頭結點實現的連結串列。size表示連結串列的長度,這裡頭結點不參與計算,我們在定義連結串列的時候自動生成了頭結點且此時size為0,index是我們在索引節點時用到的角標,同樣不包括頭結點,第一個有資料的節點index=0,下一個index=1,以此類推。

新增,刪除,查詢,反轉

1、新增:在連結串列末尾加上一個節點,讓之前的最後一個節點指向這個新節點,然後把新節點的應用域賦為null,再把它作為尾節點。這裡一個細節我覺得值得一提,java中的物件是儲存在堆中的,程式對它們的呼叫其實是通過對於這個物件的儲存位置的引用來實現的,在程式中我們列印一個物件,會輸出一段字元,它表示的就是一串地址,我們在連結串列類中定義了Node 類的tail節點物件,當我插入一個新的節點時,我們需要tail=newNode,這是為什麼呢?因為我們需要把新的節點的地址賦給tail,這樣我們就可以通過tail物件來訪問最後一個節點了,歸根結底,物件是通過地址的引用來找到的。
2、刪除:刪除的時候我們要分刪除第一個節點,中間的節點,最後一個節點這三種情況來討論。對於第一個節點,我們只要把頭結點指向當前節點的下一個節點再把第一個節點的指標域賦為空即可,對於之間的節點,我們需要把當前節點的前一個節點指向當前節點的下一個節點,再把當前節點的指標域賦為空,這步操作的作用是可以斷開這個節點和原連結串列的連線,使得這個廢棄的節點能夠被GC清除。最後一個節點就直接把倒數第二個節點的引用域賦為空並把它變成尾節點即可。
3、查詢:思路就是通過頭結點去遞迴尋找子節點,控制迴圈的次數就可以獲得需要的物件引用。
4、反轉:反轉的思路是,從最後一個節點開始操作,把倒數第三個節點對倒數第二個節點的引用賦給最後一個,就實現了最後一個節點指向它前一個節點了,同時我們需要斷開倒數第二個節點對下一個節點的引用。從後往前,直到實現引用域的反轉,原先的第一個節點就是尾節點了,頭結點需要重新定義,在第一次操作的時候需要使得它獲得到最後一個節點的引用。在迴圈結束後,我們在把這個節點作為新的頭結點。這種思路是不改變資料域,只調整引用域。當然也可以重新定義一個新的連結串列,實現反向賦值,也可以實現反轉,不過這樣就不是原連結串列了。

程式碼實現

1、節點類:

public class Node<T>{    //定義一個節點類,物件用泛型來表示,泛型類用T表示	
	public T data;   //資料域
	public Node<T> next; //引用域				
}

2、連結串列類:

public final class Linked<T> {  //泛型類,此連結串列支援泛型輸入
    //此連結串列頭結點不存資料,資料放在下一個開始的節點中,頭結點不參與索引,我們這裡的第一個節點不包括頭結點,是指有資料的節點。	
	public int size=0; //節點數量
	public Node<T> head;//定義頭結點
	public Node<T> tail;//定義尾節點
	/**
	 * 連結串列的構造方法
	 */
	public Linked (){
		Node<T> firstNode = new Node<T>(); //建立連結串列的時候先建立頭結點
		firstNode.data =null;
		firstNode.next =null;
		head=firstNode; //把此節點作為頭結點
		tail=firstNode; //把此節點作為尾節點	
	}
	/**
	 * 新增元素
	 * @param data
	 */
	public void add(T data)
	{
		Node<T> node = new Node<T>();
		node.data=data;
		node.next=null; 
		if(size>0)
		{
			Node<T> preNode = getPreNode(size);//獲取前一個節點	
			preNode.next= node; //使前一個節點指向當前節點
			tail=node; //把當前節點作為尾節點
			size++; //長度加一
		}
		else if(size==0)
		{
			head.next= node; //使頭結點指向當前節點
			tail=node; //把當前節點作為尾節點
			size++; //長度加一	
		}
	}	
	/**
	 * 獲取指定索引的前一個節點
	 */
	public Node<T> getPreNode(int index){  
		Node<T> preNode = new Node<T>();
		  preNode = head.next; 
		for(int i=0; i<index-1;i++)   //從頭結點開始迴圈查詢節點
		{
			preNode=preNode.next ;
		}		
		return preNode;		//返回此節點		
	}	
	/**
	 * 刪除指定索引處的節點
	 * 返回被刪除節點的資料
	 * @param index
	 */
	public T deleteNode(int index)
	{
		if(index<0&&index>size)
		{
			return null;
		}
		else if (index ==0) //刪除第一個節點(頭結點)後的那個節點
		{
			T data = head.next.data;
		        head.next=head.next.next;//使頭結點指向刪除節點的下一個節點
		        head.next.next=null;//指標賦空,可以被GC清除刪除的元素
			size--;
			return data;
		}
		else if(index==size-1)//刪除最後一個節點
		{
	                Node<T> preNode = getPreNode(index);//獲取前一個節點
			preNode.next = null;//使前一個節點指向null
			T data = preNode.next.data; //儲存資料
			tail = preNode; //前一節點設為尾節點
			size--; 
			return data;
		}
		else
		{
		        Node<T> preNode = getPreNode(index);//獲取前一個節點
		        Node<T> theNode = preNode.next;//當前節點
		        T data = theNode.data; //儲存資料
			preNode.next = theNode.next;//使前一個節點指向當前節點的下一個節點	
			theNode.next=null;//當前節點指標賦為空
			size--;
			return data;
		}		
	}
    /**
     * 查詢指定索引的節點
     */
	public T search(int index)
	{
		if(index<0&&index>size-1) //在範圍外,返回null
		{
			 return null;
		}
		else if(index==0)
		{
			return head.next.data; //返回第一個節點的資料(頭結點的下一個節點)			
		}
		else
		{
			Node<T> preNode = getPreNode(index); //獲取前一個節點
			T data =preNode.next.data; //返回索引節點的資料
			return data;
		}						
	}	
	/**
	 * 連結串列反轉
	 */
	public void reversal(){			
		Node<T> newHeadNode = new Node<T>();//定義一個節點,之後作為新的頭結點
		newHeadNode.data=null; //資料域為空
		for(int i = size ; i>2 ; i--  )			
		{    
			if(i==size) //第一次操作時使新節點指向最後一個節點
			{
				newHeadNode.next=getPreNode(size-1).next;				
				getPreNode(i).next=getPreNode(i-2).next; //迴圈使得後一個節點指向前一個節點
				getPreNode(i-1).next=null; //斷開向前一個節點至後一個節點的引用
			}
			else
			{
				getPreNode(i).next=getPreNode(i-2).next; //迴圈使得後一個節點指向前一個節點
				getPreNode(i-1).next=null; //斷開向前一個節點至後一個節點的引用				
			}
		}
		getPreNode(2).next=head.next; //使得第二個節點指向第一個節點(頭結點不算入節點數)
		getPreNode(1).next=null; //斷開引用	
		tail=head.next;//原先的第一個節點變成尾節點	
		head.next=null;//斷開原頭結點
		head=newHeadNode;//把新的節點變為頭結點
	}		
}

測試

一、測試新增資料:

	public static void main(String[] args)
	{
		Linked<String> linked = new Linked<>();
		linked.add("3");//index=0
		linked.add("ss");//index=1
                linked.add("dfdf");
                linked.add("ddd");
                linked.add("345");
                linked.add("222");               
		for(int i=0; i<linked.size;i++)
		{
			System.out.println(linked.search(i));
		}				
	}		
}

輸出:
3
ss
dfdf
ddd
345
222
二、測試刪除資料:

	public static void main(String[] args)
	{
		Linked<String> linked = new Linked<>();
		linked.add("3");//index=0
		linked.add("ss");//index=1
	        linked.add("dfdf");
	        linked.add("ddd");
	        linked.add("345");
	        linked.add("222");        
	        linked.deleteNode(3);   //刪除ddd
		for(int i=0; i<linked.size;i++)
		{
			System.out.println(linked.search(i));
		}		
	}		
}

輸出:
3
ss
dfdf
345
222
三、測試反轉連結串列:

public class Test {
	public static void main(String[] args)
	{
		Linked<String> linked = new Linked<>();
		linked.add("3");//index=0
		linked.add("ss");//index=1
	        linked.add("dfdf");
	        linked.add("ddd");
	        linked.add("345");
	        linked.add("222");        
		for(int i=0; i<linked.size;i++)
		{
			System.out.println(linked.search(i));
		}		
		System.out.println("進行反轉:");		
		linked.reversal();//連結串列反轉的方法		
		for(int i=0; i<linked.size;i++)
		{
			System.out.println(linked.search(i));
		}				
	}		
}

輸出:
3
ss
dfdf
ddd
345
222
進行反轉:
222
345
ddd
dfdf
ss
3