資料結構分析及其實現(Stack、Queue、Tree、LinkedList)

Guo_1_9發表於2019-03-01

常見資料結構分析及實現

說明

  • 本文中的程式碼是參考《Java程式設計思想》、某培訓機構。
  • 文中的程式碼放Github了,有興趣的可以看看,點個star鼓勵下我。
  • 程式碼在Sublime中敲的,坑爹的GBK,註釋了很多中文,一轉碼不能用了!!!
  • 重點在思想,而不是實現 。再次推薦《Java程式設計思想》。

1、資料結構

程式設計的本質就是對資料(資訊以資料的形式而存在)的處理,實際程式設計中不得不處理大量資料,因此實際動手程式設計之前必須先分析處理這些資料,處理資料之間存在的關係。

現實的資料元素之間有個錯綜複雜的邏輯關係,需要採用合適的物理結構來儲存這些資料,並以此為基礎對這些資料進行相應的操作。同時還要分析這些資料結構在時間和空間上的開銷。這種專門研究應用程式中的資料之間的邏輯關係,儲存方式及其操作的學問就是資料結構。

資料元素之間存在的關聯關係被稱為資料的邏輯結構,歸納起來,大致有如下四種基本的邏輯結構:

  • 集合:資料元素之間只有"同屬於一個集合"的關係
  • 線性關係:資料元素之間存在一個對一個的關係
  • 樹形結構:資料元素之間存在一個對多個的關係
  • 圖狀結構或網狀結構:資料元素之間存在多個對多個的關係。

腦補圖:

資料結構分析及其實現(Stack、Queue、Tree、LinkedList)

圖片>程式碼>文字,個人理解,能用圖片說明問題的就不要用程式碼,同理,儘量用程式碼+文字解釋問題的本質。

同一種的邏輯結構,在底層通常有兩種物理儲存結構:

  • 順序儲存結構,如一維陣列
  • 非順序儲存結構,如鏈式儲存結構(連結串列)、雜湊

順序結構適合讀操作(為啥呢?因為有索引啊),連結串列儲存適合寫操作(為啥呢?斷開,加上節點就完成,不需要底層複製啊)

演算法的設計取決於邏輯結構:演算法的實現依賴於儲存結構。物件的設計取決於類結構,(...)

什麼是資料結果呢?資料結構歸納起來所要研究的問題就三方面:

  • 資料元素之間的客觀聯絡(邏輯結構)
  • 資料在計算機內部的儲存方式(儲存結構)
  • 針對資料實施的有效的操作和處理(演算法)

物件之間的關係(對現實的抽象,繼承?組合?),儲存在記憶體中哪裡,堆上啊,怎麼存?存在陣列裡?hash表裡?怎麼處理的啊?增刪改查啊,排序那,加密解密啊,


Stack

對於普通的線性表而言,它的作用是一個容器,用於裝具有相似結果的資料。

  • 分為順序儲存機構和鏈式儲存結構
  • 可以進行插入、刪除和排序的操作
  • 如果線性表之允許線上性表的某端新增、刪除元素,這時就演變為:棧和佇列。(先進後出(彈夾),先進先出(火車站排隊))

以下圖片來自維基百科(百X百科就別看了)

資料結構分析及其實現(Stack、Queue、Tree、LinkedList)

原諒沒放恐怖的,來自Google(百X就別用了)

資料結構分析及其實現(Stack、Queue、Tree、LinkedList)

棧(Stack),是一種特殊的線性表,只能在固定的一端(線性表的尾端)進行插入、刪除操作。

  • 允許進行插入、刪除操作的一端為棧頂(top),另一端,你猜?(bottom)
    • 進棧:將一個元素插入棧的過程,棧的長度+1,(壓入子彈)
    • 出棧:刪除一個元素的過程,棧的長度-1.(彈出發射...)
  • 先進後出,或者說後進先出。
  • 常用操作:初始化,(隨著棧幀的移除,方法在執行。可能出現stackoverflow.com/),++i,i++,
  • 在Java中繼承關係,Stack繼承自Vector,List,(abstractList?)

