日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ReviewForJob——算法设计技巧(贪婪算法+分治算法+动态规划)

發布時間:2023/12/3 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ReviewForJob——算法设计技巧(贪婪算法+分治算法+动态规划) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

【0】README

1)本文旨在介紹算法設計技巧包括 貪婪算法、分治算法、動態規劃 以及相關的荔枝等;


【1】貪婪算法

1)intro: 貪婪算法是分階段進行的,在每個階段,可以認為所做的決定是最好的,而不考慮將來的后果;

2)我們已經看到過的貪婪算法有:

alg1)迪杰斯特拉算法:該算法計算單源(起點固定)有權最短路徑,使用到了 二叉堆優先來選取權值最小的鄰接頂點,因為每次都選擇權值最小的鄰接頂點作為輸出,當然 是最好的決定了,滿足貪婪算法的特性;

alg2)普利姆算法:該算法用于在無向有權圖中尋找 最小生成樹(該樹是一個連通圖,且圖中所含邊的權值最小),普利姆算法是基于迪杰斯特拉算法的,且在普利姆算法過程中,有且只有一個連通圖;

alg3)克魯斯卡爾算法:同普利姆算法一樣,該算法用于在無向有權圖中尋找最小生成樹;與普利姆算法不同的是,該算法連續地按照最小的全選擇邊,并且當所選的邊不產生圈時就把它作為取定的邊;


【1.1】貪婪算法的經典荔枝——(找零問題):說某商店的硬幣只有 1角,5角,10角,12角;

1)problem+solutions:

1.1)problem:那現在如果要找15角的零錢,且要求 硬幣數量最少,應該怎么找?

1.2)solutions:方法一:按照貪婪算法的定義,則首先選擇一個12角,3個1角 ,總硬幣數量為4個;方法二:其實我們也可以選擇一個10角 和 一個5角來找零錢,沒必要一開始就選擇幣值最大的硬幣;

2)總結:從上面找零錢的貪婪算法荔枝就知道,貪婪算法并不總能給出最優的解決方案;


【1.2】貪婪算法的荔枝——Huffman編碼?Huffman 編碼源碼

0)哈夫曼編碼的應用: 文件壓縮,用0,1 代碼表示文件中的字母;要知道一般的字符編碼都是等長的,見下圖:

(干貨——哈夫曼編碼的應用很重要,因為偉哥說他電話面試的時候, ali 的 項目經理有問到,這也是為什么我把它寫在了 開頭的原因)


1)哈夫曼算法描述:該算法是對一個由樹組成的森林進行的(因為我可以把一個頂點看做一個樹);

step1)起初,每個節點都看做一顆樹,該樹(根)的權值等于其樹葉的頻率和;(頻率?就是指一個字母出現的次數/總的 字母出現次數,因為為了減少編碼長度,頻率小的應該盡可能地遠離樹根,頻率大的應該盡可能地接近樹根)

step2)任意選取最小權的兩顆樹 t1 和 t2,并任意形成以 t1 和 t2 為子樹的新樹,將這樣的過程進行(節點數-1)次,即 對 step2)循環執行(節點數-1)次;(這里也符合貪婪算法的定義,即每次哈夫曼算法都要選擇權值最小的樹合并以建立哈夫曼編碼樹,既然每次都選擇權值最小的樹,那么在每個階段,這個決定是最好的)

step3)最終的樹就是 最優哈夫曼編碼樹,遍歷該哈夫曼樹,左邊上0,右邊上1 即可;

2)看了描述,想必你也猜到了,哈夫曼算法用到了二叉堆優先隊列(小根堆),因為它要選取權值最小的樹;(堆節點使用了 結構體指針類型,下面會講到為什么會選擇結構體指針類型而不選結構體類型或 int基本類型作為堆節點類型)

3)將哈夫曼算法 與 二叉堆優先隊列結合起來 闡述源碼的實現steps:

補充)我們先看節點類型

