數據結構與算法學習03-線性表

一、線性表的定義
線性表(List)是由零個或多個數據元素組成的有限數列。
特點:
1.線性表是一個序列,各個元素之間按照順序排列。
2.若元素存在多個,則第一個元素無前驅,最後一個元素無後繼,其他元素都有且只有一個前驅和後繼。
3.線性表具有有限性,它處理的元素是有限的。線性表元素的個數n(n>=0)定義爲線性表的長度,當n=0時稱爲空表。
二、抽象數據類型
數據類型:指一組性質相同的值的集合及定義在此集合上的一些操作的總稱。
抽象數據類型(Abstract Data Type , ADT):是指一個數學模型及定義在該模型上的一組操作。抽象數據類型的定義僅取決於它的邏輯特性,與其在計算及內部如何表示和實現無關。
描述抽象數據類型的格式:

ADT 抽象數據類型名
Data
    數據之間的邏輯關係的定義
Operation
    操作
endADT

三、線性表的抽象數據類型

線性表的基本操作:

ADT 線性表List
Data
    線性表的數據對象集合爲{a1,a2,...,an},每個元素的類型相同。除了第一個元素a1外,每一個元素都有一個直接前驅元素;除了最後一個元素an外,每個元素都有一個直接後繼元素。數據元素之間是一對一的關係。
Operation
    InitList(*L):初始化操作,創建一個空的線性表L。
    ListEmpty(L):判斷線性表是否爲空表,若爲空表返回true;否則返回false。
    ClearList(*L):將線性表清空。
    GetElem(L,i,*e):將線性表的第i個位置的元素返回給e。
    LocateElem(L,e):在線性表L中查找與給定值e相等的元素,如果查找成功,返回該元素在表中的序號表示成功;否則返回0表示失敗。
    ListInsert(*L,i,e):在線性表L中第i個位置插入新元素e。
    ListDelete(*L,i,*e):刪除線性表中第i個元素,並用e返回其值。
    ListLength(L):返回線性表L的元素個數。
endADT

四、線性表的存儲結構及其C語言實現
線性表有兩種存儲結構:順序存儲和鏈式存儲結構。
1.順序存儲結構
概念:
用一段地址連續的存儲單元依次存儲線性表的數據元素。
C語言代碼:

a.定義線性表

#define MAXSIZE 20    //最大存儲容量
typedef int ElemType;    //類型,這裏定義爲整形
typedef struct
{
    ElemType data[MAXSIZE];    //data位置是線性表存儲空間起始位置
    int length;    //當前長度
}SqList;

順序存儲結構中,可以計算出線性表中任意一個位置的地址,計算方法如下:LOC(ai)=LOC(a1)+(i-1)*c(第一個元素爲a1,c爲存儲單元寬度)。存儲時間的性能爲O(1),通常稱爲隨機存儲結構。

b.獲取第i個位置元素值

typedef int Status;

//Status是函數的類型,其值是函數結果狀態代碼,這裏設爲int,返回0表示失敗1表示成功。
//初始條件:線性表L存在,且1<=i<=ListLength(L)。
//操作結果:用e返回第i個元素(a[i-1])的值。

Status GetElem(SqList L,int i,ElemType *e){
    if(L.Length==0||i<1||i>L.Length){return 0;}
    *e=L.data[i-1];
    return 1;
}

c.插入操作(插入到第i個位置)
·如果插入位置不合理,拋出異常。
·如果線性表長度大於等於數組長度,拋出異常或者動態擴容。
·從最後一個元素開始向前遍歷到第i個位置,分別將他們向後移動一個位置。
·將元素插入第i個位置。
·線性表長度+1。

Status ListInsert(SqList *L,int i,ElemType e){
    int k;
    if(L->length==MAXSIZE){return 0;}//線性表已滿
    if(i<1||i>L->length+1){return 0;}//插入位置不在範圍內
    if(i<=L->length){//若插入位置在表中間,則將後面的元素向後移動
        for(k=L->length-1;k>=i-1;k--){
            L->data[k+1]=L->data[k];}   
    }
    L->data[i]=e;//插入
    L->length++;//修改長度
    return 1;
}

d.刪除操作
·如果刪除位置不合理,拋出異常。
·取出刪除元素。
·從刪除位置開始向後遍歷,分別將元素向前移動。
·表長-1。

