【資料結構】稀疏矩陣的壓縮儲存和轉置演算法(C++程式碼)
一 稀疏矩陣的定義
矩陣是如今很多科學與工程計算問題中常用的數學物件,矩陣涉及到的計算通常會出現矩陣的階數比較高但是非零元素的個數卻比較少的情況,因此,我們需要有一種方法來壓縮這種比較稀疏的矩陣。
那麼,首先第一個問題就是如何定義一個矩陣是否是稀疏的?參考嚴蔚敏的資料結構教材,第96頁給出了稀疏矩陣的定義:假設在m×n的矩陣中,有t個元素不為零,令δ=t/(m+n),稱δ為矩陣的稀疏因子。通常認為δ<=0.05時稱為稀疏矩陣。
二 矩陣的壓縮儲存
那麼,如何進行稀疏矩陣的壓縮儲存呢?
按照壓縮儲存的概念,只要儲存矩陣中的非零元素的資訊就好了,同時還需要保證根據儲存的資訊能唯一的確定一個矩陣。因此,採用三元祖(行,列,元素值)再加上矩陣的行、列值就可以解決這個問題。由三元組的不同表示方法可引出稀疏矩陣不同的壓縮儲存方法,比如說有:三元組順序表、行邏輯連結的順序表、十字連結串列等。本文暫時只討論三元組順序表的情況。
三元組順序表的儲存表示如下:
#define MAXSIZE 12500
typedef struct {
int i,j; //元素的行下標和列下標
ElemType e; //元素值
}Triple;
typedef struct{
Triple data[MAXSIZE+1]; //非零元素的三元組
int mu,nu,tu; //矩陣的行數、列數和非零元個數
}TSMatrix;
三 矩陣的轉置演算法
矩陣的轉置運算是一種最簡單的矩陣運算,接下來介紹在稀疏矩陣儲存為三元組形式下的兩種矩陣轉置演算法。先從第一種簡單的轉置演算法說起,並配上虛擬碼,文章末尾附上可執行的C++程式碼,後續會加附Java程式碼。
要獲取一個矩陣的轉置矩陣,我們先來分析一下初始矩陣和轉置後矩陣的三元組的差異,這裡假設矩陣以行序為主序進行儲存,如下圖所示。
a要變成b只需要經過3步:(1)將矩陣的行列值相互交換;(2)將每個三元組中的i,j相互調換;(3)重組三元組之間的次序。前兩條是很容易做到的,關鍵是如何實現第三條。
(1)這裡提出第一種解決辦法,即引出了第一個演算法:按照b.data中三元組的次序來找a.data中對應的三元組進行轉置。也就是按照初始矩陣的列序來進行轉置操作,文字描述可能會產生歧義,看下面這張圖就能明白了。
虛擬碼如下:
Status TransposeSMatrix(TSMatrix M, TSMatrix &T){
T.mu = M.nu; T.nu = M.mu; T.tu = M.tu;
if(T.tu){
q = 1;
for (col = 1; col <= M.nu; ++col)
{
for (p = 1; p <= M.tu; ++p)
if (M.data[p].j == col)
{
T.data[q].i = M.data[p].j;
T.data[q].j = M.data[p].i;
T.data[q].e = M.data[p].e;
++q;
}
}
return Ok;
}
}//TransposeSMatrix
(2)還有另外一種解決辦法,就是按照a.data中元素的順序進行轉置,對於每一個a.data中的元素,去找在b.data中存放的位置,這種解決辦法的難點就在於如何找到a.data中元素對應於b.data中的位置。為了確定這些位置,在轉置之前先做一個預處理,先求得初始矩陣中每一列的非零元素個數,進而求出每一列的第一個非零元素在b.data中的位置,這些是可以預先計算出來的。因此,需要附加num和cpot兩個向量,num[col]表示矩陣中第col列中非零元素的個數,cpot[col]表示矩陣中第col列的第一個非零元素在b.data中的恰當位置,有以下公式:
虛擬碼如下:
Status FastTransposeSMatrix(TSMatrix M, TSMatrix &T){
T.mu = M.nu; T.nu = M.mu; T.tu = M.tu;
if(T.tu){
for (col = 1; col <= M.nu; ++col) num[col] = 0;
for (t = 1; t <= M.nu; ++t) ++num[M.data[t].j];
cpot[1] = 1;
for(col = 2; col <= M.nu; ++col) cpot[col] = cpot[col-1]+num[col-1];
for (p = 1; p <= M.nu; ++p)
{
col = M.data[p].j;
q = cpot[col];
T.data[q].i = M.data[p].j;
T.data[q].j = M.data[p].i;
T.data[q].e = M.data[p].e;
++cpot[col];
}
}
return Ok;
}//TransposeSMatrix
附C++原始碼:
#include<iostream>
#include <vector>
using namespace std;
#define MAXSIZE 1000
typedef struct{
int row, col; //元素所在行,列
int elem; //元素值
}Triple;
typedef struct {
Triple data[MAXSIZE+1];
int row_num, col_num, elem_num; //矩陣的行數,列數,非零元個數
}TSMatrix;
/************************************************************************/
/* 轉置演算法1,時間複雜度為O(col_num*elem_num) */
/************************************************************************/
void TransposeMatrix(TSMatrix matrix, TSMatrix &transed_matrix){
transed_matrix.row_num = matrix.col_num;
transed_matrix.col_num = matrix.row_num;
transed_matrix.elem_num = matrix.elem_num;
int q = 0;
//按照matrix中列的順序來遍歷matrix
for (int i = 0; i < matrix.col_num; i++)
{
//找到第i列中所有的非零元素,然後加入到transed_matrix中
for(int j = 0; j < matrix.elem_num; j++){
//這裡要減一是因為輸入的時候按照下標為1開始,儲存的時候是從0開始
if((matrix.data[j].col -1) == i){
transed_matrix.data[q].row = matrix.data[j].col;
transed_matrix.data[q].col = matrix.data[j].row;
transed_matrix.data[q].elem = matrix.data[j].elem;
q++;
}
}
}
}//TransposeMatrix
/************************************************************************/
/* 轉置演算法2,時間複雜度為O(elem_num) */
/************************************************************************/
void FastTransposeMatrix(TSMatrix matrix, TSMatrix &transed_matrix){
transed_matrix.row_num = matrix.col_num;
transed_matrix.col_num = matrix.row_num;
transed_matrix.elem_num = matrix.elem_num;
//新增num和cpot兩個變數;
//num[col] : 表示矩陣中第col列中非零元素的個數
//cpot[col] : 矩陣中第col列的第一個非零元素在transed_matrix.data中的恰當位置
/*--------初始化這兩個陣列---------------*/
vector<int> num;
num.resize(matrix.col_num, 0);
for (int i = 0; i < matrix.elem_num; i++)
{
num[matrix.data[i].col-1]++;
}
vector<int> cpot;
cpot.resize(matrix.col_num, 0);
cpot[0] = 0;
for (int col = 1; col < matrix.col_num; col++)
{
cpot[col] = cpot[col-1]+num[col-1];
}
/*--------------------------------------*/
for (int p = 0; p < matrix.elem_num; p++)
{
int col = matrix.data[p].col-1;
int q = cpot[col];
transed_matrix.data[q].row = matrix.data[p].col;
transed_matrix.data[q].col = matrix.data[p].row;
transed_matrix.data[q].elem = matrix.data[p].elem;
++cpot[col];
}
}//FastTransposeMatrix
//列印矩陣
void printMatrix(TSMatrix matrix){
int max_row = 0, max_col = 0;
//獲取矩陣非零元素的最大行數和最大列數
for (int i = 0; i < matrix.elem_num; i++)
{
if (max_row < matrix.data[i].row)
{
max_row = matrix.data[i].row;
}
if (max_col < matrix.data[i].col)
{
max_col = matrix.data[i].col;
}
}
//構造一個二維陣列,存的是二維矩陣的元素,只是為了方便輸出
vector<vector<int>> tmp_data;
//先初始化這個二維陣列
tmp_data.resize(max_row);
for (int i = 0; i < max_row; i++)
{
tmp_data[i].resize(max_col, 0);
}
//將三元組中的資訊新增到二維陣列中
for (int i = 0; i < matrix.elem_num; i++)
{
Triple tri;
tri.col = matrix.data[i].col - 1;
tri.row = matrix.data[i].row - 1;
tri.elem = matrix.data[i].elem;
tmp_data[tri.row][tri.col] = tri.elem;
}
for (int i = 0; i < max_row; i++)
{
for (int j = 0; j < max_col; j++)
{
cout<<tmp_data[i][j]<<"\t";
}
cout<<endl;
}
cout<<endl;
}//printMatrix
/************************************************************************/
/* author by bo 2017-4-6 */
/************************************************************************/
void main(){
TSMatrix matrix;
TSMatrix transed_matrix;
// 依次輸入矩陣的非零元素個數、矩陣的行數、矩陣的列數、三元組的內容
cout<<"input the non-zero number of matrix:";
cin>>matrix.elem_num;
cout<<"input the number of row:";
cin>>matrix.row_num;
cout<<"input the number of column:";
cin>>matrix.col_num;
cout<<"input the triple of matrix(下標從1開始):"<<endl;
for (int i = 0; i < matrix.elem_num; i++)
{
cin>>matrix.data[i].row>>matrix.data[i].col>>matrix.data[i].elem;
}
cout<<"input finished!"<<endl;
//矩陣的轉置演算法1
TransposeMatrix(matrix, transed_matrix);
//矩陣的轉置演算法2
// FastTransposeMatrix(matrix, transed_matrix);
cout<<"/--------原始矩陣------------\\"<<endl;
printMatrix(matrix);
cout<<"\\----------------------------/"<<endl;
cout<<"/--------轉置後矩陣-----------\\"<<endl;
printMatrix(transed_matrix);
cout<<"\\----------------------------/"<<endl;
}//main