#define ElementNum 7// 堆節點類型是 結構體指針類型 而不是單純的 int類型. #define ElementType HeapNode // 二叉堆的堆節點類型為 結構體指針類型. struct HeapNode; typedef struct HeapNode* HeapNode; struct HeapNode {int value; // 字符出現的頻率char flag; // 字符標識HeapNode left;HeapNode right; };// 二叉堆的結構體. struct BinaryHeap; typedef struct BinaryHeap *BinaryHeap; struct BinaryHeap {int capacity;int size; HeapNode* array; // 堆節點類型是結構體指針. 而優先隊列是結構體指針數組. };struct HuffmanCode; typedef struct HuffmanCode* HuffmanCode; struct HuffmanCode {char flag;char code[ElementNum-2+1]; // 因為還有 '\0'// 為什么 code的長度是ElementNum-2,因為 如元素個數是7,其最大高度為5. };

step1)將 森林中的樹標識(字符)和樹頻率 插入堆;

char flag[ElementNum] = {'a', 'e', 'i', 's', 't', 'p', 'n'};int frequency[ElementNum] = {10, 15, 12, 3, 4, 13, 1};ElementType root, temp1, temp2;int i;BinaryHeap heap;// step1: 建堆.heap = initBinaryHeap(ElementNum+1); // 因為0號下標不用.if(heap==NULL){return ;}for(i=0; i<ElementNum; i++){ insert(createHeapNode(frequency[i], flag[i]), heap);}printBinaryHeap(heap);// step1: over.

step2)只要堆不為空,連續兩次刪除堆中最小元素已選擇權值最小的樹,并構建哈夫曼樹;構建后再次插入到小根堆中;繼續step2,直到堆為空,退出step2;

// step2: 依次刪除堆中最小元素 以 構建哈夫曼樹. while(!isEmpty(heap)){temp1 = deleteMin(heap);if(!isEmpty(heap)){temp2 = deleteMin(heap);root = createHeapNode(temp1->value+temp2->value, ' ');root->right = temp1; // 優先發右邊.root->left = temp2; // 合并后,其根還要插入堆.insert(root, heap); } }// step2 over. step3)遍歷該哈夫曼樹以建立各個字符對應的哈夫曼編碼,左邊上0,右邊上1,即可;(干貨——其實經過編碼,你會發現 哈夫曼樹的節點的left兒子為空的話,它的右兒子絕對為空,可能會被編碼帶來方便)

// step3 save huffman code. huffmanCodeRecursion(root, 0);// step3 over.// 記錄完 哈夫曼編碼后,打印編碼效果.for(i=0; i<ElementNum; i++){printf("\n code[%c] = %s", codes[i].flag, codes[i].code);}// (遞歸實現)記錄每個字符的哈夫曼編碼;root == 哈夫曼樹根, depth == 樹的深度, 從0開始取. void huffmanCodeRecursion(HeapNode root, int depth) { if(root->left){code[depth] = '0';code[depth+1] = '\0';huffmanCodeRecursion(root->left, depth+1);}if(root->right){code[depth] = '1';code[depth+1] = '\0';huffmanCodeRecursion(root->right, depth+1);} else{ codes[counter].flag = root->flag;copyCodes(code, codes[counter++].code); // printf("%s\n", code); // 取消本行注釋可以調試程序.} }

4)測試用例如下:

void main() {char flag[ElementNum] = {'a', 'e', 'i', 's', 't', 'p', 'n'};int frequency[ElementNum] = {10, 15, 12, 3, 4, 13, 1};ElementType root, temp1, temp2;int i;BinaryHeap heap;// step1: 建堆.heap = initBinaryHeap(ElementNum+1); // 因為0號下標不用.if(heap==NULL){return ;}for(i=0; i<ElementNum; i++){ insert(createHeapNode(frequency[i], flag[i]), heap);}printBinaryHeap(heap);// step1: over.// step2: 依次刪除堆中最小元素 以 構建哈夫曼樹. while(!isEmpty(heap)){temp1 = deleteMin(heap);if(!isEmpty(heap)){temp2 = deleteMin(heap);root = createHeapNode(temp1->value+temp2->value, ' ');root->right = temp1; // 優先發右邊.root->left = temp2; // 合并后,其根還要插入堆.insert(root, heap); } }// step2 over.printf("\n === nodes in huffman tree are as follows.===\n");printPreorder(root, 1);// step3 save huffman code. huffmanCodeRecursion(root, 0);// step3 over.// 記錄完 哈夫曼編碼后,打印編碼效果.for(i=0; i<ElementNum; i++){printf("\n code[%c] = %s", codes[i].flag, codes[i].code);}printf("\n"); }

【1.3】近似裝箱問題?

1)intro:有兩種版本的裝箱問題:第一種是聯機裝箱問題,必須將每一件物品放入一個箱子后才處理下一件物品;第二種是脫機裝箱問題;

