數據結構第四課 --- 廣義表和矩陣壓縮

廣義表算是線性表的一種提升吧,廣義表中的元素可以使一個原子類型,也可以是一個表,廣義表的存儲類型有兩種,一種是混合類型,將原子類型和表結點類型放在一起的,此時每一個結點有一個自己的頭指針和一個尾指針,中間是該節點的數據域,指向一個原子類型,另一種存儲方式是,對每一個原子類型的結點也具有一個尾指針,這樣當其中的一個結點爲表結點的時候,其子表結點是原子結點不斷向下指向的.

//兩種存儲方式的表示形式(僞碼錶示)
typedef struct GlNode 
{
	int tag;
	union {
		AtomType atom;
		struct {
			struct GlNode *hp ,* tp;
		}ptr;
	}
};

typedef struct GlNode
{
	int tag;
	union {
		AtomType atom;
		struct GlNode *hp;
	}
	struct GlNode *tp;
};

通過比較兩種存儲方式不難發現,當廣義表中的元素大多是原子類型的時候,採用第二中方式是比較節省存儲空間的。廣義表的應用有通過廣義表表示多元多項式,每一個元作爲一個表結點,下面連接一個線性表,表示出其係數和次數。

求廣義表的深度(採用地遞歸的方式得到每一個子表結點的深度,然後將最大的字表深度加1就是當前廣義表的深度)

//計算廣義表的深度
int GListDepth (GList L){
	if (!L) return 1;
	if (L -> tag == ATOM) return 0;
	for (max = 0, p = L; p; p = p->tp){
		dep = GListDepth (p -> hp);
		if (dep > max)
			max = dep;
	}
	return max+1;
}
矩陣,在這裏分爲特殊矩陣和稀疏矩陣,特殊矩陣就是具有特殊的性質,比如對稱或者數據全部相等等,稀疏矩陣就是矩陣中爲0的比較多,而且其分佈沒有規律性,關於矩陣就是如何來存儲,使得其佔有的空間最小,也就是壓縮存儲

通常在高級語言編程中,都是用二維數組來存儲矩陣的元,然而在階數很高的矩陣中,有很多值相同的元或者是零元素。有時爲了節省存儲空間,可以對這類矩陣進行壓縮存儲。所謂壓縮存儲是指:爲多個值相同的元只分配一個存儲空間;對零元素不分配空間。

三元組書序表又稱有序的雙下標法,其特點:非零元在表中按行序有序存儲,因此便於進行依行順序處理的矩陣運算。但是,若需按行號存取某一行的非零元素,則需從頭開始進行查找。(時間複雜度較高)

行邏輯鏈接的順序表:便於隨機存取任意一行的非零元。

十字鏈表:當矩陣的非零元個數和位置在操作過程中變化較大時,就不適宜採用順序存儲結構來表示三元組的線性表。

對於稀疏矩陣的存儲結構應該根據對稀疏矩陣的操作來選擇合適的存儲方式,否則出現以時間換空間的情況,適得其反。以下只舉例說明三元組順序表的實現方式:

