矩陣連乘求解優化

来源:https://www.cnblogs.com/feicaixian/archive/2019/10/07/11627520.html
-Advertisement-
Play Games

前言 從旭東的博客 看到一篇博文:矩陣連乘最優結合 動態規劃求解,挺有意思的,這裡做個轉載【略改動】。 問題 矩陣乘法滿足結合律,但不滿足交換律。例如矩陣$A_{ab}, B_{bc}, C_{cd}$ 連乘得到矩陣$S_{ad}$ \[ S_{ad}=A_{ab} B_{bc} C_{cd} \] ...


前言

旭東的博客 看到一篇博文:矩陣連乘最優結合 動態規劃求解,挺有意思的,這裡做個轉載【略改動】。

問題

矩陣乘法滿足結合律,但不滿足交換律。例如矩陣$A_{ab}, B_{bc}, C_{cd}$ 連乘得到矩陣$S_{ad}$

\[ S_{ad}=A_{ab} B_{bc} C_{cd} \]

實際運算可以先將矩陣A和B相乘,S=(AB)C,也可以先將矩陣B和C相乘,S=A(BC) 。這兩種演算法的計算量是否一樣呢?

先看A×B的計算量。A×B是a行c列矩陣,共a×c個元素。b是A、B兩矩陣的乘法介面,積矩陣的每個元素都需經過b次乘法運算。那麼A×B需要a×b×c次乘法運算。

(AB)C乘法運算次數為:abc+acd=ac(b+d)

A(BC)乘法運算次數為:bcd+abd=bd(a+c)

由於a、b、c、d的取值是任意的, 所以ac(b+d)和bd(a+c)不可能保持相同,也即結合律可以用於矩陣乘法優化。那麼,對於連乘AiAi+1…Aj,如何找出最優的運算順序?

分析

這個問題長得像遞歸,但是用遞歸似乎不好做,因為子問題裡面有很多分支,那子問題的返回值是什麼,你返回哪個分支的返回值?而且就算能實現,肯定會對一些相同的子問題重覆計算。

由於計算n個矩陣的連乘,總是通過計算更少個數的矩陣連乘實現的,最終化歸到計算兩個矩陣的相乘,所以考慮從兩個矩陣相乘著手。這樣自底向上,就把問題解決了。當我們把所有的兩個矩陣相乘的乘法運算次數求出後,便可以去計算三個矩陣相乘的乘法運算次數。這時,三個矩陣的連乘相當於兩個矩陣相乘。這樣,所有的矩陣連乘都是兩個矩陣相乘,其乘法運算次數為兩個矩陣各自的乘法運算次數之和再加上兩個矩陣相乘的乘法運算次數。對於兩個矩陣A、B相乘的乘法運算次數$\beta$,上面A×B的示例已經講過了。進一步可以抽象為

\[\beta=行*介面*列\]

按上述需要,我們將矩陣連乘表達式視為一系列子式。每一個子式,由起點和跨度唯一決定。起點和跨度剛好構成兩個維度,於是可以用n階方陣索引子式,比如索引[1][2]表示起點為1跨度為2的子式。起點至多有n個,最小為0,最大為n-1。跨度至多n種,最小為0,最大為n-1。用兩個n階方陣分別存儲每個子式的最少計算次數及分割點。

C++提供了vector模板類,作為數組的一個替代品,我們用vector實現n階方陣。首先確定跨度,再確定起點,構成一個二級嵌套迴圈,通過起點的平移確定子式。再嵌套一級遍歷子式的分割點求出子式的最少計算次數。最後我們得到最大的子式,也就是連乘本身的最少運算次數及分割方案。

代碼如下:

 1 #include <iostream>
 2 #include <vector>
 3 #include <algorithm>
 4 #include <limits.h>
 5 #include <string>
 6 
 7 using namespace std;
 8 
 9 //計算矩陣的分割方案(基於向量)