2)補充)聯機算法和脫機算法:

2.1)聯機算法:說聯機算法就好比 英語聽力考試(或口語考試),做完這道題,才能做下一題;

2.2)脫機算法:說脫機算法就好比 一般性考試,只需要在規定時間內完成即可,做題沒有先后順序;


【1.3.1】聯機裝箱算法

1)幾種聯機裝箱算法介紹

算法1)下項適合算法:效果最差,只要當前箱子無法盛放物品,就開辟一個新箱子;

算法2)首次適合算法: 從頭到尾掃描所有箱子,并把物品放入足夠盛下它的第一個箱子中。如沒有箱子可以盛放,再開辟新箱子;

算法3)最佳適合算法: 該方法不是把一項新物品放入所發現的能容納它的箱子,而是放到 所有箱子中能夠容納它的最滿箱子;

2)聯機算法的主要問題:在于將 大項物品裝箱困難,特別是當他們在輸入的晚期出現的時候。圍繞這個問題的自然方法是將各項物品排序,把最大的物品放在最先;這就要借鑒 脫機算法的idea了;


【1.3.2】脫機裝箱算法

1)intro: 脫機裝箱算法 說白了:就是將各項物品排序,吧最大的物品放在最前面;然后再進行裝箱;可能你也猜到了,因為 脫機裝箱問題 需要吧 物品排序,然后選擇 最大的物品;這就要用到 二叉堆優先隊列了(二叉堆是大根堆);

2)幾種脫機裝箱算法介紹和源碼實現

補充)個人覺得,碰到一個問題,要尋找解決問題的算法,首先要確定數據類型,即結構體的成員,這個很重要,會省去很多不必要的麻煩;下面看 裝箱問題的結構體類型。

// 堆節點類型為 int. #define ElementType int #define Error(str) printf("\n error: %s \n",str) #define ElementNum 7 #define SUM 10 // 箱子的最大容量是10struct BinaryHeap; typedef struct BinaryHeap *BinaryHeap; struct BinaryHeap {int capacity;int size; ElementType *array; };// 貨物(箱子)結構體. struct Good; typedef struct Good* Good; typedef struct Good* Box; struct Good {int value; // 這里的value 對于貨物 指代 貨物重量.// 這里的value 對于箱子 指代 箱子的剩余容量.Good next; };// 定義一個倉庫結構體, 包含多個箱子. struct Warehouse; // 倉庫可以看多箱子數組. typedef struct Warehouse* Warehouse; struct Warehouse {int capacity;int size;Box* array; // 多個箱子. };

算法1)首次適合遞減算法:物品排序后,應用首次適合算法 得到?首次適合遞減算法;首次適合遞減算法源碼

step1)基于物品的重量建立大根堆;

// step1: 建立大根堆.heap = initBinaryHeap(ElementNum+1); // 堆的下標0的元素不用,這是老生常談了.if(heap==NULL){return ;}for(i=0; i<ElementNum; i++){ insert(goods[i], heap);}//step1 over.

step2)首次適合遞減算法

// step2: 應用首次適合遞減算法.printf("\n\t === review for first fit decreasing alg ===\n");first_fit_decreasing(heap, warehouse);

// 首次適合遞減算法.(把物品放入能夠盛下它的第一個箱子中) void first_fit_decreasing(BinaryHeap heap, struct Warehouse warehouse) { int i, weight; Good temp;Box* array = warehouse.array; // step2: 刪除大根堆中最大元素.用刪除的元素 添加到 箱子中.while(!isEmpty(heap)){i=0;weight = deleteMin(heap); while(weight > array[i++]->value);if(array[i-1]->value == SUM){warehouse.size++;}temp = array[i-1]; // 因為上面的 while循環多加了一個1.while(temp->next){ temp = temp->next;} temp->next = createGood(weight); // 因為i 自加了一次, 所以要減1.if(temp->next) // 如果內存分配成功.{array[i-1]->value -= weight;}}printBoxes(warehouse); }

