1. 程式人生 > >資料結構(2)-- 單向連結串列

資料結構(2)-- 單向連結串列

目錄

0.目錄
1.線性表 – 陣列
2.線性表 – 單向連結串列

連結串列

  連結串列是一種物理儲存單元上非連續、非順序的資料結構,資料元素的邏輯順序是通過連結串列中的指標連結次序實現的。連結串列由一系列節點組成,這些節點不必在記憶體中相連。每個節點由資料部分Data和鏈部分Next組成,Next指向下一個節點,這樣當新增或者刪除時,只需要修改相關節點的Next指向,效率很高)。

// 連結串列節點程式碼(使用內部類)
private class Node<E> {
	private E element;
	private Node<E> next;
	public Node(E e, Node<E> next) {
		element = e;
		this.next = next;
	}
	public Node(E e) {
		this(e, null);
	}
	public Node() {
		this(null);
	}

// 使用虛擬頭結點保持邏輯連貫性、使用尾節點改進效能
private Node<E> dummyHead;
private Node<E> tail;
private int size = 0;

// 兩種構造方法
public Linked(E e) {
	dummyHead = new Node<>();
	dummyHead.next = new Node<>(e);
	tail = dummyHead.next;
	size++;
}

public Linked() {
	dummyHead = new Node<>();
	tail = dummyHead;
}

// 在指定索引位置插入元素
public void add(int index, E e) {
	if (index < 0 || index > size) {
		throw new IllegalArgumentException("Wrong index: " + index);
	}
	Node<E> pre = dummyHead;
	for (int i = 0; i < index; i++) {
		pre = pre.next;
	}
	pre.next = new Node<>(e, pre.next);
	if (index == size) {
		tail = pre.next;
	}
	size++;
}

// 順序遍歷連結串列(重寫toString方法)
@Override
public String toString() {
	StringBuilder sb = new StringBuilder("Linked head[");
	Node<E> cur = dummyHead;
	while (cur.next != null) {
		cur = cur.next;
		sb.append(cur.element).append("->");
	}
	sb.append("NULL").append("]tail");
	return sb.toString();
}

// 逆序遍歷連結串列(遞迴思想)
public void printDesc() {
	System.out.print("Linked tail[Null");
	printDesc(dummyHead.next);
	System.out.println("]head");
}
private void printDesc(Node<E> head) {
	if (head != null) {
		printDesc(head.next);
		System.out.print("<-" + head.element);
	}
}

// 單鏈表反轉
public void reverse() {
	// 非遞迴(效率高)
	dummyHead.next = reverse(dummyHead.next);
	// 遞迴
	// dummyHead.next = reverseRecursion(dummyHead.next);
}
// 非遞迴實現
private Node<E> reverse(Node<E> head) {
	Node<E> pre = null;
	Node<E> cur = head;
	Node<E> next = null;
	while (cur != null) {
		next = cur.next;
		cur.next = pre;
		pre = cur;
		cur = next;
	}
	return pre;
}
// 遞迴實現
private Node<E> reverseRecursion(Node<E> node) {
	if (node == null) {
		return null;
	}
	if (node.next == null) {
		return node;
	}
	Node<E> next = node.next;
	node.next = null;
	Node<E> result = reverseRecursion(next);
	next.next = node;
	return result;
}

  上述程式碼簡單實現了基於單向連結串列實現的線性表的一些細節。單向連結串列實現的線性表,不需要擴容操作;引入了虛擬頭結點,使各方法在實現邏輯上會更加統一;引入了尾節點tail,使得對單鏈表的部分尾部操作效能顯著提升(O(n) -> O(1)),可顯著提升基於單向連結串列實現的其他資料結構的效能(如佇列、棧等,後續會介紹)。
  除了上述單向連結串列,還有其他的實現方式,常見的有迴圈單向連結串列、雙向連結串列、迴圈雙向連結串列。其中,LinkedList集合類的實現就是雙向連結串列。

複雜度分析

  1. 增:add – O(n)、addFirst 和 addLast(tail實現) – O(1)
  2. 刪:remove – O(n)、removeFirst – O(1)
  3. 改:set – O(n)、setFirst 和 setLast(tail實現) – O(1)
  4. 查:get – O(n)、getFirst 和 getLast(tail實現) – O(1)

  可以看出,基於單鏈表實現的線性表結構,特點是:中間操作慢、首尾操作快

原始碼

public class Linked<E> {

	private class Node<E> {
		private E element;
		private Node<E> next;

		public Node(E e, Node<E> next) {
			element = e;
			this.next = next;
		}

		public Node(E e) {
			this(e, null);
		}

		public Node() {
			this(null);
		}

		@Override
		public String toString() {
			return element == null ? "null" : element.toString();
		}
	}

	private Node<E> dummyHead;
	private Node<E> tail;
	private int size = 0;

	public Linked(Node<E> node) {
		dummyHead = new Node<>();
		dummyHead.next = node;
		if (node == null) {
			tail = dummyHead;
		} else {
			tail = dummyHead.next;
			size++;
		}
	}

	public Linked(E e) {
		dummyHead = new Node<>();
		dummyHead.next = new Node<>(e);
		tail = dummyHead.next;
		size++;
	}

	public Linked() {
		dummyHead = new Node<>();
		tail = dummyHead;
	}

	/**
	 * 在指定索引位置插入元素 O(n) 
	 * 特例:addFirst 和 addLast(tail實現) 為O(1)
	 * 
	 * @param index
	 * @param e
	 */
	public void add(int index, E e) {
		if (index < 0 || index > size) {
			throw new IllegalArgumentException("Wrong index: " + index);
		}
		Node<E> pre = dummyHead;
		for (int i = 0; i < index; i++) {
			pre = pre.next;
		}
		pre.next = new Node<>(e, pre.next);
		if (index == size) {
			tail = pre.next;
		}
		size++;
	}

	public void addFirst(E e) {
		add(0, e);
	}

	public void addLast(E e) {
		// 複用add方法,則時間複雜度為O(n)
		// add(size, e);

		// 使用tail節點實現,則時間複雜度為O(1)
		tail.next = new Node<>(e);
		tail = tail.next;
		size++;
	}

	/**
	 * 刪除指定位置的元素 O(n) 
	 * 特例:removeFirst為O(1)
	 * 
	 * @param index
	 */
	public void remove(int index) {
		if (index < 0 || index >= size) {
			throw new IllegalArgumentException("Wrong index: " + index);
		}
		Node<E> pre = dummyHead;
		for (int i = 0; i < index; i++) {
			pre = pre.next;
		}
		if (index == size - 1) {
			tail = pre;
		}
		// 使del節點能被JVM正常GC
		Node<E> del = pre.next;
		pre.next = del.next;
		del.next = null;
		size--;
	}

	public void removeFirst() {
		remove(0);
	}

	public void removeLast() {
		remove(size - 1);
	}

	/**
	 * 更改指定索引位置的元素 O(n) 
	 * 特例:setFirst 和 setLast(tail實現) 為O(1)
	 * 
	 * @param index
	 * @param e
	 */
	public void set(int index, E e) {
		if (index < 0 || index >= size) {
			throw new IllegalArgumentException("Wrong index: " + index);
		}
		Node<E> cur = dummyHead.next;
		for (int i = 0; i < index; i++) {
			cur = cur.next;
		}
		cur.element = e;
	}

	public void setFirst(E e) {
		set(0, e);
	}

	public void setLast(E e) {
		// 複用add方法,則時間複雜度為O(n)
		// set(size - 1, e);

		// 使用tail節點實現,則時間複雜度為O(1)
		tail.element = e;
	}

	/**
	 * 查詢指定索引位置的元素 O(n) 
	 * 特例:getFirst 和 getLast(tail實現) 為O(1)
	 * 
	 * @param index
	 * @return
	 */
	public E get(int index) {
		if (index < 0 || index >= size) {
			throw new IllegalArgumentException("Wrong index: " + index);
		}
		Node<E> cur = dummyHead.next;
		for (int i = 0; i < index; i++) {
			cur = cur.next;
		}
		return cur.element;
	}

	public E getFirst() {
		return get(0);
	}

	public E getLast() {
		return tail.element;
	}

	public int getSize() {
		return size;
	}

	public boolean isEmpty() {
		return size == 0;
	}

	/**
	 * 查詢連結串列是否包含某個元素,如果包含返回索引,如果不包含返回-1 O(n)
	 * 
	 * @param e
	 * @return
	 */
	public int contains(E e) {
		if (e == null) {
			return -1;
		}
		Node<E> cur = dummyHead.next;
		for (int i = 0; i < size; i++) {
			if (e.equals(cur.element)) {
				return i;
			}
			cur = cur.next;
		}
		return -1;
	}

	/**
	 * 反向列印連結串列,遞迴思想
	 */
	public void printDesc() {
		System.out.print("Linked tail[Null");
		printDesc(dummyHead.next);
		System.out.println("]head");
	}

	private void printDesc(Node<E> head) {
		if (head != null) {
			printDesc(head.next);
			System.out.print("<-" + head.element);
		}
	}

	/**
	 * 反轉連結串列
	 */
	public void reverse() {
		// 非遞迴(效率高)
		dummyHead.next = reverse(dummyHead.next);

		// 遞迴
		// dummyHead.next = reverseRecursion(dummyHead.next);
	}

	private Node<E> reverse(Node<E> head) {
		Node<E> pre = null;
		Node<E> cur = head;
		Node<E> next = null;
		while (cur != null) {
			next = cur.next;
			cur.next = pre;
			pre = cur;
			cur = next;
		}
		return pre;
	}

	// 遞迴實現
	private Node<E> reverseRecursion(Node<E> node) {
		if (node == null) {
			return null;
		}
		if (node.next == null) {
			return node;
		}
		Node<E> next = node.next;
		node.next = null;
		Node<E> result = reverseRecursion(next);
		next.next = node;
		return result;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder("Linked head[");
		Node<E> cur = dummyHead;
		while (cur.next != null) {
			cur = cur.next;
			sb.append(cur.element).append("->");
		}
		sb.append("NULL").append("]tail");
		// sb.append(size);
		return sb.toString();
	}
}