常見的基本數據結構——表

表 ADT

形如A1,A2,A3,.....,An這樣的表。這個表的大小是n,大小爲0的表爲空表。算法

對於除空表外的任何表,咱們說A[i+1]後繼A[i]而且A[i-1]前驅A[i]。表中的第一個元素A[1]不定義前驅,最後一個元素A[N]不定義後繼。數組

表ADT上面的操做:PrintList,MakeEmpty,Find,FindKth,Insert,Delete。函數

 

表的簡單數組spa

對錶的全部操做均可以經過使用數組來實現。須要對錶的最大值進行預估,估計大了會嚴重的浪費空間。指針

數組實現使得PrintList和MakeEmpty以線性時間執行,而FindKth花費常數時間,可是插入和刪除的代價code

是昂貴的。插入須要將後面的元素進行向後移動,刪除須要將後面的元素向前移動。平均來看,仍是以線性時間執行。blog

 

鏈表排序

爲了不插入和刪除的開銷,咱們容許使用不連續存儲。ip

鏈表由一系列沒必要再內存中相連的結構組成。每個結構均包含有表元素和指向後繼元素的指針。咱們稱之爲Next指針。最後的Next指針指向NULL。內存

 

鏈表上面的操做

對於PrintList和Find只需間指針傳遞到該表的第一個元素,而後用一些Next指針傳遞便可,此時的時間是線性的。刪除命令能夠經過修改指

針來實現,插入命令須要申請空間調用mallo函數,而後調整兩次指針。

下面是鏈表的相關操做的具體實現

 

鏈表的類型聲明

struct Node;
typedef struct Node *PtrToNode;
typedef PtrToNode List;
typedef PtrToNode Position;

struct Node{
     ElementType Element;
     Position Next;
};

 

判斷鏈表是否爲空

int
IsEmpty(List L){
     return L->Next == NULL;
}

 

判斷當前是否爲鏈表的末尾位置

int
IsLast(Position P,List L){
     return P->Next == NULL;
}

 

Find查找函數

Position
Find(ElementType X, List L){
    Position p;
    P = L->Next;
    while(p != NULL && p->Element != X){
        p = p->Next;
    } 
     return p;
}

 

鏈表的刪除操做

void 
Delete(ElementType X, List L){
    Position p, TepCell;
    p = FindPrevious(X);
    if(!IsLast(P, L)){
        TmpCell = p->Next;
        p->Next = TmpCell->Next;
        free(TmpCell);
    } 
}

Position
FindPrevious(ElementType X, List L){
    Position P;
    P = L;
    while(P->Next != NULL && P->Next->Element != X){
        p=p->next;
    }
    return P;
}
    

 

鏈表的插入操做

void
Insert(ElementType X, List L, Position P){
    Position TmpCell;
    TmpCell = malloc(sizeof(Struct Node));
    if(TMpCell == NULL){
        FatalError(」out of space」);
    }
    TmpCell->Element = X;
    TmpCell->Next = P->Next;
    P->Next = TmpCell;
}

注意上述的方法中的參數,雖然有些參數在函數中沒有使用,可是之因此這麼作是由於別的實現方法可能會用上。

對於上述的操做,最壞的狀況是掃描整個表,平均來看運行時間是O(N),由於必須平均掃描半個表。

 

常見的錯誤

當指針爲空時,指向的是非法空間,使用指針的屬性或者操做時將會產生錯誤,不管什麼時候只要你肯定一個指向,就必需要保證該指針不是NULL。

刪除表的不正確的作法

void
DeleteList(List L){
    Position P, Tmp;
    P = L->Next;
    L->Next = NULL;
    while(P != NULL){
        Tmp = P->Next;
        free(P);
        P = Temp;
    }
}

 

雙鏈表

有時候以倒序掃描鏈表很方便,標準的實現方法倒是無能爲力了,然而解決辦法很簡單,就是在數據域附加上一個域,使它包含指向前一個單元的指針便可。其附加的開銷是它增長了空間,同時插入

和刪除的開銷增長了一倍,由於有更多的指針須要定位。另外一方面,它簡化了刪除操做,再也不使用指向前驅的的指針來訪問關鍵字。

 

循環鏈表