測試用例如下:void main() {int i;int goods[] = {2, 5, 4, 7, 1, 3, 8};BinaryHeap heap;struct Warehouse warehouse;initWarehouse(&warehouse, ElementNum); // 初始化倉庫(箱子數組);// step1: 建立大根堆.heap = initBinaryHeap(ElementNum+1); // 堆的下標0的元素不用,這是老生常談了.if(heap==NULL){return ;}for(i=0; i<ElementNum; i++){ insert(goods[i], heap);}//step1 over.printBinaryHeap(heap);// step2: 應用首次適合遞減算法.printf("\n\t === review for first fit decreasing alg ===\n");first_fit_decreasing(heap, warehouse); }

算法2)最佳適合遞減算法:物品排序后,應用最佳適合算法 得到 最佳適合遞減算法;最佳的意思就是,在可以存放物品的前提下,物品被存放后,箱子的剩余容量最小的為最佳,或存放后箱子最滿的為最佳;(數據類型同 首次適合遞減算法)?最佳適合遞減算法源碼

step1)同樣,建立大根堆

// step1: 建立大根堆.heap = initBinaryHeap(ElementNum+1); // 堆的下標0的元素不用,這是老生常談了.if(heap==NULL){return ;}for(i=0; i<ElementNum; i++){ insert(goods[i], heap);}//step1 over.step2)應用最佳適合遞減算法

// 應用最佳適合遞減算法.(注意是最佳不是首次適合遞減算法)printf("\n\t === review for best fit decreasing alg ===\n");best_fit_decreasing(heap, warehouse);// 最佳適合遞減算法.(首先遍歷箱子,找出該貨物存放后,對應箱子的剩余容量最小的箱子) void best_fit_decreasing(BinaryHeap heap, struct Warehouse warehouse) { int i, weight, diff; Box temp;Box* array = warehouse.array; int minIndex=-1, minValue = SUM;// step2: 刪除大根堆中最大元素.用刪除的元素 添加到 箱子中.while(!isEmpty(heap)){ weight = deleteMin(heap);for(i=0; i<warehouse.size;i++) // 遍歷倉庫中的所有箱子.{ diff = array[i]->value - weight; // diff 此刻表示差值.if(diff>=0 && diff <= minValue) // key if condition.{minValue = diff;minIndex = i;if(diff==0) //當差值等于0時,表示最佳的.{break;}}} // 所有箱子遍歷over.if(minValue == SUM) // 沒有找到適合的箱子,需要開辟一個新箱子(size++).{minIndex = i;warehouse.size++;}// 裝貨入箱.temp = array[minIndex]; while(temp->next){ temp = temp->next;} temp->next = createGood(weight); if(temp->next) // 如果內存分配成功.{array[minIndex]->value -= weight;} // 裝貨over.//printBoxes(warehouse); // 取消這行注釋用于調試.}printBoxes(warehouse); }測試用例void main() {int i;int goods[] = {2, 5, 4, 7, 1, 3, 8};BinaryHeap heap;struct Warehouse warehouse;initWarehouse(&warehouse, ElementNum); // 初始化倉庫(箱子數組);// step1: 建立大根堆.heap = initBinaryHeap(ElementNum+1); // 堆的下標0的元素不用,這是老生常談了.if(heap==NULL){return ;}for(i=0; i<ElementNum; i++){ insert(goods[i], heap);}//step1 over.printBinaryHeap(heap);// 應用最佳適合遞減算法.(注意是最佳不是首次適合遞減算法)printf("\n\t === review for best fit decreasing alg ===\n");best_fit_decreasing(heap, warehouse); }

Attention)寫代碼,要先寫 首次適合遞減alg 的源碼,因為它要簡單些,然后再寫 最佳適合遞減alg, 且 最佳適合遞減算法 是基于 首次適合遞減算法的;


【2】分治算法

0)intro:對于分治算法,我們只以 歸并排序進行講解,它是理解分治算法的最佳荔枝,沒有之一;

1)我們已經看過的分治算法有:最大子序列和問題, 歸并排序和快速排序;時間復雜度都是 O(NlogN);

2)分治算法分為 分和治:

2.1)分:將一個大問題分為兩個大致相同的小問題;

