在編程過程當中,一般會遇到的一個問題就是,性能瓶頸。不少時候考慮的都是怎麼去作橫向擴展,但恰恰忽略掉了最基本的問題就是系統是否真的已經達到了瓶頸?
性能瓶頸一般的表象是資源消耗過多外部處理系統的性能不足;或者資源消耗很少但程序的響應速度卻仍達不到要求。
而調優的方式就是 尋找過分消耗資源的代碼 和 尋找未充分使用資源但程序執行慢的緣由和代碼。 基礎決定高度 就拿汽車來比較,一般不懂變速箱、發動機的原理但也是能開車,一樣一個不懂數據結構和算法的人也能編程。不少人以爲基本的數據結構及操做已經在高級語言中封裝,均可以直接調用庫函數,那麼到底有沒有必要好好學習數據結構?java
數據結構+算法=程序
node
一般在程序中,遇到一個實際問題,充分利用數據結構,將數據及其之間的關係有效地存儲在計算機中,而後選擇合適的算法策略,並用程序高效實現,這纔是提升程序性能的主要方式。git
若是沒有具有這塊相應的知識,怎麼完成上述的實現?若是脫離了原有的調用,怎麼完成程序的高效實現?而全部的應用實現都依賴於基礎,基礎就是數據結構和算法。瞭解這塊,才能作到無懼任何技術的演變。全部說基礎決定高度!github
數據結構表示數據在計算機中的存儲和組織形式,主要描述數據元素之間和位置關係等。選擇適當的數據結構能夠提升計算機程序的運行效率(時間複雜度)和存儲效率(空間複雜度)。算法
數據結構的三種層次
:編程
集合結構(集)
: 全部的元素都屬於一個整體,除了同屬於一個集合外沒有其餘關係。集合結構不強調元素之間的任何關聯性。線性結構(表)
: 數據元素之間具備一對一的先後關係。結構中必須存在惟一的首元素和惟一的尾元素。樹形結構(樹)
: 數據元素之間一對多的關係網狀結構(圖)
: 圖狀結構或網狀結構 結構中的數據元素之間存在多對多的關係順序結構
: 順序結構就是使用一組連續的存儲單元依次存儲邏輯上相鄰的各個元素
優勢
: 只須要申請存放數據自己的內存空間便可,支持下標訪問,也能夠實現隨機訪問。
缺點
: 必須靜態分配連續空間,內存空間的利用率比較低。插入或刪除可能須要移動大量元素,效率比較低數組
鏈式結構
: 鏈式存儲結構不使用連續的存儲空間存放結構的元素,而是爲每個元素構造一個節點。節點中除了存放數據自己之外,還須要存放指向下一個節點的指針。
優勢
: 不採用連續的存儲空間致使內存空間利用率比較高,克服順序存儲結構中預知元素個數的缺點 插入或刪除元素時,不須要移動大量的元素。
缺點
: 須要額外的空間來表達數據之間的邏輯關係, 不支持下標訪問和隨機訪問。數據結構
索引結構
: 除創建存儲節點信息外,還創建附加的索引表來標節點的地址。索引表由若干索引項組成。
優勢
: 是用節點的索引號來肯定結點存儲地址,檢索速度塊
`缺點: 增長了附加的索引表,會佔用較多的存儲空間。app
散列結構
: 由節點的關鍵碼值決定節點的存儲地址。散列技術除了能夠用於查找外,還能夠用於存儲。
優勢
: 散列是數組存儲方式的一種發展,採用存儲數組中內容的部分元素做爲映射函數的輸入,映射函數的輸出就是存儲數據的位置, 相比數組,散列的數據訪問速度要高於數組
缺點
: 不支持排序,通常比用線性表存儲須要更多的空間,而且記錄的關鍵字不能重複。數據結構和算法
數據結構比較
經常使用的數據結構:
數據結構選擇:
O符號
O在算法當中表述的是時間複雜度,它在分析算法複雜性的方面很是有用。常見的有:
O(1)
:最低的複雜度,不管數據量大小,耗時都不變,均可以在一次計算後得到。哈希算法就是典型的O(1)O(n)
:線性,n表示數據的量,當量增大,耗時也增大,常見有遍歷算法O(n²)
:平方,表示耗時是n的平方倍,當看到循環嵌循環的時候,基本上這個算法就是平方級的,如:冒泡排序等O(log n)
:對數,一般ax=n,那麼數x叫作以a爲底n的對數,也就是x=logan,這裏是a一般是2,如:數量增大8倍,耗時只增長了3倍,二分查找就是對數級的算法,每次剔除一半O(n log n)
:線性對數,就是n乘以log n,按照上面說的數據增大8倍,耗時就是8*3=24倍,歸併排序就是線性對數級的算法在Java中,數組是用來存放同一種數據類型的集合,注意只能存放同一種數據類型。
//只聲明瞭類型和長度
數據類型 [] 數組名稱 = new 數據類型[數組長度];
//聲明瞭類型,初始化賦值,大小由元素個數決定
數據類型 [] 數組名稱 = {數組元素1,數組元素2,......}
複製代碼
大小固定,不能動態擴展(初始化給大了,浪費;給小了,不夠用),插入快,刪除和查找慢
public class Array {
private int[] intArray;
private int elems;
private int length;
public Array(int max) {
length = max;
intArray = new int[max];
elems = 0;
}
/** * 添加 * @param value */
public void add(int value){
if(elems == length){
System.out.println("error");
return;
}
intArray[elems] = value;
elems++;
}
/** * 查找 * @param searchKey * @return */
public int find(int searchKey){
int i;
for(i = 0; i < elems; i++){
if(intArray[i] == searchKey)
break;
}
if(i == elems){
return -1;
}
return i;
}
/** * 刪除 * @param value * @return */
public boolean delete(int value){
int i = find(value);
if(i == -1){
return false;
}
for (int j = i; j < elems-1; j++) {
//後面的數據往前移
intArray[j] = intArray[j + 1];
}
elems--;
return true;
}
/** * 更新 * @param oldValue * @param newValue * @return */
public boolean update(int oldValue,int newValue){
int i = find(oldValue);
if(i == -1){
return false;
}
intArray[i] = newValue;
return true;
}
/** * 顯示全部 */
public void display(){
for(int i = 0 ; i < elems ; i++){
System.out.print(intArray[i]+" ");
}
System.out.print("\n");
}
/** * 冒泡排序 * 每趟冒出一個最大數/最小數 * 每次運行數量:總數量-運行的趟數(已冒出) */
public void bubbleSort(){
for(int i = 0; i < elems-1; i++){//排序趟數 n-1次就好了
System.out.println("第"+(i+1)+"趟:");
for(int j = 0; j < elems-i-1; j++){//每趟次數 (n-i) -1是防止下標越界,後面賦值用到了+1
if(intArray[j] > intArray[j+1]){ //控制按大冒泡仍是按小冒泡
int temp = intArray[j];
intArray[j] = intArray[j+1];
intArray[j+1] = temp;
}
display();
}
}
}
/*** * 選擇排序 * 每趟選擇一個最大數/最小數 * 每次運行數量:總數量-運行的趟數(已選出) */
public void selectSort(){
for(int i = 0; i < elems-1; i++) {//排序趟數 n-1次就好了
int min = i;
for(int j = i+1; j < elems; j++){ //排序次數 每趟選擇一個數出來,-1次
if(intArray[j] < intArray[min]){
min = j;
}
}
if(i != min ){
int temp = intArray[i];
intArray[i] = intArray[min];
intArray[min] = temp;
}
display();
}
}
/** * 插入排序 * 每趟選擇一個待插入的數 * 每次運行把待插入的數放在比它大/小後面 */
public void insertSort(){
int j;
for(int i = 1; i < elems; i++){
int temp = intArray[i];
j = i;
while (j > 0 && temp < intArray[j-1]){
intArray[j] = intArray[j-1];
j--;
}
intArray[j] = temp;
}
display();
}
public static void main(String[] args) {
Array array = new Array(10);
array.add(6);
array.add(3);
array.add(8);
array.add(2);
array.add(11);
array.add(5);
array.add(7);
array.add(4);
array.add(9);
array.add(10);
// array.bubbleSort();
// array.selectSort();
array.insertSort();
// array.display();
// System.out.println(array.find(4));
// System.out.println(array.delete(1));
// array.display();
// System.out.println(array.update(2,6));
// array.display();
}
}
複製代碼
O(n)
O(n)
O(1)
O(1)
Stack()
複製代碼
public class Stack {
//小貼士:一般能夠利用棧實現字符串逆序,還能夠利用棧判斷分隔符是否匹配,如<a[b{c}]>,能夠正進反出,棧爲空則成功。
/**基於數組實現的順序棧,連續存儲的線性實現,須要初始化容量**/
//固定數據類型
//private int[] array;
//動態數據類型
private Object[] objArray;
private int maxSize;
private int top;
public Stack() {
}
public Stack(int maxSize) {
if(maxSize > 0){
objArray = new Object[maxSize];
this.maxSize = maxSize;
top = -1;
}else{
throw new RuntimeException("初始化大小錯誤:maxSize=" + maxSize);
}
}
/** * 入棧 * @param obj */
public void objPush(Object obj){
//擴容
grow();
//++在前表示先運算再賦值,優先級高,在後表示先賦值再運算,優先級低
objArray[++top] = obj;
}
/** * 出棧 * @return */
public Object objPop(){
Object obj = peekTop();
//聲明原頂棧能夠回收空間(GC)
objArray[top--] = null;
return obj;
}
/** * 查看棧頂 * @return */
public Object peekTop(){
if(top != -1){
return objArray[top];
}else{
throw new RuntimeException("stack is null");
}
}
/** * 動態擴容 */
public void grow(){
// << 左移運算符,1表示乘以2的1次方
if(top == maxSize-1){
maxSize = maxSize<<1;
objArray = Arrays.copyOf(objArray,maxSize);
}
}
/**基於鏈式存儲,不連續存儲的非線性實現**/
private class Node<Object>{
private Object data;
private Node next;
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
}
private Node nodeTop;
private int size;
public void nodePush(Object obj){
//棧頂指向新元素,新元素的next指向原棧頂元素
nodeTop = new Node(obj,nodeTop);
size++;
}
public Object nodePop(){
Node old = nodeTop;
//聲明原頂棧能夠回收空間(GC)
old.next = null;
//棧頂指向下一個元素
nodeTop = nodeTop.next;
size--;
return old.data;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[ ");
for(Node<Object> node = nodeTop; node != null; node = node.next){
sb.append(node.data.toString() + " ");
}
return sb.toString()+"]";
}
public static void main(String[] args) {
// Stack stack = new Stack(1);
// stack.objPush("abc");
// stack.objPush(123);
// stack.objPush("de");
// stack.objPush("cd");
// stack.objPush("er");
// stack.objPush("hello");
// stack.objPush(666);
// stack.objPush(545);
// stack.objPush("word");
// while (stack.top != -1){
// System.out.println(stack.objPop());
// }
// System.out.println(stack.peekTop());
Stack stack = new Stack();
stack.nodePush("111");
stack.nodePush("222");
stack.nodePush("aaa");
stack.nodePush("bbb");
System.out.println(stack);
while (stack.size > 1)
System.out.println(stack.nodePop());
System.out.println(stack);
}
}
複製代碼
O(n)
O(n)
O(1)
O(1)
public class Queue {
/*** * 1.單向隊列(Queue):只能在一端插入數據,另外一端刪除數據。 * 2.雙向隊列(Deque):每一端均可以進行插入數據和刪除數據操做。 * * 與棧不一樣的是,隊列中的數據不老是從數組的0下標開始的 * 選擇的作法是移動隊頭和隊尾的指針。 * 爲了不隊列不滿卻不能插入新的數據,咱們可讓隊尾指針繞回到數組開始的位置,這也稱爲「循環隊列」。 * */
// 單向循環隊列,順序存儲結構實現
private Object[] objQueue;
//隊列大小
private int maxSize;
//頂部
private int top;
//底部
private int bottom;
//實際元素
private int item;
public Queue(int size) {
maxSize = size;
objQueue = new Object[maxSize];
top = 0;
bottom = -1;
item = 0;
}
/** * 入隊 * @param obj */
public void add(Object obj){
if(item == maxSize){
throw new RuntimeException(obj+" add error, queue is full");
}
//循環隊列,首尾結合,下標控制隊首和隊尾位置
if(bottom == maxSize-1){
bottom = -1;
}
objQueue[++bottom] = obj;
item++;
}
/** * 出對 * @return */
public Object out(){
if(item == 0){
throw new RuntimeException("queue is null");
}
Object obj = objQueue[top];
//聲明原頂棧能夠回收空間(GC)
objQueue[top] = null;
top++;
//重置下標
if(top == maxSize){
top = 0;
}
item--;
return obj;
}
//鏈式存儲結構實現
private class NodeQueue<Object>{
private Object data;
private NodeQueue next;
public NodeQueue(Object data, NodeQueue next) {
this.data = data;
this.next = next;
}
}
//隊列頭 出
private NodeQueue queueTop;
//隊列尾 進
private NodeQueue queueBottom;
//隊列大小
private int size;
public Queue() {
queueTop = null;
queueBottom = null;
size = 0;
}
/** * 入隊 * @param obj */
public void addNodeQueue(Object obj){
if(size == 0){
queueTop = new NodeQueue(obj,null);
//指向同一存儲地址
queueBottom = queueTop;
}else{
NodeQueue<Object> nodeQueue = new NodeQueue(obj,null);
//讓尾節點的next指向新增的節點
queueBottom.next = nodeQueue;
//以新節點做爲尾節點
queueBottom = nodeQueue;
}
size ++;
}
/** * 出隊 * @return */
public Object removeNodeQueue(){
if(size == 0){
throw new RuntimeException("queue is null");
}
NodeQueue nodeQueue = queueTop;
queueTop = queueTop.next;
//聲明原隊列頭next能夠回收空間(GC)
nodeQueue.next = null;
size--;
return nodeQueue.data;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{ ");
for(NodeQueue nodeQueue = queueTop ; nodeQueue != null ; nodeQueue = nodeQueue.next){
sb.append(nodeQueue.data.toString()+" ");
}
return sb.toString()+"}";
}
public static void main(String[] args) {
Queue queue = new Queue();
queue.addNodeQueue("123");
queue.addNodeQueue("abc");
queue.addNodeQueue("ddd");
System.out.println(queue);
queue.removeNodeQueue();
System.out.println(queue);
queue.removeNodeQueue();
queue.removeNodeQueue();
System.out.println(queue);
}
}
複製代碼
單向鏈表
: 鏈表中的節點僅指向下一個節點,而且最後一個節點指向空。雙向鏈表
: 其中每一個節點具備兩個指針 p、n,使得 p 指向先前節點而且 n 指向下一個節點;最後一個節點的 n 指針指向 null。循環鏈表
:每一個節點指向下一個節點而且最後一個節點指向第一個節點的鏈表。O(n)
O(n)
O(1)
O(1)
public class LinkedList {
/*** * 鏈表一般由一連串節點組成,每一個節點包含任意的實例數據(data fields)和一或兩個用來指向上一個/或下一個節點的位置的連接("links") */
private Node head; //鏈表頭
private Node tail; //鏈表尾
private int size; //節點數
/** * 雙端鏈表 */
public class Node{
private Object data;
private Node prev; //上一個
private Node next; //下一個
public Node(Object data) {
this.data = data;
}
}
public LinkedList() {
this.head = null;
this.tail = null;
this.size = 0;
}
/** * 向鏈表頭添加數據 * @param object */
public void addHead(Object object){
Node node = new Node(object);
if(size == 0){
head = node;
tail = node;
size++;
}else{
head.prev = node;
node.next = head;
head = node;
size++;
}
}
/** * 刪除頭 */
public void deleteHead(){
//頭部指向下一個,prev值爲null則說明是鏈表的頭部
if(size != 0){
head.prev = null;
head = head.next;
size--;
}
}
/** *向鏈表尾添加數據 * @param object */
public void addTail(Object object){
Node node = new Node(object);
if(size == 0){
head = node;
tail = node;
size++;
}else{
node.prev = tail;
tail.next = node;
tail = node;
size++;
}
}
/** * 刪除尾部 */
public void deleteTail(){
//尾部指向上一個,next值爲null則說明是鏈表的尾部
if(size != 0){
tail.next = null;
tail = tail.prev;
size--;
}
}
/** * 顯示數據 */
public void display(){
Node node = head;
while (size > 0){
System.out.print("["+node.data+"->");
node = node.next;
size--;
}
}
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.addHead("123");
// linkedList.addHead("abc");
// linkedList.addHead("%$$");
// linkedList.addTail("+_+");
// linkedList.addTail("hello");
linkedList.addTail("word");
linkedList.deleteHead();
linkedList.deleteTail();
linkedList.display();
}
}
複製代碼
二叉樹(由一個根結點和兩棵互不相交的、分別稱爲根結點的左子樹和右子樹組成) 二叉樹便是每一個節點最多包含左子節點與右子節點這兩個節點的樹形數據結構。
滿二叉樹
: 樹中的每一個節點僅包含 0 或 2 個節點。完美二叉樹(Perfect Binary Tree)
: 二叉樹中的每一個葉節點都擁有兩個子節點,而且具備相同的高度。徹底二叉樹
: 除最後一層外,每一層上的結點數均達到最大值;在最後一層上只缺乏右邊的若干結點。
O(1)
O(log(n))
O(log(n))
鏈地址法(Separate Chaining)
: 鏈地址法中,每一個桶是相互獨立的,包含了一系列索引的列表。搜索操做的時間複雜度便是搜索桶的時間(固定時間)與遍歷列表的時間之和。開地址法(Open Addressing)
: 在開地址法中,當插入新值時,會判斷該值對應的哈希桶是否存在,若是存在則根據某種算法依次選擇下一個可能的位置,直到找到一個還沒有被佔用的地址。所謂開地址法也是指某個元素的位置並不永遠由其哈希值決定。無向圖(Undirected Graph)
: 無向圖具備對稱的鄰接矩陣,所以若是存在某條從節點 u 到節點 v 的邊,反之從 v 到 u 的邊也存在。有向圖(Directed Graph)
: 有向圖的鄰接矩陣是非對稱的,即若是存在從 u 到 v 的邊並不意味着必定存在從 v 到 u 的邊。排序
O(nlog(n))
O(n^2)
O(nlog(n))
O(nlog(n))
O(nlog(n))
O(nlog(n))
Ω(n + k)
O(n^2)
Θ(n + k)
Ω(nk)
O(nk)
Θ(nk)
O(|V| + |E|)
O(|V| + |E|)
O(|V| + |E|)
O(|V|^2)
Bellman-Ford 算法
**是在帶權圖中計算從單一源點出發到其餘節點的最短路徑的算法。O(|E|)
O(|V||E|)
Floyd-Warshall 算法
可以用於在無環帶權圖中尋找任意節點的最短路徑。O(|V|^3)
O(|V|^3)
O(|V|^3)
Prim 算法
**是用於在帶權無向圖中計算最小生成樹的貪婪算法。換言之,Prim 算法可以在圖中抽取出鏈接全部節點的邊的最小代價子集。O(|V|^2)
Kruskal 算法
**一樣是計算圖的最小生成樹的算法,與 Prim 的區別在於並不須要圖是連通的。O(|E|log|V|)
s & (1 << k)
s |= (1 << k)
s &= ~(1 << k)
s ^= ~(1 << k)
s << n
s >> n
s & t
s | t
s & ~t
x = x ^ y ^ (y = x)
s & (-s)
~s & (s + 1)
x ^= y; y ^= x; x ^= y;