數據結構是相互之間存在一種或多種關係的數據的集合。java
分爲線性結構和非線性結構,node
即物理結構,分四種(存儲數據時不僅要存儲元素的值,還要存儲數據元素間的關係)面試
時間複雜度就是程序邏輯執行的次數。一般在求解時間複雜度的時候,會對其進行簡化。下面是推導大 O 階的方法:算法
常見的時間複雜度:數組
int num = 0, n = 100; num = (1 + n) * n / 2; printf(num);
主義上面的時間複雜度是常數階 O(1),而不是 O(3).數據結構
for(int i=0; i<n; i++) { // 執行時間複雜度爲O(1)的操做 }
上面的時間複雜度是 O(n).架構
int cnt = 1; while (cnt < n) { cnt *= 2; // 執行時間複雜度爲O(1)的操做 }
上面的時間複雜度是 O(logn).ide
int i, j; for (int i=0;i<n;i++) } for (int j=0;j<n;j++) { // 執行時間複雜度爲O(1)的操做 } }
上面的計算的時間複雜度是 O(n2)函數
int i, j; for (int i=0;i<n;i++) } for (int j=0;j<m;j++) { // 執行時間複雜度爲O(1)的操做 } }
上面的計算的時間複雜度是 O(nm)學習
下面的程序的時間複雜度也是 O(n2):
int i, j; for (int i=0;i<n;i++) } for (int j=i;j<n;j++) { // 執行時間複雜度爲O(1)的操做 } }
執行的次數,O(f(n)),其中 f(n) 是執行的次數,表示執行時間與 f(n) 成正比
時間複雜度的大小關係:
O(1)<O(log2n)<O(n)<O(nLogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(nn)
所需的物理存儲空間
題目:算法 void fun(int n){ int i=1; while(i<=n) { i*=2; } } 的時間複雜度是? 思路:函數中運算次數最多的是 i*=2; 這一行,那麼假設它執行了 t 次,t 次時 i=2^t。 所以,有:2^t<=n,因而得 t<=log2n ,因而可得 O(log2n)
線性表是具備相同數據類型的n個數據元素的有限序列。
線性表的順序存儲,即存儲在一組連續的存儲單元裏,如數組。順序表能夠是靜態分配的,也能夠是動態分配的。動態分配的,好比用指針指示動態數組,靜態分配的是建立數組的時候就指定數組的大小。
注意,在線性表當中插入或者刪除數據是要移動其餘元素的,而訪問的是否直接使用索引訪問便可。因此,對於線性表訪問第i個位置的元素的時間複雜度爲 O(1),在第i個位置插入元素的時間複雜度爲 O(n).
普通的鏈表,定義的形式是
public class LinkedList<E> { transient int size = 0; transient Node<E> first; private static class Node<E> { E item; Node<E> next; Node(E element, Node<E> next) { this.item = element; this.next = next; } } }
這裏使用泛型的來表明鏈表的每一個節點中存儲的數據,使用內部類 Node 類定義鏈表的一個節點。
下面的是一份基於Java的雙向鏈表的實現:
public class LinkedList<E> { transient int size = 0; transient Node<E> first; transient Node<E> last; private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } }
藉助數組類描述鏈表,結點有數據域 data 和指針域 next,描述(這種設計的好處是適用於不支持指針的計算機語言)
棧是一種後進先出的數據結構,下面是一種使用單向鏈表實現的棧:
public class Stack<E>{ private int size; private Node<E> first; private static class Node<E> { E element; Node<E> next; Node(E element, Node<E> next) { this.element = element; this.next = next; } } public void push(E element) { first = new Node<E>(element, first); size++; } public E pop() { if (size == 0) { throw new UnsupportedOperationException("The stack is empty."); } size --; Node<E> oldFirst = first; first = oldFirst.next; return oldFirst.element; } public boolean isEmpty() { return size == 0; } }
題1:3個不一樣的元素依次進棧,能獲得幾種不一樣的出棧序列?
5種,abc acb bac bca cba,最後一種若是以c開頭,那麼ab必然已經存入了棧中,取出的順序只能是ba,即以c開頭的只有cba.
題2:a b c d e f 以所給的順序依次進棧,若在操做時容許出棧,這得不到的序列爲?
A fedbca B bcafed C dcefba D cabdef
這種題應該從每一個選項的第一個字母入手,以C爲例,若是d處在第一個,那麼說明前面的a b c確定已經存在棧中,那麼它們必然按照c b a的順序出來;
若是題目中的c b a的出現次序不對,那麼就得不到。而後再使用相同的思路判斷第二個字符的狀況。答案D
隊列也是一種線性表,特性是先入先出,隊列和棧的主要區別是插入、刪除操做的限定不同。下面是基於 Java 的一種使用鏈表來實現的隊列:
public class Queue<E> { private Node<E> first, last; private int size; private static class Node<E> { E element; Node<E> next; Node(E element, Node<E> next) { this.element = element; this.next = next; } } public void enqueue(E element) { size++; Node<E> node = new Node<E>(element, null); if (last == null) { first = node; last = node; return; } last.next = node; last = node; } public E dequeue() { if (size == 0) { throw new UnsupportedOperationException("The queue is empty."); } size--; E element = first.element; first = first.next; return element; } public boolean isEmpty() { return size == 0; } }
隊首和隊尾都容許入隊和出隊的隊列。
揹包是一種不支持從中刪除元素的集合數據類型,它的目的就是幫助用例收集並迭代遍歷全部收集到的元素。使用揹包的不少場景可能使用棧或者隊列也能實現,可是使用揹包能夠說明元素存儲的順序不重要。下面的是一份基於 Java 的揹包的實現,在這裏咱們只是在以前棧的代碼的基礎之上作了一些修改,並讓其實現 Iterable 接口以在 foreach 循環中使用揹包遍歷元素:
public class Bag<E> implements Iterable<E>{ private int size; private Node<E> first; private static class Node<E> { E element; Node<E> next; Node(E element, Node<E> next) { this.element = element; this.next = next; } } public void add(E element) { first = new Node<E>(element, first); size++; } public Iterator<E> iterator() { return new ListIterator(); } private class ListIterator implements Iterator<E> { private Node<E> current = first; @Override public boolean hasNext() { return current != null; } @Override public E next() { E element = current.element; current = current.next; return element; } @Override public void remove() {} } public boolean isEmpty() { return size == 0; } }
題:一棵有n個結點的樹,全部結點的度數之和爲_______.
問題轉換成:一棵有3個結點的樹,全部結點的度數之和爲____. 由於題目是選擇,因此應該儘可能簡化題目。答案n-1
image
在上圖中左側的是徹底二叉樹,右側的是滿二叉樹。
說明:所謂的編號就是指每層從左到右,按照滿二叉樹的形式編號,上面的滿二叉樹每一個結點的值就是它們的編號。這個編號也是咱們在使用順序存儲的時候對應數組的下標。
1.順序存儲
這種存儲方式,能夠理解爲使用數組存儲,注意開始存儲的下標是1,這是爲了與二叉樹的性質對應,另外有時候咱們也能夠將數組的第一個元素做爲哨兵。順序存儲的基本思想是,按照滿二叉樹的編號順序,若是指定編號(其實就是數組的下標)處有結點的話,數組指定位置的值即爲結點的值,不然爲0(表示空結點)。固然,也能夠在各個元素中存放一些具備具體含義的值。
如圖所示的樹在數組中的實際存儲爲:- 1 2 3 0 4 0 5 0 0 6 0(第0位不使用)。
2.鏈式存儲
下面是使用Java代碼實現的一個二叉樹,這裏每一個結點要包含左右兩個子結點以及相應的數據實體。顯然,
public class Tree<E> { private Node<E> root; private static class Node<E> { E element; Node<E> leftChild; Node<E> rightChild; Node(Node<E> leftChild, E element, Node<E> rightChild) { this.leftChild = leftChild; this.element = element; this.rightChild = rightChild; } } }
說明:上述三幅圖從左到右的遍歷方式依次是先序遍歷、中序遍歷和後序遍歷。
做爲練習,這裏給出遍歷二叉樹的方法,能夠觀察一下二叉樹的實現,以及中序遍歷的時候輸出的二叉樹的值,其中 outNode() 方法用來輸出結點的值:
/* 先序遍歷 */ public void former() { former(root); } private void former(Node<Key, Value> node) { if (node == null) return; outNode(node); // 先序 former(node.leftChild); former(node.rightChild); } /* 中序遍歷,注意中序輸出的結果和排序的結果的關係 */ public void center() { center(root); } private void center(Node<Key, Value> node) { if (node == null) return; center(node.leftChild); outNode(node); // 中序 center(node.rightChild); } /* 後序遍歷 */ public void latter() { latter(root); } private void latter(Node<Key, Value> node) { if (node == null) return; latter(node.leftChild); latter(node.rightChild); outNode(node); // 後序 }
結論:
注意若是隻知道二叉樹的先序序列和後序序列是沒法惟一地肯定一棵二叉樹的。
image
如圖所示的二叉樹,它的三種遍歷方式:
說明:先序遍歷的時候,根結點處在第1的位置;中序遍歷的時候根節點前面是左子樹,後面是右子樹;後序遍歷的時候,根結點處在最後的位置。能夠根據上面的思路,依次進行判斷,從而能夠寫出完整的樹。
使用一個V*V矩陣來表示,其中V是頂點的個數。矩陣的某個元素中存儲的能夠是布爾變量,表示該邊是否存在,在實際應用中也能夠將其設置爲某個邊的權重。使用這種方式的缺點是當數據量比較大的時候會佔用很大的存儲空間。
即便用一個以頂點爲數組的索引的列表數組來表示圖。如下是圖的一個基於Java的實現:
public class Graph { private Bag<Integer>[] adj; public Graph(int V) { adj = new Bag[V]; for (int i=0;i<V;i++) { adj[i] = new Bag<Integer>(); } } }
能夠看出實際在這裏咱們使用了揹包來存儲圖的數據。adj 的具體含義是:好比,若是頂點 0 對應的數組元素是 adj[0],這裏的 adj[0] 是一個揹包,它裏面存儲了與頂點 0 相連的其餘頂點。若是是無向圖的話,只要在揹包中存儲全部相關的頂點就能夠了。若是是有向圖的話,須要存儲該點指向的頂點的值。
它跟鄰接表的區別在於,在鄰接表中,同一條邊由用兩個頂點表示,而在鄰接多重表中只用一個頂點表示。
上圖就是邊集數組的表示方法,能夠看出它使用一個數組來存儲各個邊。