2.2)治:將兩個小問題的解合并,得到整個問題的解;


【2.1】看個荔枝:歸并排序 ?歸并排序源碼

1)歸并排序的思想:基于分治思想,是遞歸算法一個很好的荔枝,是用于分析遞歸例程方法的經典荔枝;

2)歸并排序中基本操作:是 合并兩個已排序 的表。因為兩個表已經排序,所以若將輸出放到 第3個表中,則該算法可以通過對輸入數據一趟排序來完成;

3)歸并排序的steps:

step1)后序遍歷raw 數組,依據 [left, center] 和 [center+1, right] 分割數組為兩個子數組;

step2)合并數組操作在 分割完后進行,后序遍歷的意思就是 非遞歸操作在遞歸之后進行;

// 對數組raw[left, right]進行歸并排序. // 歸并排序是合并兩個已排序的表,并吧合并結果存儲到 第三個數組temp中. void mergesort(ElementType* raw, ElementType* temp, int left, int right) {int center;if(left < right){center = (left + right) / 2;mergesort(raw, temp, left, center);mergesort(raw, temp, center + 1, right);mergeArray(raw, temp, left, right); // 合并已排序的兩個表[left,center] 和 [center+1,right]} }

step3)合并兩個已排序數組到第3個數組中;

step3.1)把數組raw[left,center]或raw[center+1,right]中的元素copy到 temp數組中.

step3.2)把沒有copy完的數組中的元素copy到 temp數組中, 要知道合并完后,肯定還有一個數組中的元素沒有 copy完,因為兩個數組的長度不等.

step3.3)現在temp 數組中的元素已經有序了,再把temp中的數組copy 回 raw數組中.

// 合并數組raw[left,center] 和 數組raw[center+1, right] 到 temp數組. void mergeArray(ElementType* raw ,ElementType* temp, int left, int right) {int center = (left+right)/2;int start1, start2; int end1, end2; int index;start1 = left; //第一個數組起點.end1 = center; //第一個數組終點.start2 = center+1; // 第二個數組起點.end2 = right; // 第二個數組終點.index = left; // 數組索引.// 依序合并2個數組到 第3個數組 分3個steps:// step1: 把數組raw[left,center]或raw[center+1,right]中的元素copy到 temp數組中.while(start1 <= end1 && start2 <= end2){if(raw[start1] < raw[start2]) // 誰小,誰就copy到 temp數組中.{temp[index++] = raw[start1++];}else{temp[index++] = raw[start2++];}} // step1 over.// 合并完后,肯定還有一個數組中的元素沒有 copy完,因為兩個數組的長度不等.// step2: 把沒有copy完的數組中的元素copy到 temp數組中;while(start1 <= end1){temp[index++] = raw[start1++];} while(start2 <= end2){temp[index++] = raw[start2++];} // step2 over.// step3: 現在temp 數組中的元素已經有序了,再把temp中的數組copy 回 raw數組中.for(index = left; index <= right ; index++){raw[index] = temp[index];} }

4)歸并排序的運行示意圖


對上圖的分析(Analysis):

A1)上面的關于歸并排序的steps的描述中這樣提到:歸并排序的基本操作是合并兩個已排序的表;結合上面的遞歸流程圖,我們發現,首先 從 (0,0) 和 (1,1)開始,他們都只表示一個元素,當然這兩個子數組是有序的;對其他葉子節點也是同樣的道理,接著就合并兩個子數組了;

A2)為什么歸并排序是基于分治算法思想的呢? 因為從上圖,我們可以看出,該歸并排序算法 首先將 數組分割成若干個子數組(分割終點是 left>=right)即,分割數組直到最后的子數組的元素個數為1為止,然后再對元素個數為1的兩個子數組進行合并,再對元素個數為2的兩個子數組進行合并...... 這不是分治這是什么?

5)測試用例

int main() { ElementType raw[] = {10, 100, 20, 90, 60, 50, 120, 140, 130, 5};int size = 10;ElementType *tempArray; tempArray = createArray(size);if(tempArray==NULL){return -1;}mergesort(raw, tempArray, 0, size-1); printf("\nexecute mergesort for {10, 100, 20, 90, 60, 50, 120, 140, 130, 5}\n"); printArray(raw, size); return 0; }

【3】動態規劃

1)intro:動態規劃是將問題分為一系列相互聯系的子問題,求解一個子問題可能要用到已經求解過的 子問題的解的 算法設計技巧;

2)problem+solutions:

2.1)problem:任何數學遞歸公司都是可以直接用遞歸算法計算的,但編譯器常常不能正確的對待遞歸算法,結果導致遞歸算法很低效;

2.2)solutions:我們給編譯器一些幫助,將遞歸算法重新寫成非遞歸算法(如將遞歸算法通過循環來代替),讓后者把那些子問題的答案系統地記錄在一個表內。利用這種方法的一個技巧叫做動態規劃;


【3.1】 用一個表代替遞歸

【3.1.1】荔枝1:斐波那契數列(Fibonacci Sequence)?斐波那契數列源碼

1)intro:?斐波那契數列(Fibonacci sequence):又稱黃金分割數列、因數學家列昂納多·斐波那契以兔子繁殖為例子而引入,故又稱為“兔子數列”,指的是這樣一個數列:0、1、1、2、3、5、8、13、21、34、……在數學上,斐波納契數列以如下被以遞歸的方法定義:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N*);


對上圖的分析(Analysis):?上述遞歸算法如此慢的原因在于算法模仿了遞歸。為了計算 FN, 存在一個對 FN-1 和 FN-2 的調用。 然而, 由于FN-1遞歸地對 FN-2 和 FN-3 進行調用, 因此存在兩個單獨的計算FN-2 的調用;如果我們試探整個算法,可以發現,FN-3 被計算了3次, FN-4 計算了5次, 而FN-5計算 了8次;如下圖所示, 冗余計算的增長是爆炸性的;


2)使用循環計算斐波那契數的線性算法(為什么使用循環會這么快? 這符合動態規劃的定義因為斐波那契數列 靠后的數列值的計算需要用到 靠前的數列值,把求每一個斐波那契數列值的看做一個子問題, 這符合動態規劃定義中的敘述?求解一個子問題可能要用到已經求解過的 子問題的解)?

#include <stdio.h>int elements[255];// 計算斐波那契數的線性算法 void fibonacci(int index) {if(index==0){elements[index]=0;}else if(index==1){elements[index]=1;}else // 還必須要這個 else 語句.{elements[index] = elements[index-1] + elements[index-2];} }void main() {int i;int size = 10; for(i=0; i<size; i++){fibonacci(i);printf("fibonacci(%d) = %d\n", i, elements[i]);} }



【3.1.2】荔枝2:求解遞歸關系?求解遞歸關系源碼

1)算法描述:我們想要檢查以下遞歸關系 C(N)=(2/N)∑C(i)+N,其中C(0)=1,i=0~i=N-1;

















2)以上遞歸實現的動態規劃idea的問題:這里,遞歸又做了重復性的工作。 3)通過循環以線性運行時間實現(這里的算法idea 也符合 動態規劃的定義) #include <stdio.h> #include <malloc.h> #define Error(str) printf("\n\terror: %s\n", str)void eval(int n) {double* array;int i; i = 0;array = (double*)malloc(sizeof(double) * (n+1));if(array == NULL){Error("failed eval, for out of space !");return ;}array[i] = 1.0;printf("\n\tarray[%d] = %8lf", i, array[i]);for(i=1; i<=n; i++){array[i] = 2 * array[i-1] / i + i;printf("\n\tarray[%d] = %8lf", i, array[i]);array[i] += array[i-1];} }int main() { eval(5);printf("\n");return 0; }

【3.2】所有點對最短路徑 1)要知道,計算點對最短路徑有兩種算法: 迪杰斯特拉算法 和 弗洛伊德算法; 1.1)迪杰斯特拉算法:用于在稀疏圖中計算從一個給定的起點到其他頂點的最短路徑;當然了 對于每一個頂點 都執行一次 迪杰斯特拉算法 與可以計算 所有點對最短路徑;?迪杰斯特拉算法 對 稀疏圖運行得更快; 1.2)佛洛依德算法:用于在稠密圖中 計算所有點對最短路徑;佛洛依德算法 對稠密圖運行得更快,因為它的循環更加緊湊;運行時間為 O(|V|^3); Attention)以下內容轉自:天勤計算機考研——數據結構高分筆記之佛洛依德算法(Floyd Alg)
2)Floyd alg 求解最短路徑的一般過程(steps):佛洛依德算法源碼

