大話數據結構學習筆記 - 線性表、順序存儲、單鏈表及靜態鏈表

大話數據結構學習筆記 - 線性表、順序存儲、單鏈表及靜態鏈表

定義

線性表(List): 零個或多個數據元素的有限序列

數學定義: 若將線性表記爲(a1,...,ai1,ai,ai+1,...,an) , 則表中 ai1 領先於 aiai 領先於 ai+1 , 稱 ai1ai 的直接前驅元素, ai+1ai 的直接後繼元素。 當有i=1,2,...,n1 時, ai 僅有一個直接後繼, 當 i=2,3,...,n 時, ai 有且僅有一個直接前驅

線性表的抽象數據類型

ADT 線性表(List)
Data

​ 線性表的數據對象集合爲 {a1,a2,...,an} , 每個元素的類型均爲DataType。 其中, 除第一個元素 a1 外,每一個元素有且只有一個直接前驅元素,除了最後一個元素 an 外,每一個元素有且只有一個直接後繼元素。數據元素之間的關係是一對一的關係

Operation

​ InitList(*L); 初始化操作, 建立一個空的線性表 L。

​ ListEmpty(L); 若線性表爲空,返回 true, 否則返回 false

​ ClearList(*L); 將線性表清空

​ GetElem(L, i, *e); 將線性表L中的第i個位置元素值返回給e

​ LocateElem(L, e); 在線性表L中查找與給定值e相等的元素,如果查找成功,返回鈣元素在表中序號表

​ 示成功;否則返回0表示失敗

​ ListInsert(*L, i, e); 在線性表L中的第i個位置插入新元素e

​ ListDelete(*L, e, *e); 刪除線性表L中第i的位置元素, 並用e返回其值

​ ListLength(L); 返回線性表L的元素個數

endADT

線性表的順序存儲結構

即用一段地址連續的存儲單元依次存儲線性表的數據元素

#define MAXSIZE 20  // 存儲空間初始分配量
typedef int ElemType;  // ElemType 類型根據實際情況而定, 這是設爲 int
typedef struct
{
    ElemType data[MAXSIZE];  // 數組存儲數據元素, 最大值爲 MAXSIZE
    int length;  // 線性表當前長度
} SqList;

由上可知,順序存儲結構需要三個屬性

  • 存儲空間的起始位置:數組data, 它的存儲位置就是存儲空間的存儲位置
  • 線性表的最大存儲容量:數組長度MaxSize
  • 線性表的當前長度:length

順序存儲結構的插入操作

插入算法的思路:

  • 如果插入位置不合理,拋出異常
  • 如果線性表長度大於等於數組長度,則拋出異常或動態增加容量
  • 從最後一個元素開始向前遍歷到第i個位置,分別將它們都向後移動一個位置
  • 將要插入元素填入位置i
  • 表長加1
// 操作結果:在 L 中第 i 個位置插入新的數據元素 e, L 的長度加 1
Status ListInsert(SqList *L, int i, ElemType e)
{
    if(L->length == MAXSIZE)  // 順序線性表已滿
        return ERROR;
    if(i < 1 || i > L->length + 1)  // 當插入位置 i 不在範圍內時
        return ERROR;
    if(i <= L->length)  // 若插入位置不在表尾
    {
        for(int k = L->length - 1; k >= i - 1; k--)  // 將要插入位置後數據元素向後移動一位
            L->data[k + 1] = L->data[k];
    }
    L->data[i - 1] = e;  // 插入新元素
    L->length++;
    return OK;
}

順序存儲結構的刪除操作

刪除算法的思路:

  • 如果刪除位置不合理,拋出異常
  • 取出刪除元素
  • 從刪除元素位置開始遍歷到最後一個位置,分別將它們都向前移動一個位置
  • 表長減1
Status ListDelete(SqList *L, int i, ElemType *e)
{
    if(L->length == 0)
        return ERROR;
    if(i < 1 || i > L->length)
        return ERROR;
    *e = L->data[i - 1];
  if(i < L->length)
  {
    for(int j = i - 1; j < L->length - 1; j++)
        L->data[j] = L->data[j + 1];
  }
    L->length--;
}

線性表順序存儲結構的優缺點

linear_list_sequential_storage_structure_advantages_and_disadvantages

線性表的鏈式存儲結構

定義

爲了表示每個數據元素 ai 與其直接後繼數據元素 ai+1 之間的邏輯關係,對數據元素ai 來說, 除了存儲其本身的信息之外,還需存儲一個指示其直接後繼的信息(即直接後繼的存儲位置)。我們把存儲數據元素信息的域成爲數據域,把存儲直接後繼位置的域稱爲指針域。指針域中存儲的信息稱作指針或鏈。這兩部分信息組成數據元素ai 的存儲映像,稱爲結點(Node)

n個結點(ai 的存儲映像)鏈結成一個鏈表,即爲線性表(a1,a2,...,an )的鏈式存儲結構, 因爲此鏈表的每個節點中只包含一個指針域,所以叫單鏈表

鏈表中第一個結點的存儲位置叫做頭指針。單鏈表的第一個結點前附設一個結點,稱爲頭結點

頭指針與頭結點的異同

head_pointer_and_head_node_same_and_diff

單鏈表

  • 無頭結點的單鏈表

    single_link_list_without_head_node

  • 有頭結點的單鏈表

    single_link_list_with_head_point

單鏈表結構

結點由存放數據元素的數據域以及存放後繼結點地址的指針域組成

typedef struct Node
{
    ElemType data;
    struct Node *next;
}Node;
typedef struct Node *LinkList;