10 int calculate_M(vector<vector<int> >&num, vector<pair<int, int> > &data, vector<vector<int> > &points) {
11     int matrixNum = data.size();
12     int span;//起止點間隔距離,表示跨度
13     int start;//起點
14     int end;//終點
15     int spiltPoint;//分割點
16     int multiplyTimes;//臨時存儲子式的乘法運算次數
17     for (span = 1; span < matrixNum; span++) {  ///間隔距離 相鄰矩陣的間隔距離為1
18         for (start = 0; start < matrixNum - span; start++) {  ///起點平移
19             //子式確立,下麵開始計算這個子式的最少乘法運算次數,初值先設為最大整數
20             end = start + span;
21             num[start][end] = INT_MAX;
22             for (spiltPoint = start; spiltPoint < end; spiltPoint++) {
23                 
24                 multiplyTimes = num[start][spiltPoint] + num[spiltPoint + 1][end] + data[start].first*data[spiltPoint].second*data[end].second;
25                 if (multiplyTimes < num[start][end]) {
26                     points[start][end] = spiltPoint;  ///記錄分割點
27                     num[start][end] = multiplyTimes;   ///記錄最少乘法次數
28                 }
29             }
30         }
31     }
32     return 0;
33 }
34 
35 //根據記錄的分割點,生成最後的矩陣相乘表達式
36 string make_result(vector<vector<int> > &points, int t1, int t2) {
37     if (t1 == t2)
38         return string(1, 'A' + t1);
39     int split = points[t1][t2];
40     return "(" + make_result(points, t1, split) + "*" + make_result(points, split + 1, t2) + ")";
41 }
42 
43 
44 int main()
45 {
46     int matrixNum=0;///連乘的矩陣個數
47     vector<pair<int, int>> data; ///存儲矩陣的尺寸
48 
49     //固定輸入
50     data.push_back(make_pair(10, 100));  //A
51     data.push_back(make_pair(100, 5));   //B
52     data.push_back(make_pair(5, 25));    //C
53     data.push_back(make_pair(25, 15));   //D
54     data.push_back(make_pair(15, 20));   //E
55     matrixNum = 5;
56 
57     //為記錄乘法次數和分割點的n階矩陣分配空間並初始化
58     vector<vector<int> > num(matrixNum, vector<int>(matrixNum));
59     vector<vector<int> > points(matrixNum, vector<int>(matrixNum));
60     for (int i = 0; i < matrixNum; i++) {
61         for (int j = 0; j < matrixNum; j++) {
62             points[i][j] = -1;
63             num[i][j] = 0;
64         }
65     }
66 
67     calculate_M(num, data, points);
68     cout << make_result(points, 0, matrixNum - 1) << "\t最少乘法次數為:" << num[0][matrixNum - 1] << endl;
69     
70     cin >> matrixNum;
71     return 0;
72 }
View Code

 代碼輸出為:

((A*B)*((C*D)*E))       最少乘法次數為:9375

 

如果從控制台輸入矩陣尺寸的話,可以輸入第一個矩陣的行數和各矩陣的列數。直接使用這個序列存儲矩陣尺寸。

  1 #include <iostream>
  2 #include <vector>
  3 #include <algorithm>
  4 #include <limits.h>
  5 #include <string>
  6 
  7 using namespace std;
  8 
  9 int* uncertainInput(int& inputNum);
 10 
 11 //計算矩陣的分割方案(基於數組)
 12 int calculate_M1(int*data, int matrixNum, vector<vector<int> >&num, vector<vector<int> > &points) {
 13     
 14     int span;//起止點間隔距離,表示跨度 最大跨度比總的矩陣數小1
 15     int start;//起點
 16     int end;//終點
 17     int spiltPoint;//分割點 分割線在分割點與下一點之間
 18     int multiplyTimes;//臨時存儲子式的乘法運算次數
 19     int rows, columns, interfaces;//行 列 介面
 20 
 21     for (span = 1; span < matrixNum-1; span++) {                ///間隔距離 相鄰矩陣的間隔距離為1
 22         for (start = 1; start + span < matrixNum; start++) {    ///起點從第一個矩陣的列開始
 23             //子式確立,下麵開始計算這個子式的最少乘法運算次數,初值先設為最大整數
 24             end = start + span;
 25             num[start][end] = INT_MAX;
 26             for (spiltPoint = start; spiltPoint < end; spiltPoint++) {
 27                 rows = *(data + start-1);
 28                 columns = *(data + end);
 29                 interfaces = *(data + spiltPoint);
 30 
 31                 multiplyTimes = num[start][spiltPoint] + num[spiltPoint + 1][end] + rows * interfaces * columns;
 32                 if (multiplyTimes < num[start][end]) {
 33                     points[start][end] = spiltPoint;  ///記錄分割點
 34                     num[start][end] = multiplyTimes;   ///記錄最少乘法次數
 35                 }
 36             }
 37         }
 38     }
 39     return 0;
 40 }
 41 
 42 //根據記錄的分割點,生成最後的矩陣相乘表達式
 43 string make_result(vector<vector<int> > &points, int t1, int t2) {
 44     if (t1 == t2)
 45         return string(1, 'A' + t1 -1);
 46     int split = points[t1][t2];
 47     return "(" + make_result(points, t1, split) + "*" + make_result(points, split + 1, t2) + ")";
 48 }
 49 
 50 
 51 int main()
 52 {
 53     int matrixNum = 0;///連乘的矩陣個數
 54     vector<pair<int, int>> data; ///存儲矩陣的尺寸
 55 
 56     //手動輸入
 57     //輸入的第一個值是第一個矩陣的行,其餘是各矩陣的列
 58     int* matrixSizeSeq;
 59     matrixSizeSeq = uncertainInput(matrixNum);
 60     if (matrixSizeSeq == NULL)
 61         return false;
 62     
 63     //重構為向量
 64     /*for (int ptrOffset = 0; ptrOffset < matrixNum - 1; ptrOffset++)    ///遍歷到倒數第二個輸入
 65         data.push_back(make_pair(*(matrixSizeSeq + ptrOffset), *(matrixSizeSeq + ptrOffset + 1)));*/
 66 
 67     //為記錄乘法次數和分割點的n階矩陣分配空間並初始化
 68     vector<vector<int> > num(matrixNum, vector<int>(matrixNum));
 69     vector<vector<int> > points(matrixNum, vector<int>(matrixNum));
 70     for (int i = 0; i < matrixNum; i++) {
 71         for (int j = 0; j < matrixNum; j++) {
 72             points[i][j] = -1;
 73             num[i][j] = 0;
 74         }
 75     }
 76 
 77     calculate_M1(matrixSizeSeq, matrixNum, num, points);
 78     cout << make_result(points, 1, matrixNum - 1) << "\t最少乘法次數為:" << num[1][matrixNum - 1] << endl;
 79     cin >> matrixNum;
 80     return 0;
 81 }
 82 
 83 int* uncertainInput(int& inputNum)
 84 {
 85     //輸入0表示輸入完成 輸入負數認為手誤
 86     using namespace std;
 87 
 88     int allocateSpace = 5;
 89     int* inputSequence = (int *)malloc(allocateSpace * sizeof(int));    ///存儲矩陣的尺寸輸入
 90 
 91 
 92     int ptrOffset = 0;        ///指針偏移
 93     int tempSize = 0;        ///臨時存儲輸入值
 94 
 95     do {
 96         cin >> tempSize;
 97         if (tempSize <= 0)
 98             continue;
 99         *(inputSequence + ptrOffset++) = tempSize;
100         if (ptrOffset >= allocateSpace)
101         {
102             allocateSpace += 5;
103             inputSequence = (int*)realloc(inputSequence, allocateSpace * sizeof(int));
104         }
105     } while (tempSize != 0);
106     inputNum = ptrOffset;    ///偏移量即輸入個數
107     return inputSequence;
108 }

