數據結構是以某種形式將數據組織在一塊兒的集合,它不只存儲數據,還支持訪問和處理數據的操做。算法是爲求解一個問題須要遵循的、被清楚指定的簡單指令的集合。下面是本身整理的經常使用數據結構與算法相關內容,若有錯誤,歡迎指出。html
爲了便於描述,文中涉及到的代碼部分都是用Java語言編寫的,其實Java自己對常見的幾種數據結構,線性表、棧、隊列等都提供了較好的實現,就是咱們常常用到的Java集合框架,有須要的能夠閱讀這篇文章。Java - 集合框架徹底解析前端
1、線性表
1.數組實現 2.鏈表 2、棧與隊列 3、樹與二叉樹 1.樹 2.二叉樹基本概念 3.二叉查找樹 4.平衡二叉樹 5.紅黑樹 4、圖 5、總結
線性表是最經常使用且最簡單的一種數據結構,它是n個數據元素的有限序列。node
實現線性表的方式通常有兩種,一種是使用數組存儲線性表的元素,即用一組連續的存儲單元依次存儲線性表的數據元素。另外一種是使用鏈表存儲線性表的元素,即用一組任意的存儲單元存儲線性表的數據元素(存儲單元能夠是連續的,也能夠是不連續的)。算法
數組實現
數組是一種大小固定的數據結構,對線性表的全部操做均可以經過數組來實現。雖然數組一旦建立以後,它的大小就沒法改變了,可是當數組不能再存儲線性表中的新元素時,咱們能夠建立一個新的大的數組來替換當前數組。這樣就可使用數組實現動態的數據結構。swift
int[] oldArray = new int[10]; int[] newArray = new int[20]; for (int i = 0; i < oldArray.length; i++) { newArray[i] = oldArray[i]; } // 也可使用System.arraycopy方法來實現數組間的複製 // System.arraycopy(oldArray, 0, newArray, 0, oldArray.length); oldArray = newArray;
//oldArray 表示當前存儲元素的數組
//size 表示當前元素個數 public void add(int index, int e) { if (index > size || index < 0) { System.out.println("位置不合法..."); } //若是數組已經滿了 就擴容 if (size >= oldArray.length) { // 擴容函數可參考代碼1 } for (int i = size - 1; i >= index; i--) { oldArray[i + 1] = oldArray[i]; } //將數組elementData從位置index的全部元素日後移一位 // System.arraycopy(oldArray, index, oldArray, index + 1,size - index); oldArray[index] = e; size++; }
上面簡單寫出了數組實現線性表的兩個典型函數,具體咱們能夠參考Java裏面的ArrayList集合類的源碼。數組實現的線性表優勢在於能夠經過下標來訪問或者修改元素,比較高效,主要缺點在於插入和刪除的花費開銷較大,好比當在第一個位置前插入一個元素,那麼首先要把全部的元素日後移動一個位置。爲了提升在任意位置添加或者刪除元素的效率,能夠採用鏈式結構來實現線性表。後端
鏈表
鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表中的指針連接次序實現的。鏈表由一系列節點組成,這些節點沒必要在內存中相連。每一個節點由數據部分Data和鏈部分Next,Next指向下一個節點,這樣當添加或者刪除時,只須要改變相關節點的Next的指向,效率很高。數組
下面主要用代碼來展現鏈表的一些基本操做,須要注意的是,這裏主要是以單鏈表爲例,暫時不考慮雙鏈表和循環鏈表。數據結構
class Node<E> { E item; Node<E> next; //構造函數 Node(E element) { this.item = element; this.next = null; } }
//頭節點和尾節點都爲空 鏈表爲空
Node<E> head = null; Node<E> tail = null;
//建立一個新的節點 並讓head指向此節點
head = new Node("nodedata1"); //讓尾節點也指向此節點 tail = head;
//建立新節點 同時和最後一個節點鏈接起來 tail.next = new Node("node1data2"); //尾節點指向新的節點 tail = tail.next;
Node<String> current = head; while (current != null) { System.out.println(current.item); current = current.next; }
static void printListRev(Node<String> head) { //倒序遍歷鏈表主要用了遞歸的思想 if (head != null) { printListRev(head.next); System.out.println(head.item); } }
//單鏈表反轉 主要是逐一改變兩個節點間的連接關係來完成
static Node<String> revList(Node<String> head) { if (head == null) { return null; } Node<String> nodeResult = null; Node<String> nodePre = null; Node<String> current = head; while (current != null) { Node<String> nodeNext = current.next; if (nodeNext == null) { nodeResult = current; } current.next = nodePre; nodePre = current; current = nodeNext; } return nodeResult; }
上面的幾段代碼主要展現了鏈表的幾個基本操做,還有不少像獲取指定元素,移除元素等操做你們能夠本身完成,寫這些代碼的時候必定要理清節點之間關係,這樣纔不容易出錯。框架
鏈表的實現還有其它的方式,常見的有循環單鏈表,雙向鏈表,循環雙向鏈表。 循環單鏈表 主要是鏈表的最後一個節點指向第一個節點,總體構成一個鏈環。 雙向鏈表 主要是節點中包含兩個指針部分,一個指向前驅元,一個指向後繼元,JDK中LinkedList集合類的實現就是雙向鏈表。 循環雙向鏈表 是最後一個節點指向第一個節點。函數
棧和隊列也是比較常見的數據結構,它們是比較特殊的線性表,由於對於棧來講,訪問、插入和刪除元素只能在棧頂進行,對於隊列來講,元素只能從隊列尾插入,從隊列頭訪問和刪除。
棧
棧是限制插入和刪除只能在一個位置上進行的表,該位置是表的末端,叫做棧頂,對棧的基本操做有push(進棧)和pop(出棧),前者至關於插入,後者至關於刪除最後一個元素。棧有時又叫做LIFO(Last In First Out)表,即後進先出。
下面咱們看一道經典題目,加深對棧的理解。
上圖中的答案是C,其中的原理能夠好好想想。
由於棧也是一個表,因此任何實現表的方法都能實現棧。咱們打開JDK中的類Stack的源碼,能夠看到它就是繼承類Vector的。固然,Stack是Java2前的容器類,如今咱們可使用LinkedList來進行棧的全部操做。
隊列
隊列是一種特殊的線性表,特殊之處在於它只容許在表的前端(front)進行刪除操做,而在表的後端(rear)進行插入操做,和棧同樣,隊列是一種操做受限制的線性表。進行插入操做的端稱爲隊尾,進行刪除操做的端稱爲隊頭。
咱們可使用鏈表來實現隊列,下面代碼簡單展現了利用LinkedList來實現隊列類。
public class MyQueue<E> { private LinkedList<E> list = new LinkedList<>(); // 入隊 public void enqueue(E e) { list.addLast(e); } // 出隊 public E dequeue() { return list.removeFirst(); } }
普通的隊列是一種先進先出的數據結構,而優先隊列中,元素都被賦予優先級。當訪問元素的時候,具備最高優先級的元素最早被刪除。優先隊列在生活中的應用仍是比較多的,好比醫院的急症室爲病人賦予優先級,具備最高優先級的病人最早獲得治療。在Java集合框架中,類PriorityQueue就是優先隊列的實現類,具體你們能夠去閱讀源碼。
樹型結構是一類很是重要的非線性數據結構,其中以樹和二叉樹最爲經常使用。在介紹二叉樹以前,咱們先簡單瞭解一下樹的相關內容。
樹
樹 是由n(n>=1)個有限節點組成一個具備層次關係的集合。它具備如下特色:每一個節點有零個或多個子節點;沒有父節點的節點稱爲 根 節點;每個非根節點有且只有一個 父節點 ;除了根節點外,每一個子節點能夠分爲多個不相交的子樹。
二叉樹基本概念
二叉樹是每一個節點最多有兩棵子樹的樹結構。一般子樹被稱做「左子樹」和「右子樹」。二叉樹常被用於實現二叉查找樹和二叉堆。
二叉樹的每一個結點至多隻有2棵子樹(不存在度大於2的結點),二叉樹的子樹有左右之分,次序不能顛倒。
二叉樹的第i層至多有2^(i-1)個結點;深度爲k的二叉樹至多有2^k-1個結點。
一棵深度爲k,且有2^k-1個節點的二叉樹稱之爲 滿二叉樹 ;
深度爲k,有n個節點的二叉樹,當且僅當其每個節點都與深度爲k的滿二叉樹中,序號爲1至n的節點對應時,稱之爲 徹底二叉樹 。
在二叉樹的一些應用中,經常要求在樹中查找具備某種特徵的節點,或者對樹中所有節點進行某種處理,這就涉及到二叉樹的遍歷。二叉樹主要是由3個基本單元組成,根節點、左子樹和右子樹。若是限定先左後右,那麼根據這三個部分遍歷的順序不一樣,能夠分爲先序遍歷、中序遍歷和後續遍歷三種。
(1) 先序遍歷 若二叉樹爲空,則空操做,不然先訪問根節點,再先序遍歷左子樹,最後先序遍歷右子樹。 (2) 中序遍歷 若二叉樹爲空,則空操做,不然先中序遍歷左子樹,再訪問根節點,最後中序遍歷右子樹。(3) 後序遍歷 若二叉樹爲空,則空操做,不然前後序遍歷左子樹訪問根節點,再後序遍歷右子樹,最後訪問根節點。
(1) 二叉樹每一個節點最多有2個子節點,樹則無限制。 (2) 二叉樹中節點的子樹分爲左子樹和右子樹,即便某節點只有一棵子樹,也要指明該子樹是左子樹仍是右子樹,即二叉樹是有序的。 (3) 樹決不能爲空,它至少有一個節點,而一棵二叉樹能夠是空的。
上面咱們主要對二叉樹的相關概念進行了介紹,下面咱們將從二叉查找樹開始,介紹二叉樹的幾種常見類型,同時將以前的理論部分用代碼實現出來。
二叉查找樹
二叉查找樹就是二叉排序樹,也叫二叉搜索樹。二叉查找樹或者是一棵空樹,或者是具備下列性質的二叉樹: (1) 若左子樹不空,則左子樹上全部結點的值均小於它的根結點的值;(2) 若右子樹不空,則右子樹上全部結點的值均大於它的根結點的值;(3) 左、右子樹也分別爲二叉排序樹;(4) 沒有鍵值相等的結點。
對於二叉查找樹來講,當給定值相同但順序不一樣時,所構建的二叉查找樹形態是不一樣的,下面看一個例子。
能夠看到,含有n個節點的二叉查找樹的平均查找長度和樹的形態有關。最壞狀況下,當前後插入的關鍵字有序時,構成的二叉查找樹蛻變爲單支樹,樹的深度爲n,其平均查找長度(n+1)/2(和順序查找相同),最好的狀況是二叉查找樹的形態和折半查找的斷定樹相同,其平均查找長度和log2(n)成正比。平均狀況下,二叉查找樹的平均查找長度和logn是等數量級的,因此爲了得到更好的性能,一般在二叉查找樹的構建過程須要進行「平衡化處理」,以後咱們將介紹平衡二叉樹和紅黑樹,這些都可以使查找樹的高度爲O(log(n))。
class TreeNode<E> { E element; TreeNode<E> left; TreeNode<E> right; public TreeNode(E e) { element = e; } }
二叉查找樹的三種遍歷均可以直接用遞歸的方法來實現:
protected void preorder(TreeNode<E> root) { if (root == null) return; System.out.println(root.element + " "); preorder(root.left); preorder(root.right); }
protected void inorder(TreeNode<E> root) { if (root == null) return; inorder(root.left); System.out.println(root.element + " "); inorder(root.right); }
protected void postorder(TreeNode<E> root) { if (root == null) return; postorder(root.left); postorder(root.right); System.out.println(root.element + " "); }
/** * @author JackalTsc */ public class MyBinSearchTree<E extends Comparable<E>> { // 根 private TreeNode<E> root; // 默認構造函數 public MyBinSearchTree() { } // 二叉查找樹的搜索 public boolean search(E e) { TreeNode<E> current = root; while (current != null) { if (e.compareTo(current.element) < 0) { current = current.left; } else if (e.compareTo(current.element) > 0) { current = current.right; } else { return true; } } return false; } // 二叉查找樹的插入 public boolean insert(E e) { // 若是以前是空二叉樹 插入的元素就做爲根節點 if (root == null) { root = createNewNode(e); } else { // 不然就從根節點開始遍歷 直到找到合適的父節點 TreeNode<E> parent = null; TreeNode<E> current = root; while (current != null) { if (e.compareTo(current.element) < 0) { parent = current; current = current.left; } else if (e.compareTo(current.element) > 0) { parent = current; current = current.right; } else { return false; } } // 插入 if (e.compareTo(parent.element) < 0) { parent.left = createNewNode(e); } else { parent.right = createNewNode(e); } } return true; } // 建立新的節點 protected TreeNode<E> createNewNode(E e) { return new TreeNode(e); } } // 二叉樹的節點 class TreeNode<E extends Comparable<E>> { E element; TreeNode<E> left; TreeNode<E> right; public TreeNode(E e) { element = e; } }
上面的代碼15主要展現了一個本身實現的簡單的二叉查找樹,其中包括了幾個常見的操做,固然更多的操做仍是須要你們本身去完成。由於在二叉查找樹中刪除節點的操做比較複雜,因此下面我詳細介紹一下這裏。
要在二叉查找樹中刪除一個元素,首先須要定位包含該元素的節點,以及它的父節點。假設current指向二叉查找樹中包含該元素的節點,而parent指向current節點的父節點,current節點多是parent節點的左孩子,也多是右孩子。這裏須要考慮兩種狀況:
// 二叉搜索樹刪除節點 public boolean delete(E e) { TreeNode<E> parent = null; TreeNode<E> current = root; // 找到要刪除的節點的位置 while (current != null) { if (e.compareTo(current.element) < 0) { parent = current; current = current.left; } else if (e.compareTo(current.element) > 0) { parent = current; current = current.right; } else { break; } } // 沒找到要刪除的節點 if (current == null) { return false; } // 考慮第一種狀況 if (current.left == null) { if (parent == null) { root = current.right; } else { if (e.compareTo(parent.element) < 0) { parent.left = current.right; } else { parent.right = current.right; } } } else { // 考慮第二種狀況 TreeNode<E> parentOfRightMost = current; TreeNode<E> rightMost = current.left; // 找到左子樹中最大的元素節點 while (rightMost.right != null) { parentOfRightMost = rightMost; rightMost = rightMost.right; } // 替換 current.element = rightMost.element; // parentOfRightMost和rightMost左孩子相連 if (parentOfRightMost.right == rightMost) { parentOfRightMost.right = rightMost.left; } else { parentOfRightMost.left = rightMost.left; } } return true; }
平衡二叉樹
平衡二叉樹又稱AVL樹,它或者是一棵空樹,或者是具備下列性質的二叉樹:它的左子樹和右子樹都是平衡二叉樹,且左子樹和右子樹的深度之差的絕對值不超過1。
AVL樹是最早發明的自平衡二叉查找樹算法。在AVL中任何節點的兩個兒子子樹的高度最大差異爲1,因此它也被稱爲高度平衡樹,n個結點的AVL樹最大深度約1.44log2n。查找、插入和刪除在平均和最壞狀況下都是O(log n)。增長和刪除可能須要經過一次或屢次樹旋轉來從新平衡這個樹。
紅黑樹
紅黑樹是平衡二叉樹的一種,它保證在最壞狀況下基本動態集合操做的事件複雜度爲O(log n)。紅黑樹和平衡二叉樹區別以下:(1) 紅黑樹放棄了追求徹底平衡,追求大體平衡,在與平衡二叉樹的時間複雜度相差不大的狀況下,保證每次插入最多隻須要三次旋轉就能達到平衡,實現起來也更爲簡單。(2) 平衡二叉樹追求絕對平衡,條件比較苛刻,實現起來比較麻煩,每次插入新節點以後須要旋轉的次數不能預知。點擊查看更多
圖是一種較線性表和樹更爲複雜的數據結構,在線性表中,數據元素之間僅有線性關係,在樹形結構中,數據元素之間有着明顯的層次關係,而在圖形結構中,節點之間的關係能夠是任意的,圖中任意兩個數據元素之間均可能相關。圖的應用至關普遍,特別是近年來的迅速發展,已經滲入到諸如語言學、邏輯學、物理、化學、電訊工程、計算機科學以及數學的其餘分支中。
由於圖這部分的內容仍是比較多的,這裏就不詳細介紹了,有須要的能夠本身搜索相關資料。
(1) 《百度百科對圖的介紹》
(2) 《數據結構之圖(存儲結構、遍歷)》
到這裏,關於常見的數據結構的整理就結束了,斷斷續續大概花了兩天時間寫完,在總結的過程當中,經過查閱相關資料,結合書本內容,收穫仍是很大的,在下一篇博客中將會介紹經常使用數據結構與算法整理總結(下)之算法篇,歡迎你們關注。