利用十字鏈表壓縮稀疏矩陣(c++)-- 數據結構


題目:

7-1 稀疏矩陣 (30 分)
 

如果一個矩陣中,0元素占據了矩陣的大部分,那么這個矩陣稱為“稀疏矩陣”。對於稀疏矩陣,傳統的二維數組存儲方式,會使用大量的內存來存儲0,從而浪費大量內存。為此,可以用三元組的方式來存放一個稀疏矩陣。

對於一個給定的稀疏矩陣,設第r行、第c列值為v,且v不等於0,則這個值可以表示為 <r,v,c>。這個表示方法就稱為三元組。那么,對於一個包含N個非零元素的稀疏矩陣,就可以用一個由N個三元組組成的表來存儲了。

如:{<1, 1, 9>, <2, 3, 5>, <10, 20, 3>}就表示這樣一個矩陣A:A[1,1]=9,A[2,3]=5,A[10,20]=3。其余元素為0。

要求查找某個非零數據是否在稀疏矩陣中,如果存在則輸出其所在的行列號,不存在則輸出ERROR。

輸入格式:

共有N+2行輸入: 第一行是三個整數m, n, N(N<=500),分別表示稀疏矩陣的行數、列數和矩陣中非零元素的個數,數據之間用空格間隔; 隨后N行,輸入稀疏矩陣的非零元素所在的行、列號和非零元素的值; 最后一行輸入要查詢的非0數據k。

輸出格式:

如果存在則輸出其行列號,不存在則輸出ERROR。

輸入樣例:

在這里給出一組輸入。例如:

10 29 3
2 18 -10
7 1 98
8 10 2
2

輸出樣例:

在這里給出相應的輸出。例如:

8 10



分析:

在矩陣中,若數值為0的元素數目遠遠多於非0元素的數目,並且非0元素分布沒有規律時,則稱該矩陣為稀疏矩陣。
為了更好地定義稀疏矩陣,引入稀疏因子 t =num/(n*m)[其中num為矩陣中非零元素個數,n為矩陣行數,m為矩陣列數],
當t<=0.05時,該矩陣即可稱作稀疏矩陣。


壓縮稀疏矩陣的常見方法有兩種:
1.利用三元組表壓縮
優點:①代碼簡易;
②占用空間相對較小
缺點:①無法實現隨機存儲;
②在進行對矩陣的操作(如矩陣的乘法/加法)矩陣中數據改變后,需要重新定義三元組表,不具有通用性,不靈活。

2.利用十字鏈表壓縮
優點:①能夠實現隨機存儲;
②對於任意矩陣具有通用性
缺點:①代碼實現困難;
②占用空間相對較大


分析之后我們發現,相對於利用三元組表壓縮稀疏矩陣,利用十字鏈表壓縮應用的范圍更為廣闊,更具有實際操作意義。
這里我們采取十字鏈表方式進行壓縮。



十字鏈表核心(難點):



                                                                                                                        



1.節點定義
(1)非零元素節點:
我們可以知道,十字鏈表說到底還是若干個循環鏈表,那么在創建十字鏈表之前我們需要構建一個合適的節點
所需信息:行(row)、列(col)、值(val),該節點又要連接行的下一個節點(*right)以及列的下一個節點(*down)


如圖:










(2)頭節點定義
頭節點與非零元素不同的是節點中row的位置存放的是原矩陣所有行數,col的位置存放的是原矩陣所有列數,且頭節點需要指向一個指針數組(用於表示行列鏈表頭)。
所需信息:行(row)、列(col)、指向指針數組*h[]的指針(*next)


如圖:


(3)行列鏈表頭節點定義:
行列鏈表頭的表示其實是利用指針數組實現的(存放指針的數組)*h[],這里為了方便,我們稱為行列頭指針。
所需信息:指向下一個行列鏈表頭節點的指針(*next)、指向該行第一個非零元素的指針(*right)、指向該列第一個非零元素的指針(*down)


如圖:





綜上所述,為了實現定義節點操作時的一致性,我們會將節點定義如下圖:

 
           
 

 

 

 

2.行列鏈表頭節點在十字鏈表中的理解


連線兩端一個看上去像行頭、另一個看上去像列頭,其實這兩個都是同一個*h[]。
我看到有建立兩個指針數組的博客 https://blog.csdn.net/zhuyi2654715/article/details/6729783,即行鏈表頭數組和列鏈表頭數組,
想必是誤解了圖的意思。





