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


表 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。

 

表的簡單數組

對表的所有操作都可以通過使用數組來實現。需要對表的最大值進行預估,估計大了會嚴重的浪費空間。

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

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

 

鏈表

為了避免插入和刪除的開銷,我們允許使用不連續存儲。

鏈表由一系列不必再內存中相連的結構組成。每一個結構均包含有表元素和指向后繼元素的指針。我們稱之為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);   } }


免責聲明!

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



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