比如求$S_{ad}$,則在控制台輸入a b c d 0,然後回車即可。當然 a b c d 必須替換為數字。

 

代碼中用到的一些知識

C++提供模版類string,其中一個構造方法可將字元轉化為字元串。如 string(1, 'A'+1),第一個參數是源字元延拓次數,這個構造函數將‘B’轉化為"B"。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 假定有下麵這樣的列表: 編寫一個函數,它以一個列表值作為參數,返回一個字元串。該字元串包含所有表項,表項之間以逗號和空格分隔,併在最後一個表項之前插入 and。例如,將前面的 spam 列表傳遞給函數,將返回'apples, bananas, tofu, and cats'。但你的函數應該能夠處理傳 ...
  • 今日主要內容 正則表達式 logging模塊 一、正則表達式 (一)什麼是正則表達式 1. 正則表達式的定義: 是對字元串操作的一種邏輯公式,就是用事先定義好的一些特定字元、及這些特定字元的組合,組成一個“規則字元串”,這個“規則字元串”用來表達對字元串的一種過濾邏輯。 簡單來說,我們使用正則表達式 ...
  • 由於JDK中提供的ByteBuffer無法動態擴容,並且API使用複雜等原因,Netty中提供了ByteBuf。Bytebuf的API操作更加便捷,可以動態擴容,提供了多種ByteBuf的實現,以及高效的零拷貝機制。 ByteBuf的操作 ByteBuf有三個重要的屬性:capacity容量,rea ...
  • 構建環境 macOS 10.13.6 JDK1.8 IntelliJ IDEA 2018.3.6 (Ultimate Edition) "Spring v5.1.9.RELEASE" Gradle 5.5.1。直接使用brew安裝Gradle 源碼構建 1.源碼導入 2.閱讀Spring源碼下的 i ...
  • 1 import random 2 3 def v_code(): 4 for i in range(5): 5 code1 = random.randrange(10) #生成一個隨機0-9隨機數字 6 code2 = random.choice(chr(random.randrange(65,9 ...
  • [TOC] numpy模塊 numpy簡介 numpy官方文檔: numpy是Python的一種開源的數值計算擴展庫。這種庫可用來存儲和處理大型numpy數組,比Python自身的嵌套列表結構要高效的多(該結構也可以用來表示numpy數組)。 numpy庫有兩個作用: 1. 區別於list列表,提供 ...
  • 關於虛擬機模板 想用vagrant搭建hadoop集群,要完成以下準備工作: 1. 三個虛擬機實例操作系統都是CentOS7的server版; 2. 每個實例都要安裝同樣的應用、關閉防火牆、關閉swap等; 今天就來做個模板,用此模板創建好的虛擬機都已經完成了上述操作; 關於vagrant的安裝和基 ...
  • 前兩篇教程中我們學習了LED、按鍵、開關的基本原理,數字輸入輸出的使用以及兩者之間的關係。我們用到了 pin_mode 、 pin_read 和 pin_write 這三個函數,實際上它們離最底層(至少是單片機製造商允許我們接觸到的最底層)就只有一步之遙了。而學單片機要是不瞭解一點底層,那跟Ardu ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...