Status ListDelete(SqList *L,int i,ElemType e){
    int k;
    if(L->length==0){return 0;}//表長爲空,拋出異常
    if(i<1||i>L->length){return 0;}//刪除位置不合理
    *e=L->data[i-1];//取出刪除元素
    if(i<L->length){//移動數據元素
        for(k=i;k<L->length;k++){
            L->data[k-1]=L->data[k];}
    }
    L->length--;//長度減一
    return 1;
}

*插入和刪除操作複雜度分析:
最好情況:待操作元素剛好在最後一位,不需要移動任何元素,複雜度爲O(1)。
最壞情況:待操作元素剛好在第一位,需要移動所有元素,複雜度爲O(n)。
平均情況:O((n-1)/2)=O(n)。

2.鏈式存儲結構
概念:
·用一組任意存儲單元存儲線性表(內存中的任意位置,不要求連續)。除了存儲數據元素信息,還要存儲它的後繼元素的指針(地址)。
·把存儲數據信息部分稱爲數據域,把存儲後繼位置部分稱爲指針域。兩部分信息組成的數據元素稱爲節點(Node)。n個節點連成線性表的線性存儲結構–鏈表。
·鏈表存儲結構不像順序存儲結構那麼集中,數據可以分散在內存的各個角落,它的增長也是動態的,所佔用的空間大小和位置是不需要預先分配和劃定的,可根據系統的情況和實際的需求即時生成。
單鏈表(只包含一個指針域的鏈表稱爲單鏈表)組成:
a.頭指針
·頭指針指向鏈表第一個節點(頭節點)。
·頭指針有標識左右,常用頭指針冠以鏈表名字。
·無論鏈表是否爲空,頭指針均不爲空。
·頭指針是鏈表必要元素。
b.頭結點
·爲了操作的統一和方便而設立,放在第一個元素節點之前。
·頭結點不一定是鏈表的必須要素。
C語言代碼:
a.定義

typedef struct Node
{
    ElemType data;//數據域
    struct Node *Next;//指針域
}Node;
typedef struct Node *LinkList;

p->data的值是一個數據元素;p->next的值是一個指針,指向第i+1個元素。
b.單鏈表的讀取
·聲明一個節點P指向鏈表的第一個節點,初始化j從1開始。
·當j小於i時,讓p的指針向後移動,不斷指向下一個節點j+1。
·若到鏈表末尾p爲空,則說明第i個元素不存在。
·若查找成功,返回節點p的值。

Status GetElem(LinkList L,int i,ElemType *e){
    int j;
    LinkList p; //p爲上面定義的指向節點的指針類型
    p=L->next; //p指向L中第一個節點
    j=1; //計數器初始化
    while(p&&j<i){ //循環遍歷鏈表,直到找到第i個元素
        p=p->next;
        ++j;
    }
    if(!p||j>i){ //沒有找到,拋出異常
        return 0;
    }
    *e=p->data; //將第i個元素的數據值存放到e中
    return 1;
}

單鏈表讀取操作的複雜度分析:
最好情況爲i=1時,不需要遍歷,O(1);最壞爲i=n時,需要遍歷整個鏈表O(n)。
平均情況複雜度爲O(n)。
c.單鏈表的插入(插入到第i個節點後)
·聲明一節點p指向鏈表頭節點,初始化j從1開始。
·遍歷鏈表,向後移動p的指針,j累加1。
·若達到鏈表末尾p爲空,則第i個元素不存在;否則查找成功,生成一個空節點S。
·將數據元素賦值給S->data。
·將S->next指向p->next;將p->next指向s。插入完成。

Status ListInsert(LinkList *L,int i,ElemType e){
    int j;
    LinkList p,s;  //指向節點的指針,新的空節點s
    p=*L; //p初始化,從頭結點開始
    j=1; //j初始化,從1開始
    while(p&&j<i){ //遍歷鏈表找到第i個節點
        p=p->next;
        j++;
    }
    if(!p||j>i){return 0;} //未找到,拋出異常
    s=(LinkList)malloc(sizeof(Node)); //創建新的節點s
    s->data=e; //給s賦值
    s->next=p->next; //插入操作,只需要操作指針
    p->next=s;
    return 1; 
}

d.刪除操作(刪除第i個節點的後繼節點)
·只需要將它的前驅節點的指針指向它(待刪除的節點)的後繼節點即可。遍歷步驟同插入操作相同。

Status ListDelete(LinkList *L,int i,ElemType *e){
    int j;
    LinkList p,q; 
    p=*L; //初始化P,從頭結點開始
    j=1;
    while(p->next&&j<i){ //查找到第i個元素
        p=p->next;
        ++j;
    }
    if(!(p-next)||j>i){return 0;} //未找到,拋出異常
    q=p->next; 
    p->next=q->next; //將第i個元素指針繞過第i+1個元素指向第i+2個元素
    *e=q->data; //待刪除的元素數據值存放到e中
    free(q); //釋放刪除的節點
    return 1;
}

插入和刪除操作的時間複雜度分析:
·插入和刪除操作都是由兩部分組成,即遍歷鏈表查找第i個元素和修改指針。時間複雜度都爲O(n)。
·與順序存儲結構對比,如果只操作一個元素,則無明顯優勢;若一次操作多個元素,順序存儲結構每個元素操作複雜度都爲O(n),而鏈表只有第一次爲O(n),剩下的元素每次只需O(1)(只需要修改指針,不需再次遍歷)。
e.單鏈表的整表創建
·聲明一個節點p和計數變量i。
·初始化一空鏈表L。
·讓L的頭結點的指針指向NULL,即建立一個帶頭結點的單鏈表。
·後繼節點的賦值和插入。

//頭插法建立單鏈表
//總是把新加進來的元素放在表頭後的第一個位置

void CreatListHead(LinkList *L,int n){
    LinkList p; //臨時變量p,用來存放新生成的節點
    int i; 
    srand(time(0));
    *L=(LinkList)malloc(sizeof(Node)); //生成頭結點
    (*L)->next=NULL; 
    for(i=0;i<n;i++){
        //創建新節點
        p=(LinkList)malloc(sizeof(Node)); 
        //用隨機數填充新節點的內容
        p->data=rand()*100; 
        //先讓新節點的next指向頭節點之後,然後讓表頭的next指向新的節點
        p->next=(*L)->next; 
        (*L)->next=p;
    }
}
//尾插法建立單鏈表
void CreatListHead(LinkList *L,int n){
    LinkList p,r; //臨時變量p,用來存放新生成的節點;r用來存放最後一個節點
    int i; 
    srand(time(0));
    *L=(LinkList)malloc(sizeof(Node));
    r=*L; //初始化r指向表的頭結點
    for(i=0;i<n;i++){
        p=(Node *)malloc(sizeof(Node));
        p->data=rand()%100+1;
        r->next=p; //最後一個節點指向新加進來的節點
        r=p; //設置新進來的節點爲最後一個節點
    }
    r->next=NULL;
}

f.單鏈表的整表刪除
·聲明節點p,q。
·將第一個節點賦值給p,下一個節點賦值給q。
·循環執行釋放p和將q賦值給p的操作。

Status ClearList(LinkList *L){
    LinkList p,q;
    p=(*L)->next;
    while(p){
        q=p->next;
        free(p); 
        p=q;
    }
    (*L)->next=NULL;
    return 1;
}

五、線性表不同存儲結構優缺點分析
1.存儲分配方式
·順序存儲結構用一段連續的存儲單元依次存儲線性表的數據元素。
·鏈式存儲結構用一組任意的存儲單元存放線性表的元素。
2.時間性能
·查找
-順序存儲結構O(1)。
-單鏈表O(n)。
·插入和刪除
-順序存儲結構需要平均移動表長一半的元素,複雜度O(n)。
-單鏈表在計算出某位置的指針後,插入和刪除時間僅爲O(1)。
·空間性能
-順序存儲結構需要預先分配存儲空間,太大容易造成浪費,太小則容易溢出。
-單鏈表不需要分配存儲空間,只要有就可以分配,元素個數也不受限制。
3.綜合對比
·若線性表需要頻繁查找,很少進行插入和刪除操作,宜採用順序存儲結構。
·若需要頻繁插入和刪除操作,則宜使用單鏈表。
·當元素個數變化較大或者根本不知道有多大時,最好使用單鏈表結構。
·如果事先知道線性表大致長度,宜使用順序存儲結構。

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