程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 數據結構——稀疏矩陣,數據結構矩陣

數據結構——稀疏矩陣,數據結構矩陣

編輯:C++入門知識

數據結構——稀疏矩陣,數據結構矩陣


在普遍的印象中,矩陣是由方括號圍住,同時各個坐標的數字整齊的排列著。如下圖所示:

看到圖示後,第一反應當然是用一個二維數組來表示,即簡單又易懂。但我們又會碰到下圖所示矩陣:

看看這個矩陣,0好多啊(我們稱之為稀疏矩陣),若用二維數組來表示,會重復存儲了很多個0了,這樣浪費了空間。

故要采取一種特殊的方式來存儲這樣的矩陣,這裡就提出了數組的方式來存儲這樣的矩陣。

typedef struct
{
    int row;        //行坐標
    int col;        //列坐標
    int value;    //該點的值
} item;

這裡item表示矩陣中點,那麼一個稀疏矩陣(非0值的個數為num)的描述就如下:

用item[num+1]來表示一個矩陣,為啥要num+1,先作一點說明:

item數組的首值為{行數,列數,非0值個數}結構,除首值外,其余值表示的矩陣中非0值的行列坐標以及本身的值。先上個小例子,來解釋下:

已知上述矩陣非0值的個數為:16-7=9;用數組item test[9+1]來表示

數組下標(i) 行(row) 列(col) 值(value) 0 4 4 9 1 0 0 a 2 0 1 b 3 0 3 c 4 2 0 d 5 2 1 e 6 2 3 f 7 3 0 g 8 3 1 h 9 3 3 i

通過這種方式來存儲,大大節約了空間,但同時帶來了一定的麻煩(主要是矩陣運算時,不太好理解)。

1、矩陣轉置運算

矩陣轉置:即把矩陣行值變列值,列值變成行值,對於二維數組而言,就是將二維數組的下標進行互換即可,原始:a[i][j]=value;轉換後:a[j][i]=value;

但轉置對於用數組表示的矩陣來說,理解起來也簡單,即將行列互換。

void transpose1(item* a,item* b)
{
    int col = a[0].row;
    int row = a[0].col;
    int value = a[0].value;
    b[0].row = row;                         //先將b[0]的值填充好
    b[0].col = col;
    b[0].value = value;
    int num=1;                              //num表示b矩陣的數組下標,因為首值已經設好,故從1開始
    for(int i=0;i<row;i++)                  //i為b的行標,裡層循環遍歷a矩陣,因為j從小到大,故獲得b矩陣的列標也是在同行標的情況下,從小到大排列
    {
        for(int j=1;j<=value;j++)           //數組下標由小到大遍歷
        {
            if(a[j].col == i)               //當a矩陣的列標與b的行標相等,則做如下處理
            {
                b[num].col = a[j].row;      //從這可以看出,b的列標在同行標下,是從小到大排列的。
                b[num].row = i;
                b[num].value = a[j].value;
                num++;
            }
        }
    }
}

從代碼中,我們可以輕松看出,時間復雜度是比較大的,兩層循環的緣故;O(row*(value));若value為row*col的話,這個開銷就非常大了。

書中提出了快速的轉置的方式,簡要描述如下:

a、首先找出轉置後的矩陣,每行非0的個數。(通過遍歷原始數組,對col的值進行次數統計)

b、獲取每行非0值的起始數組下標;(已經0行的起始數組下標為1,而1行的起始數組下標就是0行的起始下標+0行中非0值的個數,同理下推)

c、遍歷原始數組(從下標1開始),首先獲得結果數組的下標,由原始數組的列標(結果數組行標)根據b步驟來判斷循環的當前下標應該在結果數組的什麼位置。

說的好繞口,看程序。

void transpose(item* a,item* b)//a為原始矩陣,b為轉置後的矩陣。
{
    int row = a[0].col;
    int col = a[0].row;
    int value = a[0].value;
    b[0].row = row;
    b[0].col = col;
    b[0].value = value;

    //先統計下各行非0的個數
    //先初始個存儲各行非0的數組
    int* p = new int[row];
    for(int i=0;i<=row;i++)
    {
        p[i]=0;
    }
    for(int i=1;i<=value;i++)
    {
        p[a[i].col]++;
    }
    //推算每行的起始點
    int* start = new int[row];
    start[0] = 1;
    for(int i=1;i<row;i++)
    {
        start[i] = start[i-1]+p[i-1];
    }
    for(int i=1;i<=value;i++)
    {
        int j = start[a[i].col]++;//用於獲取數組下標,注意下標右移
        b[j].row = a[i].col;
        b[j].col = a[i].row;
        b[j].value = a[i].value;
    }
    delete[] start;
    delete[] p;
}

到此,我們也就完成了轉置運算。

2、矩陣的乘法