需求: 請編寫程式碼實現Stack類,該類能夠實現後進先出的堆疊功能,要求實現的方法包括:

  • Stack(int) 例項化指定深度的棧
  • boolean push(E item) 像棧頂壓入物件,成功返回true,棧已滿返回false
  • E pop() 從棧頂移除物件並返回,為空則返回null
  • E peek() 檢視並返回棧頂的物件,為空返回null
  • int size() 返回棧中當前元素數量
  • int depth() 返回當前堆疊深度

萬惡的字元編碼,無比的鬱悶以下所有程式碼參考網路,在Sublime中編寫。

基於單列表實現:

class Node<E> {
    Node<E> next = null;
    E data;
    public Node(E data) {
        this.data = data;
    }
}

//採用單連結串列實現棧
public class MyStack<E> {
    int depth;   //棧的深度

    public MyStack(int i) {
        this.depth = i;
    }

    Node<E> top = null;

    //將元素壓入棧中
    public boolean push(E data) {
        if(size() < depth) {
        Node<E> newNode = new Node<E>(data);
        newNode.next = top;
        top = newNode;
        return true;
        }
        return false;
    }

    //讀取棧中的頭節點,不刪除頭節點

    public E peek() {
        if(top ==null) {
            return null;
        }
        return top.data;
    }

    //獲取棧中的頭節點,並刪除頭節點
    public E pop() {
        if(top ==null) {
            return null;
        }
        Node<E> tmp = top;
        top = top.next;
        return tmp.data;
    }
    //棧的元素個數

    public int size() {
        int len = 0;
        Node tmeNode = top;
        while(tmeNode != null) {
            tmeNode = tmeNode.next;
            len++;
        }
        return len;
    }

    //當前棧的深度
    public int depth() {
        return this.depth;
    }
    public static void main(String[] args) {
      MyStack stack = new MyStack(2);
      System.out.println(stack.push(1));
      System.out.println(stack.push(2));
      System.out.println(stack.push(3));
      System.out.println("棧的元素個數: " +stack.size());
      System.out.println(stack.pop());
      System.out.println(stack.pop());
      System.out.println(stack.pop());
      System.out.println("棧的元素個數: " + stack.depth());
    }
}
---------------------------此程式碼來自《Java程式設計思想》----------------------------------
import java.util.LinkedList;

public class Stack<T> {
  private LinkedList<T> storage = new LinkedList<T>();
  public void push(T v) { storage.addFirst(v); }
  public T peek() { return storage.getFirst(); }
  public T pop() { return storage.removeFirst(); }
  public boolean empty() { return storage.isEmpty(); }
  public String toString() { return storage.toString(); }
}
複製程式碼

在來看看大佬的另一種實現,簡單明瞭啊。

public class LinkedStack<T> {

	private static class Node<U> {
		U item;
		Node<U> next;
		Node() {
			item = null;
			next =null;
		}
		Node(U item,Node<U> next) {
			this.item = item;
			this.next = next;
		}
		boolean end() {
			return item == null && next == null;
		}
	}

	private Node<T> top = new Node<T>();

	public void push(T item) {
		top = new Node<T>(item,top);
	}

	public T pop() {
		T result = top.item;
		if (!top.end()) {
			top = top.next;
		}
		return result;
	}

	public static void main(String[] args) {
		LinkedStack<String> lss = new LinkedStack<String>();
		for (String s : "Phasers on stun!".split(" ") )
			lss.push(s);

		String s;
		while((s = lss.pop()) != null)
			System.out.println(s);
	}
}
輸出如下:
I:\Java\note\sort\code>java LinkedStack
stun!
on
Phasers
複製程式碼

Queue

佇列(queue),也是一種特殊的線性表,使用固定的一端來插入資料,另一端用於刪除資料

  • 先進先出,就像火車站排隊買票一樣!!!,整個隊伍向前面移動。
  • 分為順序佇列結構和鏈式佇列結構
  • 從JDK 5 開始,Java集合框架提供了Queue介面,實現該介面的類可以當成佇列使用,如LinkedBlockingQueue,PriorityBlockingQueue。
  • 可以通過輪詢和等待-通知機制實現阻塞佇列。

具體Queue實現:

import java.util.*;

public class SimpleQueue<T> implements Iterable<T> {
	private LinkedList<T> storage = new LinkedList<T>();
	public void add(T t){
		storage.offer(t);
	}
	public T get() {
		return storage.poll();
	}
	public Iterator<T> iterator() {
		return storage.iterator();
	}

	public static void main(String[] args) {
		SimpleQueue queue = new SimpleQueue();
		queue.add(8);
		System.out.println(queue.get());
	}
}
複製程式碼

我們在來看看用Stack如何實現Queue,非常不錯,《Java程式設計思想》

import java.util.Stack;

 public class MyQueue{
	Stack<Integer> stack = new Stack<Integer>();
	Stack<Integer> stackTmp = new Stack<Integer>();

	//Push element X to the back of queue
	public void push(int x) {
		stack.push(x);
	}

	//Removes the element form in front of queue
	public void pop() {
		if (stackTmp.isEmpty()) {
			while (!stack.isEmpty()) {
				int tmp = stack.peek();
				stackTmp.push(tmp);
				stack.pop();
			}
		}
		else {
			stackTmp.pop();
		}
	}

	//Get the front element
	public int peek() {
		if (!stackTmp.isEmpty()) {
			int tmp = stack.peek();
			stackTmp.push(tmp);
		}
		return stackTmp.peek();
	}

	//Return whether the queueis empty
	public boolean empty() {
		if (stackTmp.isEmpty() && stack.isEmpty()) {
			return true;
		}else {
			return false;
		}
	}

	public static void main(String[] args) {
		MyQueue queue = new MyQueue();
		queue.push(8);
		System.out.println(queue.empty());     //false
	}
}
複製程式碼

Tree

樹,也是一種資料結構,非線性的,這種結構內的元素存在一對多的關係。

  • 樹,尤其是二叉樹應用很廣泛,排序二叉樹,平衡二叉樹,紅黑樹。

  • 二叉樹,在普通的樹的基礎上,讓一顆樹中每個節點最多隻能包含兩個子節點,且嚴格區分左子節點和右子節點(位置不能變化) -遍歷二叉樹,考慮深讀,優先遍歷。(先序遍歷、中序遍歷、後續遍歷)和廣度優先遍歷。

  • 哈夫曼樹,一種帶權路徑最短的二叉樹,在資訊檢索中非常有用

  • 哈夫曼編碼,假設需要對一個字串如“abcabcabc”進行編碼,將它轉化為唯一的二進位制碼,同時要求轉換出來的二進位制碼的長度最小。可以採用哈夫曼樹來解決報文編碼問題

  • 排序二叉樹:一種特殊的二叉樹,可以非常方便的對樹中的所有節點進行排序和檢索。

二叉樹,這裡採用遞迴和內部類的思想。

public class BinaryTree {
	private Node root;

	//新增節點
	public void add(int data) {
		if (root ==null) {
			root = new Node(data);
		}else {
			root.addNode(data);
		}
	}
	//列印節點
	public void print() {
		root.printNode();
	}

	private class Node {
		private int data;
		private Node left;
		private Node right;

		public Node(int data) {
			this.data = data;
		}

		public void addNode(int data) {
			//核心思想就是進來先個當前節點比,如果如果小於則在左邊新增,如果左邊沒子節點,則建立,如果有新增
			if (this.data > data) {
				if (this.left == null) {
					this.left = new Node(data);
				}else {
					this.addNode(data);    //這裡應該是採用遞迴。
				}
			}else {
				if (this.right == null) {
					this.right = new Node(data);
				}else {
					this.right.addNode(data);
				}
			}
		}

		//中序遍歷
		public void printNode() {
			if (this.left != null) {
				this.left.printNode();
			}
			System.out.println(this.data + "->");
			if (this.right !=null) {
				this.right.printNode();
			}
		}
	}
}
------------------------測試-----------------------------------------------
public static void main(String[] args) {

  BinaryTree bt = new BinaryTree();
  // 8、3、10、1、6、14、4、7、13
  bt.add(8);bt.add(3);bt.add(10);
  bt.add(1);bt.add(6);bt.add(14);
  bt.add(4);bt.add(7);bt.add(13);
  bt.print();
}
輸出:
1->3->4->6->7->8->10->13->14->
複製程式碼

LinkedList

ArrayList因為亂碼,寫了一半,無奈啊,完全坑我,其思想就是根據索引,涉及到擴容,判斷越界了麼,。這裡先不管了。直接看LinkedList。

public class MyLinkedList {
	protected Node first;	// 連結串列的第一個節點
	protected Node last;	// 連結串列的最後一個節點
	private int size;	// 節點的數量

	// 連結串列中的每一個節點
	public class Node {
		public Node(Object ele) {
			this.ele = ele;
		}

		Node prev;				// 上一個節點物件
		Node next;				// 下一個節點物件
		public Object ele; // 當前節點中儲存的資料
	}

	public void addFirst(Object ele) {
		Node node = new Node(ele);      //需要儲存的節點物件
		//進來一個節點,如果為空的話,它可定時第一個,也是最後一個
		if (size == 0) {
			this.first = node;
			this.last = node;
		}else {
			node.next = this.first;				// 把之前第一個作為新增節點的下一個節點,(進來一個,當前的只能當老二了。)
			this.first.prev = node;				// 把新增節點作為之前第一個節點的上一個節點
			this.first = node;					// 把新增的節點作為第一個節點
		}
		size++;
  }
     //這裡很重要,別忘記
	public void addLast(Object ele) {
		Node node = new Node(ele);
		if (size == 0) {
			this.first = node;
			this.last = node;
		}else {
			this.last.next = node;			// 新增節點作為之前最後一個節點的下一個節點(因為是加在後面,所以當前節點的下一個才是 新增節點)
			node.prev = this.last;			// 之前最後一個節點作為新增節點的上一個節點
			this.last = node;				// 把新增的節點作為最後一個節點
		}
	}
	//原諒我複製了
	public void remove(Object ele) {
		// 找到被刪除的節點
		Node current = this.first;// 確定為第一個節點,從頭到尾開始找
		for (int i = 0; i < size; i++) {
			if (!current.ele.equals(ele)) {// 當前為true !true 為false ,說明找到當前ele,輸出
				if (current.next == null) { // 續上: 如果false取反為true, 判斷是否最後一個,
					return;
				}
				current = current.next;
			}
		}
		//刪除節點
		if(current==first){
			this.first = current.next; //當前的下一個作為第一個
			this.first.prev = null; //當前下一個對上一個的引用設定為null

		}else if(current==last){
			this.last = current.prev;
			this.last.next = null;
		}else{
			//把刪除當前節點的下一個節點作為刪除節點的上一個節點的next
			current.prev.next =current.next;
			//把刪除節點的上一個節點作為刪除節點下一個節點的prev
			current.next.prev = current.prev;

		}
		size--;
		//System.out.println("current =" + current.ele);
	}

	public String toString() {
		if (size == 0) {
			return "[]";
		}
		StringBuilder sb = new StringBuilder(size * 2 + 1);
		Node current = this.first;// 第一個節點
		sb.append("[");
		for (int i = 0; i < size; i++) {
			sb.append(current.ele);
			if (i != size - 1) {
				sb.append(",");
			} else {
				sb.append("]");
			}
			current = current.next; // 獲取自己的下一個節點
		}
		return sb.toString();
	}
}
複製程式碼

這個雙向列表有點難理解,還是看圖吧,

線性連結串列:

資料結構分析及其實現(Stack、Queue、Tree、LinkedList)

雙向連結串列:

資料結構分析及其實現(Stack、Queue、Tree、LinkedList)

先到這裡吧,gogogo,機會是留給有準備的人,

相關文章