三元組順序表:以順序存儲結構來表示三元組表。

  1. /** 
  2.  * 三元組結構 
  3.  */  
  4. class Triple {  
  5.     int rowIndex;//非零元的行下標  
  6.     int colIndex;//非零元的列下標  
  7.     int value;//非零元的值  
  8.       
  9.     Triple(){  
  10.         this.rowIndex = 0;  
  11.         this.colIndex = 0;  
  12.         this.value = 0;  
  13.     }  
  14.       
  15.     Triple(int i, int j, int value){  
  16.         this.rowIndex = i;  
  17.         this.colIndex = j;  
  18.         this.value = value;  
  19.     }  
  20. }  


  1. public class SparseMatrix {  
  2.     public static void main(String[] args) {  
  3.         int[][] a = { { 01290000 }, { 0100000 },  
  4.                 { -30000140 }, { 00240000 },  
  5.                 { 01800000 }, { 1500, -7000 } };  
  6.   
  7.         System.out.println("稀疏矩陣轉換成三元組順序表:");  
  8.         LinkedList<?> list = constructTripleList(a);  
  9.         printTripleList(list);  
  10.           
  11.         System.out.println("輸出對應第2行的元素");  
  12.         restoreArray(list, 2);  
  13.           
  14.         System.out.println("三元組順序表還原成稀疏矩陣:");  
  15.         reverseToSparseMatrix(list);  
  16.   
  17.     }  
  18.   
  19.     /** 
  20.      * 根據行號還原順序表中的某一行 
  21.      *  
  22.      * @param list 
  23.      * @param rowIndex 
  24.      * @return 
  25.      */  
  26.     public static float[] restoreArray(LinkedList<?> list, int rowIndex) {  
  27.         float[] array = new float[7];  
  28.         Triple t = null;  
  29.         int index = 0;  
  30.         for (int i = 0; i < list.size(); i++) {  
  31.             t = (Triple) list.get(i);  
  32.             if (t.getRowIndex() == rowIndex) {  
  33.                 index = t.getColIndex();  
  34.                 array[index] = t.getValue();  
  35.             } else {  
  36.                 // array[index]=0;  
  37.             }  
  38.             if (t.getRowIndex() > rowIndex) {  
  39.                 break;  
  40.             }  
  41.         }// for_i  
  42.           
  43.         for(int i = 0; i < array.length; i++){  
  44.             System.out.print(array[i] + " ");  
  45.         }  
  46.         System.out.println();  
  47.         return array;  
  48.     }  
  49.   
  50.     /** 
  51.      * 構造三元組順序表 
  52.      *  
  53.      * @param matrix 
  54.      * @return 
  55.      */  
  56.     public static LinkedList<Triple> constructTripleList(int[][] matrix) {  
  57.         Triple e = null;  
  58.         LinkedList<Triple> list = new LinkedList<Triple>();  
  59.         for (int i = 0; i < matrix.length; i++) {  
  60.             for (int j = 0; j < matrix[0].length; j++)  
  61.                 if (matrix[i][j] != 0) {  
  62.                     e = new Triple(i, j, matrix[i][j]);  
  63.                     list.add(e);  
  64.                 }  
  65.         }  
  66.   
  67.         return list;  
  68.     }  
  69.   
  70.     /** 
  71.      * 將三元組順序錶轉換成矩陣並輸出 
  72.      *  
  73.      * @param list 
  74.      */  
  75.     public static void reverseToSparseMatrix(List<?> list) {  
  76.         Triple t = null;  
  77.         float[][] a = new float[6][7];  
  78.         int index = 0;  
  79.         for (int i = 0; i < 6; i++) {  
  80.             for (int j = 0; j < 7; j++) {  
  81.                 if (index < list.size()) {  
  82.                     t = (Triple) list.get(index);  
  83.                     if (t.getRowIndex() == i) {  
  84.                         // 肯定是存在t.getColIndex() == j的情況  
  85.                         if (j == t.getColIndex()) {  
  86.                             a[i][j] = t.getValue();  
  87.                             index++;  
  88.                             j = t.getColIndex();  
  89.                         } else if (j > t.getColIndex()) {  
  90.                             j = 0;  
  91.                         }  
  92.                     }  
  93.                 }  
  94.                 // 以下注釋部分會存在j>t.getColIndex,而t.getColIndex的確有值的情況,若按照以下操作取值,則會丟掉前面描述的情況的值  
  95.                 // if((t.getRowIndex()==i) && (t.getColIndex() == j)){  
  96.                 // a[i][j] = t.getValue();  
  97.                 // index++;  
  98.                 // }  
  99.             }// for_j  
  100.         }// for_i  
  101.   
  102.         for (int i = 0; i < 6; i++) {  
  103.             for (int j = 0; j < 7; j++) {  
  104.                 System.out.print(a[i][j] + " ");  
  105.             }  
  106.             System.out.println();  
  107.         }  
  108.     }  
  109.   
  110.     /** 
  111.      * 輸出稀疏矩陣 
  112.      *  
  113.      * @param matrix 
  114.      */  
  115.     public static void printSparseMatrix(int[][] matrix) {  
  116.         for (int i = 0; i < matrix.length; i++) {  
  117.             for (int j = 0; j < matrix[0].length; j++)  
  118.                 System.out.print(matrix[i][j] + " ");  
  119.             System.out.println();  
  120.         }  
  121.     }  
  122.   
  123.     /** 
  124.      * 輸出三元組順序表 
  125.      *  
  126.      * @param list 
  127.      */  
  128.     public static void printTripleList(List<?> list) {  
  129.         Triple e = null;  
  130.         Iterator<?> iter = list.iterator();  
  131.         while (iter.hasNext()) {  
  132.             e = (Triple) iter.next();  
  133.             System.out.println((e.rowIndex + 1) + " " + (e.colIndex + 1) + " "  
  134.                     + e.value);  
  135.         }  
  136.     }  
  137. }  
 剛纔終於完完全全、徹徹底底的搞明白了稀疏矩陣十字鏈表的存儲方式的實現與該算法的思想。我覺得有必要把自己的思路記下來,一呢等自己將來忘記了可以回過頭來看,二呢希望與我一樣對該存儲方式迷惑的朋友可以通過我的文章得到一點點的啓示。現在進入正題。

       我們知道稀疏矩陣的三元組存儲方式的實現很簡單,每個元素有三個域分別是i, j, e。代表了該非零元的行號、列號以及值。那麼在十字鏈表的存儲方式下,首先這三個域是肯定少不了的,不然在進行很多操作的時候都要自己使用計數器,很麻煩。而十字鏈表的形式大家可以理解成每一行是一個鏈表,而每一列又是一個鏈表。如圖所示:

 