讓最後的元素反過來指向第一個元素是一種流行的作法,如有表頭,則最後的元素就指向表頭,而且它還能夠是雙向鏈表。

 

多項式ADT

咱們用表來定義一種一元(具備非負次冪)多項式的的抽象數據類型。若是多項式的大部分系數非零,那麼能夠用一個簡單的數組來存儲這些係數。

多項式ADT的數組實現類型聲明

typedef struct{
    int Coeffarray[MaxDegree + 1];
    int HighPower;
}* Polynomial;

//將多項式初始化爲0的過程
void
ZeroPolynomial(Polynomial Poly){
    int i;
    for(i=0; i<=MaxDegree; i++){
        Poly->CoffArray[i] = 0;
    }
    Poly->HighPower = 0;
}

兩個多項式相加

void 
AddPolynomial(const Polynomial Poly1, const Polynomial Poly2, Polynomial PolySum){
    int i;
    ZeroPolynomial(PolySum)
    PolySum->HighPower = Max(Poly1->HighPower, Poly2->HighPower);
    for(i=PolySum->HighPower; i>=0; i++){
       PolySum->CoeffArray[i] = Poly1->CoeffArray[i] + Poly2->CoeffArray[i];
    }
}

兩個多項式相乘的過程

void
MultPolynomial(onst Polynomial Poly1, const Polynomial Poly2, Polynomial PolyProd){
  int i, j;
  ZeroPolynomial(PolyProd);
  PolyProd->HighPower = Poly1->HighPower + Poly2->HighPower;
  if(PolyProd->HighPower > MaxDegree){
    Error(Except array size);
  }else{
    for(i=0; i<=Poly1->HighPower; i++){
      for(j=0; j<=Poly2->HighPower; j++){
        PolyPord->CoeffArray[i + j] = Poly1->CoeffArray[i] * Poly2->CoeffArray[j];
      }
    }
  }
}

多項式的另外一種表示方法:

經過單鏈表的方式表示多項式,多項式的每一項保存在鏈表元素中,而且按照冪的大小間須降序進行排列。

多項式的表示多用於較稀疏的多項式的狀況,須要注意的操做時,當鏈表的多項式相乘時,須要進行多項式的合併同類型。

typedef struct Node *PtrToNode;
struct Node{
  int Coefficient;
  int Expoent;
  PtrToNode Next;
};
typedef PtrToNode Polynomial;

 

基數排序

將鏈表用於基數排序(radix sort),基數排序有時也稱爲卡式排序,由於在現代計算機出現以前,它一直用於老式穿孔卡的排序。

若是咱們有N個整數,它的範圍是從1到M(或者是0到M-1),咱們能夠利用這個信息獲得一種快速排序,叫作桶式排序。咱們留置一個數組稱爲Count,大小爲M,並初始化爲0。那麼,Count有M個單元(桶),開始時他們都是空的。當Ai被讀入時令Count[Ai]+1。當全部的輸入結束後,掃描Count數組,打印好排序的數組。該算法花費的時間是O(M+N),若是M=N,那麼桶排序爲O(N)。

基數排序是這種方法的推廣。下面的這個例子就是詳細的說明,設咱們有10個數,範圍是0到999之間,我對其排序。通常來講,這是0到N^P-1之間的N個數,p是某個常數。顯然咱們不適合使用桶排序,由於這樣桶太多了。因而咱們的策略是使用屢次桶排序,經過用最低位優先的方式,進行桶排序,算法將獲得正確的結果。若是使用最高位將會出現錯誤,並且還沒法判斷最高位。固然,有時會有多個數落入到桶中,並且這些數仍是不一樣的,所以咱們須要使用一個表來保存,由於全部的數字均可能有某位,因此用簡單數組來保存的話,數組的空間需求是O(N^2)。

下面是10個數的桶式排序的具體例子。本例的輸入是:64,8,216,512,27,729,0,1,343,64。爲了是問題簡化,此時操做按基是10進行,第一遍桶排序的結果是0, 1, 512, 343, 64, 125, 216, 27, 8, 729。在使用次低位對第一遍桶排序的結果進行排序,獲得第二次桶排序的結果:0,1,8,512,216,125,27,729,343,64。最後按照最高位進行排序,最後獲得的表:0,1,8,27,64,125,216,343,512,729。

第一趟桶排序結果

0

1

512

343

64

125

316

27

8

729

0

1

2

3

4

5

6

7

8

9

第二趟桶排序結果

8

1

0

 

216

512

729

27

125

 

 

 

343

 

 

 

64

 

 

 

0

1

2

3

4

5

6

7

8

9

第三趟桶排序結果

67

27

8

1

0

 

 

 

 

125

 

 

 

 

216

 

 

 

 

343

 

 

 

 

 

512

 

 

 

 

 

729

 

 

0

1

2

3

4

5

6

7

8

9

 

在使用數組進行實現時,下面的算法的步驟:

  1.計算待排序數組的最大位數。

  2.對桶數組和下表數組進行初始化,根據位數進行循環。

  3.根據當前的位數,對待排數組進行遍歷,計算出當前位的數值,根據數值將其放入對應的桶數組中,並更新下表數組。

  4.將桶數組中的元素更新到待排數組中,

  5.更新位數計算,跳轉至2步驟,直至位數大於最大位數。

該排序算法的時間複雜度是O(P(N+B)),其中P是排序趟數,N是要被排序的元素的個數,B是桶數。該算法的一大缺點是不能用於浮點數的排序。

當咱們把32位機器所能即是的全部整數進行排序時,假設咱們在大小爲2^11的桶分三趟進行便可,而且算法老是O(N)的時間消耗。

 

多重表

常見的多重鏈表是十字鏈表,十字鏈表的特色是對於二維的數據,它也可以經過不連續的空間進行表達,即水平方向和垂直方向都是都是鏈表,且能夠是循環鏈表。

鏈表的遊標實現

在一些不支持指針的語言中,若是須要鏈表而不能使用指針的話,那麼遊標法是一種實現方式。

鏈表的指針實現的重要特色

1.數據存儲在一組結構體中,每一個結構體含有指向下一個結構體的指針。

2.一個新的結構體能夠經過調用malloc從系統中獲得,並經過調用free而被釋放。

遊標實現必需要知足上述條件,條件一能夠經過全局結構體數組實現,經過數組下標表明一個地址。

下面是遊標的實現

struct Node{
  ElementType Element;
  Position Next;
}
struct Node CursoSpace[SpaceSize];

爲了模擬條件二,讓CursorSpace數組中的單元代替malloc和free的職能。爲此,咱們保留一個表,表由再也不任何表中的元素構成。該表將用0做爲表頭,對於Next,0的值爲NULL,爲了執行malloc功能,將第一個元素從freelist中刪除,爲了執行free功能,咱們將該單元放在freelist的前段。

下面是malloc函數和free函數的實現

static Position
CoursorAlloc(void){
  Position P;
  P = CoursorSpace[0].next;
  CursirSpace[0].Next = CursorSpace[P].next;
  return P;
}
static void
CursorFree(Position P){
  CursorSpace[P].next = CursorSpace[0].next;
  CursorSpace[0].next = P;
}

下面是一個鏈表遊標實現的列表:

slot

Element

Next

0

-

6

1

b

9

2

f

0

3

header

7

4

-

0

5

header

10

6

-

4

7

c

8

8

d

2

9

e

0

10

a

1

slot

Element

Next

0

-

6

1

b

9

2

f

0

3

header

7

4

-

0

5

header

10

6

-

4

7

c

8

8

d

2

9

e

0

10

a

1

判斷鏈表是否爲空
int IsEmpty(List L){
  return CursorSpace[L].Next == 0;
}
判斷P是不是鏈表的末尾
int IsLast(Position P, List L){
  return CursorSpace[P].Next == 0; 
}

遊標的Find實現
Position Find(Element X, List L){
  Position P;
  P = CursorSpace[L].Next;
  while(P && CursorSpace[P].Element != X){
    P = CursorSpace[P].Next;
  } 
  reutrn P;
}

鏈表的delete操做
void Delete(Element X, List L){   Position P, TmpCell;   P = FindPrevious(X, L);   if(!IsLast){     TmpCell = CursorSpace[P].Next;     CursorSpace[P].Next = CursorSpace[TmpCell].Next;     CursorFree(TmpCell);   } }
相關文章
相關標籤/搜索