1. 程式人生 > >線性結構

線性結構

結構 size 修改 nod 相同 col 單元 不同 技術

線性表及其實現

[引例]:多項式的表示

一元多項式:f(x)=a0+a1X+...+an-1Xn-1+anXn

主要運算:多項式的相加、相減、相乘等。

[分析]如何表示多項式?

多項式的關鍵數據:

  • 多項式的項數n
  • 各項系數ai及指數i

方法1:順序存儲結構直接表示

數組各分量對應多項式的各項:

a[i]:項Xi的系數ai,i是對應的指數

例如:f(x)=4x5-3x2+1,表示成:

0 1 2 3 4 5 ...... 下標i

1 0 -3 0 0 4 ......

a[i]

1 -3x2 4x5

以這樣的形式表示後,兩個多項式進行相加時,直接用兩個數組對應分量相加即可。

用這個方法有一個問題,比如,如何表示多項式x+3x2000?

  顯然,至少要用2001個長度的數組,但其本身只有兩項在起作用,不僅造成的空間極大浪費,而且運算過程中要進行循環,含有很多0項,比較繁瑣。

方法2:順序存儲結構表示非零項

每個非零項aixi涉及兩個信息:系數ai和指數i

可以將一個多項式看成是一個系數和指數(ai,i)的二元組的集合

結構數組表示:數組分量是由系數ai、指數i組成的結構,對應一個非零項

例如:P1(x)=9x12+15x8+3x2和P2(x)=26x19-4x8-13x6+82

技術分享

用這種方式來表示,只需要表示非零項即可。為了計算方便,需要按指數大小有序存儲

相加過程:從頭開始比較兩個多項式當前對應項的指數

P1:(9,12),(15,8),(3,2)

P2:(26,19),(-4,8),(-13,6),(82,0)

先看兩個多項式的第一項(9,12)和(26,19),由於指數19比12大,那麽就將(26,19)輸出,

然後將(9,12)與P2的第二項(-4,8)進行比較,由於12比8大,所以將(9,12)輸出,

然後將(-4,8)與P1的第二項(15,8)進行比較,此時兩者指數相同,則將系數相加得到(11,8)輸出,

以此類推,當其中一個多項式結束之後,另一個多項式未完成的項直接輸出即可。

===>P3:(26,19),(9,12),(11,8),(-13,6),(3,2),(82,0)

即P3

(x)=26x19+9x12+11x8-13x6+3x2+82

方法3:鏈表結構存儲非零項

鏈表中每個結點存儲多項式中的一個非零項,包括系數和指數兩個數據域以及一個指針域

coef expon link

typedef struct PolyNode *Polynomial;
struct PolyNode{
     int coef;//系數
     int expon;//指數
     Polynomial link;//指針域  
}
//定義一個結構體

上述的例子中,兩個多項式的鏈表存儲形式可以表示為:

技術分享

它的加法運算過程和方法2的過程是一樣的


什麽是線性表?

由前面關於多項式的引例可以得到如下啟示:

  • 同一個問題可以有不同的表示(存儲)方法
  • 有一類共性問題:有序線性序列的組織和管理

線性表[Linear List]”:由同類型數據元素構成有序序列的線性結構

  • 表中元素個數稱為線性表的長度
  • 線性表沒有元素時,稱為空表
  • 表起始位置稱表頭,表結束位置稱為表尾

線性表的抽象數據類型描述

  • 類型名稱:線性表(List)
  • 數據對象集:線性表是n(≥0)個元素構成的有序序列(a1,a2,...,an)
  • 操作集:線性表L€List,整數i表示位置,元素X€ElementType

線性表基本操作主要有:

  1. List MakeEmpty():初始化一個空線性表L;
  2. ElementType FindKth(int K,List L):根據位序K,返回相應元素;
  3. int Find(ElementType X,List L):在線性表L中查找X的第一次出現的位置
  4. void Insert(ElementType X,int i,List L):在位序前插入一個新元素X;
  5. void Delete(int i,List L):刪除指定位序i的元素;
  6. int Length(List L):返回線性表L的長度n。

線性表的順序存儲實現:利用數組連續存儲空間順序存放線性表的各元素

技術分享

typedef struct LNode *List;
struct LNode{
    ElementType Data[MAXSIZE];
    int Last;//代表線性表的最後一個元素
};
struct LNode L;
List PtrL;//PtrL為線性表結構的指針

訪問下標為i的元素:L.Data[i]或Ptrl->Data[i]

線性表的長度:L.Last+1或Ptrl->Last+1

主要操作的實現:

1.初始化(建立空的順序表)

List MakeEmpty(){
    List PtrL;
    PtrL = (List)malloc(sizeof(struct LNode));//申請一個結構
    PtrL->Last=-1;//Last表示鏈表的最後一個元素,當last為0時,表示含有一個元素,沒有元素就賦值為-1
    return PtrL;  //返回結構的指針
}

2.查找

int  Find(ElementType X,List PtrL){
    int i=0;
    while(i<=PtrL->Last && PtrL->Data[i]!=X){
          i++;
    }
     if(i>PtrL->Last)   
          return -1;//如果沒有找到,返回-1
     else 
          return i;//找到後返回的是存儲位置
}

查找成功的平均次數是(n+1)/2,平均時間性能為O(n)。

3.插入(第i(1≤i≤n+1)個位置上插入一個值為X的新元素)

技術分享

只能先移動最後一個元素

void Insert(ElementTyepe X,int i,List PtrL){
    int j;
    if(PtrL->Last == MAXSIZE-1){
         printf("表空間已滿,不能插入");
         return;
    }
    if(i<1 || i>PtrL->Last+2){
         printf("位置不合法");
    }
    for(j=PtrL->Last;j>=i-1;j--)
         PtrL->Data[j+1] = Ptrl->Data[j];//將ai~an倒序向後移動
    PtrL->Data[i-1]=X;//新元素插入
    PtrL->Last++;//Last仍指向最後元素
    return;
}

平均移動次數為n/2,平均時間性能為O(n)

4.刪除(刪除表的第i(1<= i<= n)個位置上的元素)

技術分享

void Delete(int i,List PtrL){
     int j;
     if(i<1 || i>PtrL->Last+1){//檢查空表及刪除位置的合法性
          printf("不存在第%d個元素",i);
          return;
     };
     for(j=i;j<=PtrL->Last;j++)
          PtrL->Data[j-1]=PtrL->Data[j];//將ai+1~an順序向前移動
     PtrL->Last--;//Last仍指向最後元素
     return;
}

平均移動次數是(n-1)/2,平均時間性能為O(n)


線性表的鏈式存儲實現

  • 不要求邏輯上相鄰的兩個元素物理上也相鄰,通過“鏈”建立起元素之間的邏輯關系。
  • 插入、刪除不需要移動數據元素,只需修改“鏈”

技術分享

typedef struct LNode *List;
//每個結點都是一個結構
struct LNode{ ElementType Data;//結點的數據 List Next;//下一個結點的位置 }; struct LNode L; List PtrL;

主要操作實現:

1.求表長

int Length(List PtrL){
    List p=PtrL;//p指向表的第一個結點
    int j=0;
    while(p){
         p=p->Next;
         j++;//當前p指向的是第j個結點
    }  
    return j;
}

平均時間性能:O(n)

2.查找

(1)按序號查找:FindKth;

List FindKth(int K,List PtrL){
     List p=PtrL;
     int i=1;
     while(p!=NULL && i<K){
          p=p->Next;
          i++;  
    }
     if(i==K)  
          return p;//找到第K個,返回指針
     else 
          return NULL;//否則返回空
}

(2)按值查找:Find

List Find(ElementType X,List PtrL){
     List p=PtrL;
     while(p!=NULL && p->Data!=X){
           p=p->Next;
     }
     return p;
}

平均時間性能:O(n)

3.插入(在第i-1(1≤i≤n+1)個結點後插入一個值為X的新結點)

(1)先構造一個新結點,用s指向;

(2)再找到鏈表的第i-1個結點,用p指向

(3)然後修改指針,插入節點(p之後插入新結點是s)

技術分享

技術分享

執行順序:(1)s->Next=p->Next;(2)p->Next=s;

思考:修改指針的兩個步驟如果交換一下,將會發生什麽?

如果語句執行順序為:(1)p->Next=s;(2)s->Next=p->Next,則

技術分享

將會導致s->Next指向其本身。

List Insert(ElementType X,int i,List PtrL){
     List p,s;
     if(i==1){//新結點插入在表頭
          s=(List)malloc(sizeof(struct LNode))//申請、填裝結點
          s->Data=X;
          s->Next=PtrL;
          return s;//返回新表頭指針
     }  
     p=FindKth(i-1,PtrL);//查找第i-1個結點
     if(p==NULL){//第i-1個不存在,不能插入
          printf("參數i錯誤");
          return NULL;
     }else{
          s=(List)malloc(sizeof(struct LNode));//申請、填裝結點
          s->Data=X;
          s->Next=p->Next;//新結點插入在第i-1個結點的後面
          p->Next=s;
          return PtrL;
     }
}

平均時間性能是:O(n/2)

4.刪除(刪除鏈表的第i(1≤i≤n)個位置上的結點)

(1)先找到鏈表的第i-1個結點,用p指向;

(2)再用指針s指向要被刪除的結點(p的下一個結點);s=p->Next;

(3)然後修改指針,刪除s所指結點;p->Next=s->Next;

技術分享

(4)最後釋放s所指結點的空間;free(s),這樣內存空間才不會泄露。

技術分享

思考:操作指針的幾個步驟如果隨意改變,將會發生什麽?

s->Next將會指向其本身。

List Delete(int i,List PtrL){
    List p,s;
    if(i==1){//若要刪除的是第一個結點
        s=PtrL;//s指向第一個結點
        if(PtrL!=NULL)
            PtrL=PtrL-Next;//從鏈表中刪除
        else 
            return NULL;
        free(s);//釋放被刪除結點
        return PtrL;
   }
    p=FindKth(i-1,PtrL);//查找第i-1個結點
    if(p==NULL){
        printf("第%d個結點不存在",i-1);
        return NULL;
    }else if(p->Next==NULL){
        printf("第%d個結點不存在",i);
        return NULL;
    }else{
        s=p->Next;//s指向第i個結點
        p->Next=s->Next;//從鏈表中刪除
        free(s);//釋放被刪除結點
        return PtrL;
    }
}

平均時間復雜性:O(n/2)


廣義表

[例]我們知道了一元多項式的表示,那麽二元多項式又該如何表示?

比如,給定二元多項式:P(x,y)=9x12y2+4x12+15x8y3-x8y+3x2

[分析]可以將上述二元多項式看成關於x的一元多項式:P(x,y)=(9y2+4)x12+(15y3-y)x8+3x2它的系數不再是常量,而是一個關於y的多項式。

所以,上述二元多項式可以用“復雜”鏈表表示為:

技術分享

廣義表(Generalized List)定義:

  • 廣義表是線性表的推廣
  • 對於線性表而言,n個元素都是基本的單元素
  • 廣義表中,這些元素不僅可以是單元素也可以是另一個廣義表

在廣義表構造時,我們會遇到這樣一個問題:一個域有可能是一個不能分解的單元素,有可能是一個指針,為了解決這個問題,C語言提供了Union

typedef struct GNode *GList;
struct GNode{
     int tag;//標識域:0表示結點是單元素,1表示結點是廣義表
     union{//子表指針域Sublist與單元素數據域Data復用,即共用存儲空間
          ElementType Data;
          GList Sublist;
     }URegion;
     GList Next;//指向後繼結點  
}

技術分享

通過Tag來區分到底是Data還是SubList


多重鏈表

多重鏈表:鏈表中的節點可能同時隸屬於多個鏈

  • 多重鏈表中結點的指針域會有多個,如前面例子中包含了Next和SubList兩個指針域;
  • 但包含兩個指針域的鏈表並不一定是多重鏈表,比如雙向鏈表不是多重鏈表

多重鏈表有廣泛的用途:基本上如樹、圖這樣相對復雜的數據結構都可以采用多重鏈表的方式實現存儲。

【例】矩陣可以用二維數組表示,但二維數組表示有兩個缺陷

  • 一是數組的大小需要事先確定
  • 對於“稀疏矩陣”,將造成大量的存儲空間浪費

技術分享

【分析】采用一種典型的多重鏈表——十字鏈表來存儲稀疏矩陣

  • 只存儲矩陣非0元素項
    • 結點的數據域:行坐標Row、列坐標Col、數值Value
  • 每個結點通過兩個指針域,把同行、同列串起來
    • 行指針(或稱為向右指針)Right
    • 列指針(或稱為向下指針)Down

矩陣A的多重鏈表圖:存在兩種結構類型

第一種:Term類型:它包含兩個指針,一個指向同一行,一個指向同一列。同一行和同一列都設計成一個循環鏈表。

技術分享

第二種:Head類型:作為行(列)鏈表的頭結點

技術分享

左上角的Term作為整個稀疏矩陣的入口,

  • 4——表示稀疏矩陣的行數
  • 5——表示稀疏矩陣的列數
  • 7——表示稀疏矩陣含有的非零項數

用一個標誌域Tag來區分頭結點和非0元素結點:

頭結點的標識值為“Head”,矩陣非0元素結點的標識值為“Term”。

技術分享 技術分享 技術分享

線性結構