step1)設置兩個矩陣distance 和 path, 初始時將圖的鄰接矩陣賦值給distance, 將矩陣path中的全部元素賦值為-1; // step1: 鄰接矩陣 和 path矩陣int distance[ElementNum][ElementNum] = {{0, 5, MyMax, 7},{MyMax, 0, 4, 2},{3, 3, 0, 2},{MyMax, MyMax, 1, 0},};int path[ElementNum][ElementNum] = {{-1, -1, -1, -1},{-1, -1, -1, -1},{-1, -1, -1, -1},{-1, -1, -1, -1},}; step2)以頂點k 為中間頂點, k取0~n-1(n為圖中頂點個數), 對圖中所有頂點對{i, j}進行如下檢測與update: 如果distance[i][j] >distance[i][k]?+distance[k][j], 則將distance[i][j] 改為distance[i][k]+distance[k][j]的值, 將path[i][j] update 為 k, 否則什么也不做;(干貨——說白了,頂點k就做為一個中轉站,檢測通過中轉站k的路徑即 i -> k ->j ?是否比 不通過中轉站的路徑 i -> j 的路徑的訪問代價(權值)要小,如果小的話,更新 distance[i][j],且頂點k 作為 path[i][j] 的中轉點,否則不做任何處理) // 弗洛伊德算法用于 計算所有點對最短路徑. // distance 是鄰接矩陣, 而 path 是中轉點. void floyd_all_pairs(int distance[ElementNum][ElementNum], int path[ElementNum][ElementNum]) {int i, j, k;for(k=0; k<ElementNum; k++) // step2: 以頂點k 作為中轉站頂點.{for(i=0; i<ElementNum; i++){for(j=0; j<ElementNum; j++){if(distance[i][j] > distance[i][k] + distance[k][j]) // 經過中轉站k的訪問代價是否減小.{distance[i][j] = distance[i][k] + distance[k][j];path[i][j] = k;}}}} }

3)我們看個實際的 荔枝:





4)測試用例

int main() {// 鄰接矩陣int distance[ElementNum][ElementNum] = {{0, 5, MyMax, 7},{MyMax, 0, 4, 2},{3, 3, 0, 2},{MyMax, MyMax, 1, 0},};int path[ElementNum][ElementNum] = {{-1, -1, -1, -1},{-1, -1, -1, -1},{-1, -1, -1, -1},{-1, -1, -1, -1},};// 弗洛伊德算法用于 計算所有點對最短路徑.floyd_all_pairs(distance, path);// 打印 floyd 的 計算結果.printf("\n\t === distance array are as follows.===\n");printArray(distance);printf("\n\t === path array are as follows.===\n");printArray(path); }


補充)那為什么 佛洛依德算法也滿足 動態規劃的定義呢??

因為第k個 更新 distance 和 path 的階段 依賴于 第(k-1)個階段,即 第 k個階段和 第k-1 個階段是有聯系的。如 distance[3][4] = ∞, 而 distance[3][1] + distance[1][4]=10 那所以 distance[3][4]=10(更新),path[3][4]=1;接著又繼續更新,當k=4的時候,因為 distance[2][3]=∞,distance[2][4]=10,distance[2][4]+distance[3][4] = 20 而不是無窮大,即是 靠后的更新階段 依賴于靠前的更新階段的更新結果;

動態規劃總結)

C1)動態規劃是強大的算法設計技巧, 它給解提供了一個起點; C2)它基本上是首先求解一些更簡單的問題的分治算法的范例, 重要的區別在于這些簡單的問題不是原問題明確的分割。因為子問題反復被求解, 所以重要的是將它們的解記錄在一個表中而不是重新計算它們; C3)在某些情況下, 解可以被改進, 而在另一些情況下, 動態規劃方法則是所知道的最好的處理方法; C4)在某種意義上,如果你看出一個動態規劃問題,那么你就看出所有的問題;(碉堡 有木有)


總結

以上是生活随笔為你收集整理的ReviewForJob——算法设计技巧(贪婪算法+分治算法+动态规划)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。