在上一篇中,講述過了矩陣乘法,這裡就不做贅述了

對於稀疏矩陣來說,乘法相對來說,處理起來比較煩鎖,並不像二維數組處理起來那麼無腦。因為並不能從兩個矩陣的非零值個數中,直接獲得乘積後的結果矩陣的非零值個數。例如:

可以看出來,處理還是比較復雜的。

void mult(item* a,item* b,item* result)
{
    //根據矩陣相乘的具體算法步驟,即結果矩陣的(i,j)為a矩陣的i行與b矩陣的j列對應相乘,
    //那麼我們可以做一個轉換,可以看成a矩陣的i行與b矩陣的轉置後的j行對應相乘,
    //對應的根據是:a矩陣的列數與b轉置後的矩陣的列數相對應
    //首先對右邊的矩陣進行轉置操作
    int a_row = a[0].row,a_col = a[0].col,a_value = a[0].value;
    int b_row = b[0].row,b_col = b[0].col,b_value = b[0].value;
    if(a_col != b_row)
    {
        std::cout << "error";
        return;
    }
    //c矩陣為b的轉置矩陣
    //先獲取結果矩陣最大長度
    item* c = new item[b_value+1];
    transpose(b,c);                                 //下面所述的row表示結果數組的行標,col表示結果數組的列標
    int row = a[1].row;                             //結果矩陣的行標row一定是從這裡開始的
    int col = 0;                                    //列標col暫置為0
                                                    //_begin用於標記row行初始位置。
    int _begin=1;                                   //如:循環時,前面row行已經處理完了,這時a數組的前面row行對於後續計算也就沒意義了,這裡_begin的作用就是用於記錄a數組的row+1行位置作用
    int sum,num=1;                                  //sum存儲的結果數組(row,col)處的value值,num用於存儲結果數組的下標
    for(int i=1;i<=a_value;)                        //從a數組1開始遍歷,直至a數組尾
    {
        col = c[1].row;                             //由於c為b數組轉置矩陣,其行標就是結果數組的列標col,由矩陣的乘法性質知,每次col都從c[1].row開始的,故每次循環開始時,要重置下。
        sum = 0;
        for(int j=1;j<=b_value+1;)                  //此處value+1的作用是應對這種情況:a數組未遍歷完,而j值已經取遍c的下標,而j++的作用導致超出c數組范圍,可能會越界,而下面的第一個if作用也正是處理這種狀況
        {
            if(j>b_value)                           //j值已經超出了b_value,意味著數組已被完全遍歷,說明當前行row的值已經完全找出了。
            {
                result[num].row = row;              //這時我們就可以把之前記錄的sum填入結果數組中,同時下標num要自加1
                result[num].col = col;
                result[num].value = sum;
                num++;
                break;                              //當前行的所有非0值均已找出,可以跳出該循環了
            }
            if(i>a_value||a[i].row != row)          //1、此時a矩陣該行已迭代完,沒有可算的值2、當前row行已經迭代完,若繼續迭代,行值就變了,
            {
                result[num].row = row;              //所以此時也可以將sum值存入結果數組中,同時下標num要自加1.
                result[num].col = col;
                result[num].value = sum;
                num++;
                i = _begin;                         //為了計算當前行的下一個(下一個col值)非零值,i要為該行的起始位置開始遍歷
                for(;j<=b_value&&c[j].row==col;j++);//主要作用為了將當前行值為col的全部遍歷過,但不作任何其他處理,然後進入下一col值
                if(j>b_value)break;                 //可能出現j的值比b_value大,就要跳出循環了。
                col = c[j].row;                     //將列值置為下一個col值
            }
            else if(c[j].row != col)                //由於c數組的row表示結果數值的列值,該條件說明當前列值與c數組的row值不等,意味著結果數組的列值應該下移了。
            {
                result[num].row = row;              //處理方式與上述有些類似
                result[num].col = col;
                result[num].value = sum;
                num++;
                i = _begin;
                col = c[j].row;
            }
            else if(a[i].col < c[j].col)            //下面3個條件很好理解。
            {
                i++;
            }
            else if(a[i].col == c[j].col)
            {
                sum += a[i++].value * c[j++].value;
            }
            else if(a[i].col > c[j].col)
            {
                j++;
            }
        }
        for(;i<=a_value&&a[i].row == row;i++);      //通過遞歸,將當前行迭代,直到進行下一個row行值。
        if(i>a_value)                               //如果i的值超出最大值,說明已經算完所有行了。
            break;                                  //終止循環
        row = a[i].row;
        _begin = i;                                 //同時記下此時row對應的開始下標。
    }
    result[0].row = a_row;
    result[0].col = b_col;
    result[0].value = num-1;
    delete[] c;
}

有空會把圖給畫出來,幫助理解。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved