線性表專題

線性表

線性表是一種簡單的線性結構,特色是在非空的有限集合中,且第一個元素沒有直接前驅元素,最後一個元素沒有直接後繼元素,其餘元素都有惟一的前驅和後繼元素。線性表有順序存儲結構和鏈式存儲結構。node

線性表的順序存儲方式

指將線性表中的各個元素依次存放在一組地址連續的存儲單元中,一般將這種方法存儲的線性表稱爲順序表。
邏輯相鄰,物理存儲地址也相鄰數組

定義以下:markdown

/* ElemType類型根據實際狀況而定,這裏假設爲int */
typedef int ElemType;
/* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef int Status;
/*線性結構使用順序表的方式存儲*/
//順序表結構設計 
typedef struct {
    ElemType *data;
    int length;
}Sqlist;
複製代碼

其中,ElemType表示數據元素類型,data用於存儲線性表中的數據元素的首地址,length用來表示線性表中數據元素的個數,Sqlist是結構體類型名。定義一個順序表代碼:Sqlist L; 指向順序表的指針:Sqlist *L;函數

  • 順序表初始化
Status InitList(Sqlist *L){
    //爲順序表分配一個大小爲MAXSIZE 的數組空間
    L->data =  malloc(sizeof(ElemType) * MAXSIZE);
    // 存儲分配失敗退出
    if(!L->data) exit(ERROR);
    // 初始化,空表長度爲0
    L->length = 0;
    return OK;
}
複製代碼
  • 順序表的插入
/* 初始條件:順序線性表L已存在,1≤i≤ListLength(L); 操做結果:在L中第i個位置以前插入新的數據元素e,L的長度加1 */
Status ListInsert(Sqlist *L,int i,ElemType e){
    // i值不合法判斷
    if((i<1) || (i>L->length+1)) return ERROR;
    // 存儲空間已滿
    if(L->length == MAXSIZE) return ERROR;
    // 插入數據不在表尾,則先移動出空餘位置
    if(i <= L->length){
        for(int j = L->length-1; j>=i-1;j--){
            //插入位置以及以後的位置後移動1位
            L->data[j+1] = L->data[j];
        }
    }
    // 將新元素e 放入第i個位置上
    L->data[i-1] = e;
    // 長度+1;
    ++L->length;
    return OK;
}
複製代碼
  • 順序表的取值
Status GetElem(Sqlist L,int i, ElemType *e){
    //判斷i值是否合理, 若不合理,返回ERROR
    if(i<1 || i > L.length) return  ERROR;
    //data[i-1]單元存儲第i個數據元素.
    *e = L.data[i-1];
    return OK;
}
複製代碼
  • 非空判斷
int ListEmpty(Sqlist L){
    if(L.length == 0) {
        return OK;
    }
    return ERROR;
}
複製代碼
  • 順序表刪除
/* 初始條件:順序線性表L已存在,1≤i≤ListLength(L) 操做結果: 刪除L的第i個數據元素,L的長度減1 */
Status ListDelete(Sqlist *L,int i){
    //線性表爲空
    if(L->length == 0) return ERROR;
    //i值不合法判斷
    if((i<1) || (i>L->length+1)) return ERROR;
    for(int j = i; j < L->length;j++){
        //被刪除元素以後的元素向前移動
        L->data[j-1] = L->data[j];
    }
    //表長度-1;
    L->length --;
    return OK;
}
複製代碼

*清空順序表性能

/* 初始條件:順序線性表L已存在。操做結果:將L重置爲空表 */
Status ClearList(Sqlist *L) {
    L->length=0;
    return OK;
}
複製代碼
  • 順序輸出List
Status TraverseList(Sqlist L) {
    int i;
    for(i=0;i<L.length;i++)
        printf("%d\n",L.data[i]);
    printf("\n");
    return OK;
}
複製代碼
  • 順序表查找元素並返回位置
/* 初始條件:順序線性表L已存在 */
/* 操做結果:返回L中第1個與e知足關係的數據元素的位序。 */
/* 若這樣的數據元素不存在,則返回值爲0 */
int LocateElem(Sqlist L,ElemType e) {
    int i;
    if (L.length==0) return 0;
    for(i=0;i<L.length;i++) {
        if (L.data[i]==e)
            break;
    }
    if(i>=L.length) return 0;
    return i+1;
}
複製代碼

總結

  • 優勢:無須關心表中元素之間的關係,因此不用增長額外的存儲空間;能夠快速地取表中任意位置的元素
  • 缺點:插入和刪除操做須要移動大量元素。使用前需事先分配好內存空間,當線性表長度變化較大時,難以肯定存儲空間的容量。分配空間過大會形成存儲空間的巨大浪費,分配的空間太小,難以適應問題的需求。

線性表的鏈式存儲

在解決實際問題時,有時並不適合採用線性表的順序存儲結構,例如兩個一元多項式的相加、相乘,這就須要另外一種存儲結構——鏈式存儲。它是採用一組任意的連續或非連續存儲單元存儲線性表的元素。爲了表示每一個元素ai與其直接後繼ai+1的邏輯關係,鏈式存儲不只須要存儲元素自己,還要存儲一個指向其直接後繼元素的地址。這種存儲結構被稱之爲結點(node)。存儲元素的叫數據域,存儲地址的叫指針域。結點元素的邏輯順序稱之爲線性鏈表或單鏈表。spa

鏈式存儲最大的特色就是不連續的,因此每一個數據與數據之間的關係是經過指針域來進行鏈接的。設計

由於第一個結點沒有直接前驅結點,所以須要一個頭指針L指向它。爲了方便操做放在第一個元素結點以前一個結點稱之爲頭結點,頭指針變成指向頭結點,其數據域能夠存放如線表長度等信息,而指針域則存放第一個元素結點的地址信息。若該鏈表爲空,則頭結點指針域爲空。 最後一個元素沒有直接後繼元素,因此將其指針域設置爲「Null」空。指針

(圖片來自邏輯教育) code

爲何要使用頭結點?

便於首元結點處理和空表和⾮空表的統一處理orm

用C語言描述以下:

  • 定義結點
typedef struct Node{
    ElemType data;
    struct Node *next;
}Node;

typedef struct Node * LinkList;
複製代碼
  • 初始化單鏈表線性表
Status InitList(LinkList *L) {
    //產生頭結點,並使用L指向此頭結點
    *L = (LinkList)malloc(sizeof(Node));
    
    //存儲空間分配失敗
    if(*L == NULL) return ERROR;
    
    //將頭結點的指針域置空
    (*L)->next = NULL;
    
    return OK;
}
複製代碼
  • 單鏈表插入
/* 初始條件:順序線性表L已存在,1≤i≤ListLength(L); 操做結果:在L中第i個位置以後插入新的數據元素e,L的長度加1; */
Status ListInsert(LinkList *L,int i,ElemType e){
    int j;
    LinkList p,s;
    p = *L;
    j = 1;
    
    //尋找第i-1個結點
    while (p && j < i) {
        p = p-> next;
        ++j;
    }
    
    //第i個元素不存在
    if(!p || j>i) return ERROR;
    
    //生成新結點s
    s = (LinkList)malloc(sizeof(Node));
    
    //將e賦值給s的數值域
    s->data = e;
    
    //將p的後繼結點賦值給s的後繼
    s->next = p->next;
    
    //將s賦值給p的後繼
    p->next = s;
    
    return OK;
}
複製代碼

注意:須要先將p的next賦值給s的next,而後在將p的next指向s,若是操做反了,會形成p的next也就是圖中的Hank老師丟失,形成野指針。

  • 單鏈表取值
