簡述
在程式設計過程中,通常會遇到的一個問題就是,效能瓶頸。很多時候考慮的都是怎麼去做橫向擴充套件,但偏偏忽略掉了最基本的問題就是系統是否真的已經達到了瓶頸?
效能瓶頸通常的表象是資源消耗過多外部處理系統的效能不足;或者資源消耗不多但程式的響應速度卻仍達不到要求。
而調優的方式就是
尋找過度消耗資源的程式碼 和 尋找未充分使用資源但程式執行慢的原因和程式碼。
基礎決定高度
就拿汽車來比較,通常不懂變速箱、發動機的原理但也是能開車,同樣一個不懂資料結構和演算法的人也能程式設計。很多人覺得基本的資料結構及操作已經在高階語言中封裝,都可以直接呼叫庫函式,那麼到底有沒有必要好好學習資料結構?
資料結構+演算法=程式
通常在程式中,遇到一個實際問題,充分利用資料結構,將資料及其之間的關係有效地儲存在計算機中,然後選擇合適的演算法策略,並用程式高效實現,這才是提高程式效能的主要方式。
- 如何有效地儲存資料,不同的資料結構產生什麼樣的演算法複雜性,有沒有更好的儲存方法提高演算法的效率?
如果沒有具備這塊相應的知識,怎麼完成上述的實現?如果脫離了原有的呼叫,怎麼完成程式的高效實現?而所有的應用實現都依賴於基礎,基礎就是資料結構和演算法。瞭解這塊,才能做到無懼任何技術的演變。所有說基礎決定高度!
基本的概念
資料結構表示資料在計算機中的儲存和組織形式,主要描述資料元素之間和位置關係等。選擇適當的資料結構可以提高計算機程式的執行效率(時間複雜度)和儲存效率(空間複雜度)。
資料結構的三種層次
:
- 邏輯結構--抽象層: 主要描述的是資料元素之間的邏輯關係
集合結構(集)
: 所有的元素都屬於一個總體,除了同屬於一個集合外沒有其他關係。集合結構不強調元素之間的任何關聯性。線性結構(表)
: 資料元素之間具有一對一的前後關係。結構中必須存在唯一的首元素和唯一的尾元素。樹形結構(樹)
: 資料元素之間一對多的關係網狀結構(圖)
: 圖狀結構或網狀結構 結構中的資料元素之間存在多對多的關係
- 物理結構--儲存層: 主要描述的是資料元素之間的位置關係
-
順序結構
: 順序結構就是使用一組連續的儲存單元依次儲存邏輯上相鄰的各個元素
優點
: 只需要申請存放資料本身的記憶體空間即可,支援下標訪問,也可以實現隨機訪問。
缺點
: 必須靜態分配連續空間,記憶體空間的利用率比較低。插入或刪除可能需要移動大量元素,效率比較低 -
鏈式結構
: 鏈式儲存結構不使用連續的儲存空間存放結構的元素,而是為每一個元素構造一個節點。節點中除了存放資料本身以外,還需要存放指向下一個節點的指標。
優點
: 不採用連續的儲存空間導致記憶體空間利用率比較高,克服順序儲存結構中預知元素個數的缺點 插入或刪除元素時,不需要移動大量的元素。
缺點
: 需要額外的空間來表達資料之間的邏輯關係, 不支援下標訪問和隨機訪問。 -
索引結構
: 除建立儲存節點資訊外,還建立附加的索引表來標節點的地址。索引表由若干索引項組成。
優點
: 是用節點的索引號來確定結點儲存地址,檢索速度塊
`缺點: 增加了附加的索引表,會佔用較多的儲存空間。 -
雜湊結構
: 由節點的關鍵碼值決定節點的儲存地址。雜湊技術除了可以用於查詢外,還可以用於儲存。
優點
: 雜湊是陣列儲存方式的一種發展,採用儲存陣列中內容的部分元素作為對映函式的輸入,對映函式的輸出就是儲存資料的位置, 相比陣列,雜湊的資料訪問速度要高於陣列
缺點
: 不支援排序,一般比用線性表儲存需要更多的空間,並且記錄的關鍵字不能重複。
- 運算結構--實現層: 主要描述的是如何實現資料結構
- 分配資源,建立結構,釋放資源
- 插入和刪除
- 獲取和遍歷
- 修改和排序
資料結構比較
常用的資料結構:
資料結構選擇:
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倍,歸併排序就是線性對數級的演算法
Array
在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();
}
}
複製程式碼
Stack
- 棧(stack)又稱為堆疊或堆疊,棧作為一種資料結構,它按照先進後出的原則儲存資料,先進入的資料被壓入棧底,最後的資料在棧頂
- java中Stack是Vector的一個子類,只定義了預設建構函式,用來建立一個空棧。
- 棧是元素的集合,其包含了兩個基本操作:push 操作可以用於將元素壓入棧,pop 操作可以將棧頂元素移除。
- 遵循後入先出(LIFO)原則。
- 時間複雜度:
- 索引:
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);
}
}
複製程式碼
Queue
- 佇列是元素的集合,其包含了兩個基本操作:enqueue 操作可以用於將元素插入到佇列中,而 dequeue 操作則是將元素從佇列中移除。
- 遵循先入先出原則 (FIFO)。
- 時間複雜度:
- 索引:
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);
}
}
複製程式碼
Linked List
- 連結串列即是由節點(Node)組成的線性集合,每個節點可以利用指標指向其他節點。它是一種包含了多個節點的、能夠用於表示序列的資料結構。
單向連結串列
: 連結串列中的節點僅指向下一個節點,並且最後一個節點指向空。雙向連結串列
: 其中每個節點具有兩個指標 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();
}
}
複製程式碼
Binary Tree
二叉樹(由一個根結點和兩棵互不相交的、分別稱為根結點的左子樹和右子樹組成) 二叉樹即是每個節點最多包含左子節點與右子節點這兩個節點的樹形資料結構。
滿二叉樹
: 樹中的每個節點僅包含 0 或 2 個節點。完美二叉樹(Perfect Binary Tree)
: 二叉樹中的每個葉節點都擁有兩個子節點,並且具有相同的高度。完全二叉樹
: 除最後一層外,每一層上的結點數均達到最大值;在最後一層上只缺少右邊的若干結點。 紅黑樹(每節點五元素,顏色(如果一個節點是紅的,則它的兩個兒子都是黑的)、鍵值、左子節點、右位元組的、父節點)
Heap
- 堆(也被稱為優先佇列(佇列+排序規則),圖一最大堆,圖二最小堆)
- 堆是一種特殊的基於樹的滿足某些特性的資料結構,整個堆中的所有父子節點的鍵值都會滿足相同的排序條件。堆更準確地可以分為最大堆與最小堆,在最大堆中,父節點的鍵值永遠大於或者等於子節點的值,並且整個堆中的最大值儲存於根節點;而最小堆中,父節點的鍵值永遠小於或者等於其子節點的鍵值,並且整個堆中的最小值儲存於根節點。
- 時間複雜度:
- 訪問最大值 / 最小值:
O(1)
- 插入:
O(log(n))
- 移除最大值 / 最小值:
O(log(n))
- 訪問最大值 / 最小值:
Hashing
- 雜湊能夠將任意長度的資料對映到固定長度的資料。雜湊函式返回的即是雜湊值,如果兩個不同的鍵得到相同的雜湊值,即將這種現象稱為碰撞。
- Hash Map: Hash Map 是一種能夠建立起鍵與值之間關係的資料結構,Hash Map 能夠使用雜湊函式將鍵轉化為桶或者槽中的下標,從而優化對於目標值的搜尋速度。
- 碰撞解決
鏈地址法(Separate Chaining)
: 鏈地址法中,每個桶是相互獨立的,包含了一系列索引的列表。搜尋操作的時間複雜度即是搜尋桶的時間(固定時間)與遍歷列表的時間之和。開地址法(Open Addressing)
: 在開地址法中,當插入新值時,會判斷該值對應的雜湊桶是否存在,如果存在則根據某種演算法依次選擇下一個可能的位置,直到找到一個尚未被佔用的地址。所謂開地址法也是指某個元素的位置並不永遠由其雜湊值決定。
Graph
- 圖是一種資料元素間為多對多關係的資料結構,加上一組基本操作構成的抽象資料型別。
無向圖(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|)
拓撲排序
- 拓撲排序是對於有向圖節點的線性排序,如果存在某條從 u 到 v 的邊,則認為 u 的下標先於 v。
- 時間複雜度:
O(|V| + |E|)
Dijkstra 演算法
- Dijkstra 演算法 用於計算有向圖中單源最短路徑問題。
- 時間複雜度:
O(|V|^2)
Bellman-Ford 演算法
- **
Bellman-Ford 演算法
**是在帶權圖中計算從單一源點出發到其他節點的最短路徑的演算法。 - 儘管演算法複雜度大於 Dijkstra 演算法,但是它適用於包含了負值邊的圖。
- 時間複雜度:
- 最優時間:
O(|E|)
- 最壞時間:
O(|V||E|)
- 最優時間:
Floyd-Warshall 演算法
Floyd-Warshall 演算法
能夠用於在無環帶權圖中尋找任意節點的最短路徑。- 時間複雜度:
- 最優時間:
O(|V|^3)
- 最壞時間:
O(|V|^3)
- 平均時間:
O(|V|^3)
- 最優時間:
Prim 演算法
- **
Prim 演算法
**是用於在帶權無向圖中計算最小生成樹的貪婪演算法。換言之,Prim 演算法能夠在圖中抽取出連線所有節點的邊的最小代價子集。 - 時間複雜度:
O(|V|^2)
Kruskal 演算法
- **
Kruskal 演算法
**同樣是計算圖的最小生成樹的演算法,與 Prim 的區別在於並不需要圖是連通的。 - 時間複雜度:
O(|E|log|V|)
位運算
- 位運算即是在位級別進行操作的技術,合適的位運算能夠幫助我們得到更快地運算速度與更小的記憶體使用。
- 測試第 k 位:
s & (1 << k)
- 設定第 k 位:
s |= (1 << k)
- 第 k 位置零:
s &= ~(1 << k)
- 切換第 k 位值:
s ^= ~(1 << k)
- 乘以 2n:
s << n
- 除以 2n:
s >> n
- 交集:
s & t
- 並集:
s | t
- 減法:
s & ~t
- 交換
x = x ^ y ^ (y = x)
- 取出最小非 0 位(Extract lowest set bit):
s & (-s)
- 取出最小 0 位(Extract lowest unset bit):
~s & (s + 1)
- 交換值:
x ^= y; y ^= x; x ^= y;