通過上面的圖我們可以知道,每個結點不止要存放i, j, e。還要存放它橫向的下一個結點的地址以及縱向的下一個結點的地址。形成一個類似十字形的鏈表的結構。那麼每個結點的結構體定義也就呼之欲出了

 

  1. typedef struct OLNode {    
  2.      int  i, j;          //行號與列號     
  3.      ElemType e;        //值     
  4.      struct OLNode *right, *down;  //指針域     
  5. }OLNode, *OList;  


 

這樣我們對結點的插入與刪除就要修改兩個指針域。爲了方便我們對結點的操作,我們要創建頭指針或者頭結點。至於到底是選擇頭指針呢還是頭結點,請繼續看下去..

我們想下要怎麼創建頭指針或者頭結點,我們可以創建OLNode結構的結點形成一段連續的地址空間來指向某一行或者某一列中的結點(這是我最初的想法)
或者我們創建指針數組,數組元素中存放的地址就是某一行或者某一列的第一個結點的地址。來分析下兩種方法
第一種方法會浪費大量的空間,而因爲指針變量的空間都是4個字節,所以相對來說第二種節省空間。

毫無疑問我們選擇第二種,也就是創建頭指針。那麼第二種我們用什麼來實現?是數組還是動態內存分配?如果用數組我們要預先定義行和列的最大值,顯然這不是一個好主意,而動態內存分配的方法我們可以在用戶輸入了行數與列數之後分配相應的一段地址連續的空間。更爲靈活,所以我們選擇動態內存分配。

  1. typedef struct {    
  2.     OLink   *Rhead, *Chead;     
  3.     int mu, nu, tu;       // 稀疏矩陣的行數、列數和非零元個數      
  4. }CrossList;    


 

注意Rhead與Chead的類型,它們是指向指針的指針,也就是說,它們是指向我們定義的OLNode結構的結點的指針的指針。這話有點繞,不過相信C學的不錯的朋友都應該清楚。如果不清楚的請看這個帖子  http://topic.csdn.net/u/20110829/01/506b33e3-ebc9-4905-bf8d-d0c877f85c08.html

現在結構體已經定義好了,我們來想想下面應該幹什麼。首先需要用戶輸入稀疏矩陣的行數與列數以及非零元的個數。那麼就需要定義一個CrossList的結構體變量來存儲這些值。

  1. int main(void)    
  2. {    
  3.     CrossList M;    
  4.     CreateSMatrix(&M);    
  5. }   


CreatSMatrix函數是我們今天要創建的函數,它用來建立稀疏矩陣並使用十字鏈表的方式存儲矩陣。該函數的原型爲int CreateSMatrix(CrossList *M);