我是那么理解的:



3.當行/列中沒有非零元素時的表示

前面提到,這是由若干個循環鏈表構成的十字鏈表,那么在空行/列的時候可以用行列頭指針指向它自己本身來表示。
同理,當為行/列末時,最后一個節點的指針right/down必然是指向行/列頭節點的。




代碼:

#include<iostream>
using namespace std;

//定義十字鏈表節點 
typedef struct matrinode{
    int i, j;
    struct matrinode *right, *down;
    union{
        int value;
        struct matrinode *next;
    }tag;
}matrinode;

//創建十字鏈表
//傳入值:頭節點指針,原矩陣行數,原矩陣列數 
void createlist(matrinode *&head, int row, int column) 
{
    int max = (row>column)?row:column;//取行列的最大值 
    //定義指針數組(用於表示行列頭),p、q、r輔助指針 
    matrinode *h[max], *p, *q, *r;
    head = new matrinode;//申請頭節點空間 
    head->i = row;//將行數、列數存入頭節點 
    head->j = column;
    
    r = head;
    
    //行列鏈表頭
    int count;
    for(count = 1; count<max+1; count++){//為了表示矩陣的行數和列數,這里從1開始計數 
        h[count] = new matrinode;//給行列鏈表頭申請空間 
        h[count]->down = h[count]->right = h[count];
        h[count]->i = h[count]->j = 0;
        r->tag.next = h[count];//head->h[1]->h[2]->...->h[max] 
        r = h[count];
    }
    r->tag.next = head;//最后一個行列鏈表頭節點指向head 
    
    int num;//非零元素個數 
    int i, j, v;
    cin>>num;
    for(count=1; count<num+1; count++){
        p = new matrinode;
        cin>>i>>j>>v;
        p->i = i;
        p->j = j;
        p->tag.value = v;
        
        
        /*這里插入非零元素有兩種情況:(以在某行插入為例) 
        ①當行鏈表為空(q->right == h[count])—或改元素列序數最大時 -> 插入到最后;
        ②非以上情況 -> 插入到某兩節點中間 
        */
         
        //行插入 
        q = h[count];
        while(q->right != h[count] && q->right->j < j){
            q = q->right;
        }    
        p->right = q->right;
        q->right = p;
        //列插入    
        q = h[count];
        while(q->down != h[count] && q->down->i < i){
            q = q->down;
        }    
        p->down = q->down;
        q->down = p;
    } 
    
}


//查找是否有符合條件的值 
void search(matrinode *head)
{
    int value;//輸入需要查找的值 
    cin>>value;
    
    matrinode *h, *p;//定義兩個輔助結點
    h = new matrinode;
    p = new matrinode;

    h = head->tag.next;//h指向h[1] 
    p = h->right;//p指向h[1]第一行第一個元素 
    int flag = 0;//flag == 0表示找不到匹配值 
    int i, j;//若找到滿足條件的值時,i、j存放該值的行和列 
    
    
    while(h->tag.next != head){//當前行不是最后一行時
        if(p == h){//若該行為空 
           h = h->tag.next;//移到下一行 
           p = h->right;
        }else{//若該行非空 
           if(p->tag.value != value){//若p節點存放的值!=需要查找的值時 
               p = p->right;//p移動到該行下一個 
           }else{//找到滿足條件的值時 
               i = p->i;
               j = p->j;
               flag = 1;
               cout<<i<<" "<<j;
               break;
            }  
        }
}    
    if(flag == 0){
        cout<<"ERROR";
    }    
    
    
 } 


int main(){
    matrinode *head;
    int row, column;
    cin>>row>>column;
    createlist(head, row, column);
    
    search(head);
    return 0;
}

 



總結:

為了學會十字鏈表,查閱了很多資料,每份資料都不盡相同。這份代碼是我打的第四份代碼,因為參考的博客都不太一樣,
比如有的博客會用到struct來定義一個十字鏈表,有的博客更喜歡用class囊括所有。從一開始的照貓畫虎,到逐漸理解算法的核心思想,
需要經過不斷的鍛造,這也可能是我與代碼互相折磨、互相成長的過程吧!


參考資料:

1.https://blog.csdn.net/xiangxizhishi/article/details/79119532

2.https://blog.csdn.net/TheLegendOfZelda/article/details/80221922


 
          


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM