1. 程式人生 > >Java資料結構(一)——連結串列

Java資料結構(一)——連結串列

Java中的資料結構又很多種,如棧,佇列,樹等,但是他們的形式歸根到底就是兩種:一個是陣列,一個是連結串列,所有的結構都是對這兩個的變形。

什麼是線性表?

陣列和連結串列都屬於是線性表,那什麼是線性表:一個線性表是n個相同特性的資料元素的有序序列。各元素之間是一對一的關係。但這並不是說一個線性表除了第一個和最後一個其他都是首尾相接,因為迴圈連結串列也是一種線性表。因為這個線性關係針對的是邏輯上的。
陣列,是物理上連續的一塊儲存空間,它便於根據索引直接取出資料和存入資料,但是搜尋和查詢的效率低下,插入和刪除的效率低下,並且陣列一旦建立以後大小就固定,如果儲存的資料太少,空間就浪費,如果儲存的資料太多,那就不能滿足,於是有了連結串列的產生。
連結串列

,不能解決資料儲存的所有問題,一般需要頻繁使用下標查詢訪問資料的地方都不使用連結串列。連結串列在記憶體中儲存的位置是隨機地,但是通過指標相互連線,形成一種線性關係,所以連結串列是一種線性表。
對於連結串列,無論是哪種形式,我們希望能實現這些基礎功能:

//建立結點類
//新增節點(只能頭部插入)
//查詢資料
//刪除資料
//列印資料
//計算長度

下面我們將簡述4種連結串列的實現方式:

1. 單向連結串列
2. 雙向連結串列
3. 有序連結串列
4. 有序連結串列優化無序陣列的排序
5. 雙端連結串列

1.單向連結串列

一個單向連結串列的結點分成兩部分:資料+下一結點的地址。
特性:只能單向遍歷,只提供連結串列頭插入
在這裡插入圖片描述

單向連結串列的具體實現:

package com.lm.list1118;

public class SingleLinklist {
	//建立結點類
	public class Node{
		Object data;//儲存資料
		Node next;//指向下一節點的地址
		public Node(Object data){//建構函式
			this.data = data;
		}
	}
	//建立頭結點
	private Node head;
	
	//新增節點(只能頭部插入)
	public void addNode(Object data) {
		Node node = new Node(data);
		if(head == null) {//當連結串列為空時
			head = node;
		}else {
			node.next = head;
			head = node;
		}
	}
	//查詢指定資料
	public Node findNode(Object data) {
		Node tmp = head;
		while(tmp != null) {
			if(data.equals(tmp.data)) {
				return tmp;
			}
			tmp = tmp.next;
		}
		return null;
	}
	//刪除資料(刪除頭結點)
	public boolean deleteHead() {
		while(head.next != null) {
			head = head.next;
			return true;
		}
		return false;
	}
	//刪除資料(刪除指定元素)
	public boolean deleteNode(Object data) {
		if(data.equals(head.data)) {
			head = head.next;
			return true;
		}
		Node tmp = head.next;
		Node curNode = head;
		while(tmp != null) {
			if(data.equals(tmp.data)) {
				curNode.next = tmp.next;
				return true;
			}else {
				curNode = curNode.next;
				tmp = tmp.next;
			}
		}
		return false;
	}
	//列印資料
	public void printNode() {
		Node node = head;
		while(node != null) {
			System.out.println("元素:"+node.data);
			node = node.next;
		}
	}
	//計算長度
	public int length() {
		int len = 0;
		Node node = head;
		while(node != null) {
			len++;
			node = node.next;
		}
		return len;
	}
	public static void main(String[] args) {
		SingleLinklist sl = new SingleLinklist();
		sl.addNode(10);
		sl.addNode(7);
		sl.addNode(4);
		sl.addNode(5);
		System.out.println("生成的連結串列為:");
		sl.printNode();
		sl.deleteHead();
		System.out.println("刪除頭結點後連結串列為:");
		sl.printNode();
		sl.deleteNode(7);
		System.out.println("刪除資料4後連結串列為:");
		sl.printNode();
		System.out.println("元素8是否存在?"+sl.findNode(8));
	}
}

上面的單向連結串列我將完整的程式碼貼出,之後的只需要稍加改動。
在這裡插入圖片描述

2.雙端連結串列

對於單向連結串列,我們暫定是對頭結點部位進行插入操作,但是這遠遠是不能滿足我們的需求,如果想要在任意位置插入資料,或者直接在尾部插入,那麼我們必須從頭部一直遍歷到尾部,這樣的演算法複雜度是O(n),如果我們適當的增加一個尾指標,用於指向尾結點,那麼複雜度會直接降到O(1)。
在這裡插入圖片描述
雙端連結串列的具體實現:

  //尾部新增節點
    	public void addTail(Object data) {
    		Node node = new Node(data);
    		if(tail == null) {
    			head = node;
    			tail = node;
    		}
    		tail.next = node;
    		tail = node;
    	}
基於雙端連結串列實現佇列:
    package com.lm.list1118;
/**
 * 雙端連結串列實現佇列
 * 先進先出(隊首出,隊尾進)
 * @author Administrator
 *
 */
public class QueueLinklist {
	private DoublePointLinklist dl;
	public QueueLinklist(){
		dl = new DoublePointLinklist();//建立雙端連結串列物件
	}
	//入佇列
	public void insert(Object data) {
		dl.addTail(data);
	}
	//出佇列
	public void delete() {
		dl.deleteHead();
	}
	//獲取佇列長度
	public int length(){
		return dl.length();
	}
	//判斷是否為空
	public boolean isEmpty() {
		if(dl.length()==0) 
			return true;
		return false;
	}
	//顯示佇列元素
	public void print() {
		dl.printNode();
	}
}

3.有序連結串列

一般說到連結串列,我們都認為他是無序的,這樣就不方便查詢最大值或最小值,所以可以對連結串列建立的時候進行排序,形成有序連結串列,這樣可以通過O(1)的時間複雜度獲取最值和刪除最值(刪除表頭即可)。而且有序連結串列在插入的時候比有序陣列速度更快,不需要進行元素的移動,而且不受固定大小的限制,所以有序連結串列有時候可以代替有序陣列。
有序連結串列的具體實現:
插入函式:
在這裡插入圖片描述
在這裡插入圖片描述

4.有序連結串列優化無序陣列的排序

我們知道氣泡排序,選擇排序和插入排序需要的時間複雜度是O(N2)如果我們將無序的陣列一個個取出然後插入有序連結串列中進行排序,然後再將排完的連結串列中的資料一個個取出重新放進陣列,這個實現的排序。大概進行N2/4次比較,優化了效率。但是不足的地方就是開闢了需要的兩倍的空間。

//插入結點,從小到大排列
     public void insert(int data) {
    	Node node = new Node(data);
    	Node pre = null;
    	Node cur = head;
    	}

我本來想著的是用兩個變數,一個表示當前指標,一個表示當前指標的下一個指標,但是邏輯上容易出現空指標的情況,情況太多,考慮不全
但是如果將一個變量表示成當前指標的前一個就簡單多了,因為如果當前指標不為空,那麼前一個指標一定不為空

while(cur != null && cur.data<data) {
			pre = cur;
			cur = cur.next;
		}
		if(pre == null) {//當連結串列為空時
			head = node;
			head.next = cur;
		}else {//當連結串列不為空,並且找到插入的位置
			pre.next = node;
			node.next = cur;
		}
	}

5. 雙向連結串列

所謂的雙向連結串列,就是相對單向連結串列而言的,可以兩個方向遍歷。

在這裡插入圖片描述
借用網上的圖來說明一下雙向連結串列的插入和刪除操作
在這裡插入圖片描述
具體的程式碼實現:

//表頭增加節點
	public void addHead(Object data) {
		Node node = new Node(data);
		if(size==0) {
			head = node;
			tail = node;
		}else {
			head.prev = node;//不要忘記對prev指標的操作
			node.next = head;
			head = node;
		}
		size++;
	}
	//表尾增加節點
	public void addTail(Object data) {
		Node node = new Node(data);
		if(size == 0) {
			head = node;
			tail = node;
		}else {
			tail.prev = node;
			node.next = tail;
			tail = node;
		}
		size++;
	}
	//刪除表頭
	public void deleteHead() {
		if(size != 0) {
			head = head.next;
			head.prev = null;
			size--;
		}
	}
	//刪除表尾
	public void deleteTail() {
		if(size!=0) {
			tail = tail.prev;
			tail.next = null;
			size--;
		}
	}