單鏈表的插入

單鏈表第i個數據插入節點的算法思路:

  • 聲明一個結點p指向鏈表第一個結點, 初始化j1開始
  • j < i時,就遍歷鏈表, 讓p的指針向後移動, 不斷指向下一結點, j累加1
  • 若到鏈表末尾p爲空,則說明第i個元素不存在
  • 否則查找成功,在系統生成一個空結點s
  • 將數據元素e賦值給s->data
  • 單鏈表的插入標準語句s->next = p->next; p->next = s
  • 返回成功
// 操作結果:在 L 中第 i 個位置之前插入新的數據元素 e, L 的長度加 1
Status ListInsert(LinkList *L, int i, ElemType e)
{
    LinkList p, s;
    p = *L;
    int j = 1;
    while(p && j < i)  // 尋找第 i 個結點
    {
        p = p->next;
        ++j;
    }
    if(!p || j > i)  // 第 i 個元素不存在
        return ERROR;
    s = (LinkList)malloc(sizeof(Node));  // 生成新結點
    s->data = e;
    s->next = p->next;  // 將 p 的後繼結點賦值給 s 的後繼
    p->next = s;  // 將 s 賦值給 p 的後繼
    return OK;
}

單鏈表的刪除

單鏈表第i個數據刪除結點的算法思路:

  • 聲明一個結點p指向鏈表第一個結點,初始化j1開始
  • j < i時,遍歷鏈表,讓p的指針向後移動,不斷指向下一個結點,j累加1
  • 若到鏈表末尾p爲空,則說明第i個元素不存在
  • 否則查找成功,將與刪除的結點p->next賦值給q, 即p爲欲刪除結點的上一結點,q爲欲刪除結點
  • 單鏈表的刪除標準語句p->next = q->next
  • q結點中的數據賦值給e,返回
  • 釋放q結點
  • 返回成功
Status ListDelete(LinkList *L, int i, ElemType *e)
{
    LinkList p, q;
    p = (*L);
    int j = 1;
    while(p->next && j < i)
    {
        p = p->next;
        ++j;
    }
    if(!(p->next) || j > i)  // 第 i 個元素不存在
        return ERROR;
    q = p->next;
    p->next = q->next;  // 將 q 的後繼賦值給 p 的後繼
    *e = q->data;  // 將 q 結點中的數據賦值給 *e
    free(q);  // 讓系統回收此節點,釋放內存
    return OK;
}

單鏈表結構與順序存儲結構優缺點

靜態鏈表

用數組描述的鏈表叫做靜態鏈表,也叫作遊標實現法,是爲了給某些沒有實現指針的編程語言設計的類似單鏈表

// 線性表的靜態鏈表存儲結構
#define MAXSIZE 1000
typedef struct
{
    ElemType data;
    int cur;  // 遊標,爲 0 時表示無指向
}Component, StaticLinkList[MAXSIZE];

另外數組第一個和最後一個元素作爲特殊元素處理,不存數據。我們通常把未被使用的數組元素稱爲備用鏈表。而數組第一個元素,即下標爲0的元素的cur就存放備用鏈表的第一個結點的下標;而數組的最後一個元素的cur則存放第一個有數值的元素的下標,相當於單鏈表中的頭結點作用,當整個鏈表爲空時,則爲0。下圖所示相當於初始化的數組狀態。

static_linked_list_abstract

靜態鏈表的插入

由於靜態鏈表中操作的是數組,而不是動態鏈表中的結點申請和釋放問題。故需實現申請結點和釋放結點的功能,方法是將所有未被使用的及被刪除的分量用遊標鏈接成備用鏈表,插入時,從備用鏈表取得第一個結點作爲待插入的新節點

// 若備用鏈表非空,則返回分配的結點下標,否則返回 0
int Malloc_SLL(StaticLinkList space)
{
    int i = space[0].cur;  // 獲取備用鏈表的第一個結點,即存放在數組第一個元素中的 cur
    if(space[0].cur)
        space[0].cur = space[i].cur;  // 備用鏈表第一個結點被申請使用,故後移一個
    return i;
}

// 在 L 中第 i 個元素之前插入新的數據元素 e
Status ListInsert(StaticLinkList L, int i, ElemType e)
{
    int j, k, l;
    k = MAXSIZE - 1;
    if(i < 1 || i > ListLength(L) + 1)
        return ERROR;
    j = Malloc_SLL(L);
    if(j)
    {
        L[j].data = e;
        for(l = 1; l < i; l++)
        {
            k = space[k].cur;
        }
        L[j].cur = L[k].cur;
        L[k].cur = j;
        return OK;
    }
    return ERROR;
}

靜態鏈表的刪除

// 將下標爲 k 的空閒結點回收到備用鏈表
void Free_SSL(StaticLinkList L, int k)
{
    L[k].cur = L[0].cur;  // 把備用鏈表的第一個元素 cur 值賦給要刪除的分量 cur
    L[0].cur = k;  // 把要刪除的分量下標賦給第一個元素的 cur
}

// 刪除在 L 中第 i 個數據元素 e
Status ListDelete(StaticLinkList L, int i)
{
    int j, k;
    k = MAXSIZE - 1;
    if(i < 1 || i > ListLength(L))
        return ERROR;
    for(j = 1; j < i; j++)
        k = L[k].cur;
    j = L[k].cur;
    L[k].cur = L[i].cur;
    Free_SSL(L, j);
    return OK;
}

靜態鏈表優缺點

static_link_list_disadvantages_and_advantages

Code

github

結語

關於鏈表的面試題後續整理, fighting

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