當我們創建好了M就需要用戶輸入了,那麼就要對用戶的輸入進行檢查,看是否符合要求,首先mu, nu, tu都不能小於0,並且mu, nu不能等於0(我們這裏假設行號與列號都是從1開始的,所以不能等於0),tu的值必須在0與mu * nu之間。

  1. int CreateSMatrix(CrossList *M)    
  2. {       
  3.     int i, j, m, n, t;    
  4.     int k, flag;    
  5.     ElemType e;    
  6.     OLNode *p, *q;    
  7.         
  8.     if (M->Rhead)    
  9.         DestroySMatrix(M);    
  10.     
  11.     do {    
  12.         flag = 1;    
  13.         printf("輸入需要創建的矩陣的行數、列數以及非零元的個數");    
  14.         scanf("%d%d%d", &m, &n, &t);    
  15.         if (m<0 || n<0 || t<0 || t>m*n)    
  16.             flag = 0;    
  17.     }while (!flag);    
  18.     M->mu = m;    
  19.     M->nu = n;    
  20.     M->tu = t;    
  21.         ...................................  
  22.          return 1;    
  23. }    


當用戶輸入了正確的值以後,我們要創建頭指針的數組

  1. //創建行鏈表頭數組     
  2. M->Rhead = (OLink *)malloc((m+1) * sizeof(OLink));    
  3. if(!M->Rhead)    
  4.     exit(-1);    
  5. //創建列鏈表頭數組     
  6. M->Chead = (OLink *)malloc((n+1) * sizeof(OLink));    
  7. if(!(M->Chead))    
  8.     exit(-1);    


 

這裏m+1與n+1是爲了後面操作的方便使得它們的下標從1開始。注意我們創建時候的強制轉換的類型。OLink * , 首先它是指針類型。我們連續創建了m+1個,每一個都指向OLink類型的變量,所以它裏面存放的就應該是一個指向OLNode類型的指針的地址。

創建完以後必須初始化,因爲我們後面的插入就其中一個判斷條件就是它們的值爲NULL,也就是該行或者該列中沒有結點

  1. for(k=1;k<=m;k++) // 初始化行頭指針向量;各行鏈表爲空鏈表      
  2.     M->Rhead[k]=NULL;    
  3. for(k=1;k<=n;k++) // 初始化列頭指針向量;各列鏈表爲空鏈表      
  4.     M->Chead[k]=NULL;  


現在我們就可以進行結點的輸入了,顯而易見的要輸入的結點個數剛纔已經存放到了t變量中,那麼我們要創建t個結點。這就是一個大的循環

而每創建一個結點我們都要修改它的兩個指針域以及鏈表頭數組。那麼我們可以分開兩次來修改,第一次修改行的指針域,第二次修改列的指針域。

  1. do {    
  2.             flag = 1;    
  3.             printf("輸入第%d個結點行號、列號以及值", k);    
  4.             scanf("%d%d%d", &i, &j, &e);    
  5.             if (i<=0 || j<=0)    
  6.                 flag = 0;    
  7.         }while (!flag);    
  8.     
  9.         p = (OLink) malloc (sizeof(OLNode));    
  10.         if (NULL == p)    
  11.             exit(-1);    
  12.         p->i = i;    
  13.         p->j = j;    
  14.         p->e = e;    


 

當用戶輸入一系列正確的值,並且我們也創建了一個OLNode類型的結點之後我們要講它插入到某一行中。首先要確定插入在哪一行?我們輸入的時候已經輸入了行號i,那麼我們自然要插入到i行中,那麼應該怎樣去插入?分兩種情況

1、當這一行中沒有結點的時候,那麼我們直接插入

2、當這一行中有結點的時候我們插入到正確的位置

逐個來分析:

怎麼判定一行中有沒有結點? 記得我們前面對Rhead的初始化嗎? 所有的元素的值都爲NULL,所以我們的判斷條件就是 NULL==M->Rhead[i].

現在我們來解決第二個問題。

怎麼去找到要插入的正確位置。當行中有結點的時候我們無非就是插入到某個結點之前或者之後。那麼我們再回到前面,在我們定義Rhead的時候就說過,某一行的表頭指針指向的就是該行中第一個結點的地址。我們假設該行中已經有了一個結點我們稱它爲A結點,如果要插在A結點之前那麼A結點的列號必定是大於我們輸入的結點(我們稱它爲P結點)的列號的。我們的插入操作就要修改頭指針與p結點的right域。就像鏈表中的插入。那麼當該行中沒有結點的時候我們怎麼去插入?同樣是修改頭指針讓它指向我們的P結點,同樣要修改P結點的right域。看,我們可以利用if語句來實現這兩種條件的判斷。那麼就有了下面的代碼!

  1. if(NULL==M->Rhead[i] || M->Rhead[i]->j>j)       
  2.         {    
  3.             // p插在該行的第一個結點處     
  4.             // M->Rhead[i]始終指向該行的第一個結點                                      p->right = M->Rhead[i];     
  5.             M->Rhead[i] = p;    
  6.         }     

 

現在我們再想一下怎麼去插入到某一個結點的後面? 我們新創建的P結點要插入到現有的A結點的後面,那麼P的列號必定是大於A的列號,那麼我們只要找到第一個大於比P的列號大的結點B,然後插入到B結點之前!如果現有的結點沒有一個結點列號是大於P結點的列號的,那麼我們就應該插入到最後一個結點之後!所以我們首先要尋找符合條件的位置進行插入  

  1. for(q=M->Rhead[i]; q->right && q->right->j < j; q=q->right)     
  2.     ;    
  3.    p->right=q->right; // 完成行插入      
  4.    q->right=p;    


這樣就插入完成了,至於列的指針域的修改和這個類似!現在貼出所有代碼 

 

  1. int CreateSMatrix(CrossList *M)    
  2. {     
  3.  int i, j, m, n, t;    
  4.  int k, flag;    
  5.  ElemType e;    
  6.  OLNode *p, *q;    
  7.      
  8.  if (M->Rhead)    
  9.   DestroySMatrix(M); do {    
  10.   flag = 1;    
  11.   printf("輸入需要創建的矩陣的行數、列數以及非零元的個數");    
  12.   scanf("%d%d%d", &m, &n, &t);    
  13.   if (m<0 || n<0 || t<0 || t>m*n)    
  14.    flag = 0;    
  15.  }while (!flag);    
  16.  M->mu = m;    
  17.  M->nu = n;    
  18.  M->tu = t;    
  19.      
  20.  //創建行鏈表頭數組     
  21.  M->Rhead = (OLink *)malloc((m+1) * sizeof(OLink));    
  22.  if(!M->Rhead)    
  23.   exit(-1);    
  24.  //創建列鏈表頭數組     
  25.  M->Chead = (OLink *)malloc((n+1) * sizeof(OLink));    
  26.  if(!(M->Chead))    
  27.   exit(-1);    
  28.  for(k=1;k<=m;k++) // 初始化行頭指針向量;各行鏈表爲空鏈表      
  29.   M->Rhead[k]=NULL;    
  30.  for(k=1;k<=n;k++) // 初始化列頭指針向量;各列鏈表爲空鏈表      
  31.   M->Chead[k]=NULL;    
  32.  //輸入各個結點     
  33.  for (k=1; k<=t; ++k)    
  34.  {    
  35.   do {    
  36.    flag = 1;    
  37.    printf("輸入第%d個結點行號、列號以及值", k);    
  38.    scanf("%d%d%d", &i, &j, &e);    
  39.    if (i<=0 || j<=0)    
  40.     flag = 0;    
  41.   }while (!flag);  p = (OLink) malloc (sizeof(OLNode));    
  42.   if (NULL == p)    
  43.    exit(-1);    
  44.   p->i = i;    
  45.   p->j = j;    
  46.   p->e = e;    
  47.   if(NULL==M->Rhead[i] || M->Rhead[i]->j>j)     
  48.   {    
  49.    // p插在該行的第一個結點處     
  50.    // M->Rhead[i]始終指向它的下一個結點     
  51.     
  52.    p->right = M->Rhead[i];    
  53.    M->Rhead[i] = p;    
  54.   }    
  55.   else // 尋查在行表中的插入位置     
  56.   {    
  57.    //從該行的行鏈表頭開始,直到找到     
  58.    for(q=M->Rhead[i]; q->right && q->right->j < j; q=q->right)     
  59.     ;    
  60.    p->right=q->right; // 完成行插入      
  61.    q->right=p;    
  62.   }  if(NULL==M->Chead[j] || M->Chead[j]->i>i)     
  63.   {    
  64.    p->down = M->Chead[j];    
  65.    M->Chead[j] = p;    
  66.   }    
  67.   else // 尋查在列表中的插入位置     
  68.   {    
  69.    //從該列的列鏈表頭開始,直到找到     
  70.    for(q=M->Chead[j]; q->down && q->down->i < i; q=q->down)     
  71.     ;    
  72.    p->down=q->down; // 完成行插入      
  73.    q->down=p;    
  74.   }    
  75.  } return 1;    
  76. }    


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章