/* 初始條件: 順序線性表L已存在,1≤i≤ListLength(L); 操做結果:用e返回L中第i個數據元素的值 */
Status GetElem(LinkList L,int i,ElemType *e){
    
    // j: 計數.
    int j;
    // 聲明結點p;
    LinkList p;
    
    // 將結點p 指向鏈表L的第一個結點;
    p = L->next;
    // j計算=1;
    j = 1;
    
    
    // p不爲空,且計算j不等於i,則循環繼續
    while (p && j < i) {
        //p指向下一個結點
        p = p->next;
        ++j;
    }
    
    // 若是p爲空或者j>i,則返回error
    if(!p || j > i) return ERROR;
    
    // e = p所指的結點的data
    *e = p->data;
    return OK;
}
複製代碼
  • 單鏈表刪除元素
/* 初始條件:順序線性表L已存在,1≤i≤ListLength(L) 操做結果:刪除L的第i個數據元素,並用e返回其值,L的長度減1 */

Status ListDelete(LinkList *L,int i,ElemType *e) {
    int j;
    LinkList p,q;
    p = (*L)->next;
    j = 1;
    
    // 查找第i-1個結點,p指向該結點
    while (p->next && j< (i-1)) {
        p = p->next;
        ++j;
    }
    
    // 當i>n 或者 i<1 時,刪除位置不合理
    if (!(p->next) || (j > i-1)) return  ERROR;
    
    // q指向要刪除的結點
    q = p->next;
    
    // 將q的後繼賦值給p的後繼
    p->next = q->next;
    
    // 將q結點中的數據給e
    *e = q->data;
    
    // 讓系統回收此結點,釋放內存;
    free(q);
    return OK;
}
複製代碼
  • 將L重置爲空表
Status ClearList(LinkList *L) {
    LinkList p,q;
     /* p指向第一個結點 */
    p = (*L)->next;          
  
   /* 沒到表尾 */
    while(p) {
        q=p->next;
        free(p);
        p=q;
    }
    /* 頭結點指針域爲空 */
     (*L)->next=NULL;       
    return OK;
}
複製代碼
  • 單鏈表前插入法
/* 隨機產生n個元素值,創建帶表頭結點的單鏈線性表L(前插法)*/
void CreateListHead(LinkList *L, int n){
    
    LinkList p;
    
    // 創建1個帶頭結點的單鏈表
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;
    
    // 循環前插入隨機數據
    for(int i = 0; i < n;i++) {
        //生成新結點
        p = (LinkList)malloc(sizeof(Node));
       
        //i賦值給新結點的data
        p->data = i;
        
        // p->next = 頭結點的L->next
        p->next = (*L)->next;
        
        //將結點P插入到頭結點以後;
        (*L)->next = p;
    }
}
複製代碼
  • 單鏈表後插入法

/* 隨機產生n個元素值,創建帶表頭結點的單鏈線性表L(後插法)*/
void CreateListTail(LinkList *L, int n){
    
    LinkList p,r;
    // 創建1個帶頭結點的單鏈表
    *L = (LinkList)malloc(sizeof(Node));
    // r指向尾部的結點
    r = *L;
    
    for (int i=0; i<n; i++) {
        
        // 生成新結點
        p = (Node *)malloc(sizeof(Node));
        p->data = i;
        
        // 將表尾終端結點的指針指向新結點
        r->next = p;
        // 將當前的新結點定義爲表尾終端結點
        r = p;
    }
    
    // 將尾指針的next = null
    r->next = NULL;
}
複製代碼

單鏈表與順序表的對比

  1. 存儲方式:順序表用一組連續的存儲單元依次存儲線性表的數據元素;而單鏈表用一組任意的存儲單元存放線性表的數據元素。
  2. 時間性能:採用循序存儲結構時查找的時間複雜度爲O(1),插入和刪除須要移動平均一半的數據元素,時間複雜度爲O(n)。採用單鏈表存儲結構的查找時間複雜度爲O(n),插入和刪除不須要移動元素,時間複雜度僅爲O(1)。
  3. 空間性能:採用順序存儲結構時須要預先分配存儲空間,分配空間過大會形成浪費,太小會形成問題。採用單鏈表存儲結構時,可根據須要進行臨時分配,不須要估計問題的規模大小,只要內存夠就能夠分配,還能夠用於一些特殊狀況,如一元多項的表示。
相關文章
相關標籤/搜索