線性表的鏈式存儲結構-單鏈表及循環鏈表

1. 鏈式存儲定義

爲了表示每一個數據元素ai與其直接後繼數據元素ai+1之間的邏輯關係,對數據元素ai來講,除了存儲其自己的信息以外,還需存儲一個指示其直接後繼的信息。咱們把存儲數據元素信息的域稱爲數據域,把存儲直接後繼位置的域稱爲指針域。這兩部分信息組成數據元素的存儲映像,稱爲結點。
n個結點鏈組成一個鏈表,即爲線性表的鏈式存儲結構,由於此鏈表的每一個結點中只包含一個指針域,因此叫作單鏈表。
對於線性表來講,有頭有尾,鏈表中的第一個結點的存儲位置叫作頭指針,鏈表的存取必須是從頭指針開始進行。
有時候我了增長對鏈表操做的方便性,咱們會在鏈表的第一個結點(首元結點)前增長一個結點,稱爲頭結點。頭結點的數據域能夠不存任何數據,指針域存儲第一個結點的指針。
鏈式存儲結構中咱們默認都帶上頭結點。
圖片示意以下:
無頭結點單鏈表示意圖.png函數


有頭結點單鏈表示意圖.png
頭指針和頭結點的區別:
頭指針:性能

  • 頭指針是指鏈表指向第一個結點的指針,若鏈表有頭結點,則是指向頭結點的指針。
  • 頭指針具備標識做用,因此經常使用頭指針冠以鏈表的名字。
  • 不管鏈表是否爲空,頭指針均不爲空。頭指針是鏈表的必要元素。

頭結點:spa

  • 頭結點是爲了操做的統一和方便而設立的,放在第一個元素的結點前,其數據域通常無心思。
  • 有了頭結點,對在第一個元素結點前插入結點和刪除第一個結點,與操做其餘結點的操做就統一了。
  • 頭結點不必定是鏈表的必要元素。

2. 單鏈表

單鏈表結點中包含了數據域指針域,以下圖所示:
節點結構.png
結點定義以下:3d

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

/* ElemType類型根據實際狀況而定,這裏假設爲int */
typedef int ElemType;
/* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef int Status;
//定義結點
typedef struct Node{
    ElemType data; // 存儲在數據域中的數據
    struct Node *next; // 直接後繼結點的地址指針
}Node;

typedef struct Node * LinkList;

2.1 單鏈表初始化

思路:指針

  1. 在內存開闢空間,建立頭結點。
  2. 初始化頭結點。
Status InitList(LinkList *L){
    //產生頭結點,並使用L指向此頭結點
    *L = (LinkList)malloc(sizeof(Node));
    //存儲空間分配失敗
    if(*L == NULL) return ERROR;
    //將頭結點的指針域置空
    (*L)->next = NULL;
    return OK;
}

2.2 單鏈表插入數據

假設要在單鏈表的兩個數據元素A和B之間插⼊一個數據元素e,已知p爲其單鏈表存儲結構中指向結點A指針。以下圖所示
單鏈表插入.png
思路:code

  1. 聲明一結點p指針指向鏈表頭結點,初始化j從1開始。
  2. 當j<i時,就遍歷鏈表,讓p的指針向後移動,不斷指向下一結點,查找插入位置的前一個結點,j累加。
  3. 若到鏈表末尾p爲空,則說明插入的位置不存在。
  4. 不然查找成功,在系統中生成一個空節點s。
  5. 將數據元素e賦值給s->data。
  6. 而後將s->next指向p->next,p->next指向s。
  7. 返回成功。

代碼實現以下:blog

/*
 初始條件:順序線性表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-1個元素不存在
    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;
}

2.3 單鏈表刪除數據

要刪除單鏈表中指定位置的元素,同插入元素同樣,首先應該找到該位置的前驅結點,以下圖所示在單鏈表中刪除元素B時,應該首先找到其前驅結點A,爲了在單鏈表中實現元素A,B,C之間的邏輯關係的變化,僅需修改結點A中的指針域便可.
單鏈表刪除.png
思路:圖片

  1. 聲明一結點p指針指向鏈表頭結點,初始化j從1開始。
  2. 當j<i時,就遍歷鏈表,讓p的指針向後移動,不斷指向下一結點,查找待刪除位置的前一個結點,j累加。
  3. 若到鏈表末尾p爲空,則說明刪除的位置不存在。
  4. 不然查找成功,則將q指向要刪除的結點。
  5. 將q的直接後繼賦值給p的直接後繼。
  6. 將q結點中的數據給e。
  7. 釋放刪除的結點。
  8. 返回成功。

代碼實現以下:內存

/*
 初始條件:順序線性表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;
    j = 1;
    //查找第i-1個結點,p指向該結點
    while (p->next && j<i) {
        p = p->next;
        ++j;
    }
    //當i>n 或者 i<1 時,刪除位置不合理
    if (!(p->next) || j>i) return  ERROR;
    //q指向要刪除的結點
    q = p->next;
    //將q的後繼賦值給p的後繼
    p->next = q->next;
    //將q結點中的數據給e
    *e = q->data;
    //讓系統回收此結點,釋放內存;
    free(q);
    return OK;
}

2.4 單鏈表讀取數據

在單鏈表中,咱們不能像順序存儲結構那樣直接經過下標直接獲取數據,咱們沒辦法一開始就知道,必須得從頭開始找,進行遍歷。
思路:get

  1. 聲明一結點p指針指向鏈表首元結點(不是頭結點),初始化j從1開始。
  2. 當j<i時,就遍歷鏈表,讓p的指針向後移動,不斷指向下一結點,j累加。
  3. 若到鏈表末尾p爲空,則說明讀取的元素不存在。
  4. 不然查找成功,返回節點p的數據。

代碼實現以下:

/*
 初始條件: 順序線性表L已存在,1≤i≤ListLength(L);
 操做結果:用e返回L中第i個數據元素的值
 */
Status GetElem(LinkList L,int i,ElemType *e){
    int j = 1;
    //聲明結點p;
    LinkList p;
    //將結點p 指向鏈表L的首元結點;
    p = L->next;
    //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;
}

2.5 頭插法總體建立鏈表

思路:

  1. 聲明一個結點p。
  2. 初始化一空鏈表L。
  3. 讓L的頭結點的指針指向NULL,即創建一個帶頭結點的單鏈表。
  4. 循環:

    • 生成一個新的結點賦值給p。
    • 給p->data賦值。
    • 將p插入到頭結點以後。

代碼實現以下:

/* 隨機產生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插入到頭結點以後;
        (*L)->next = p;
    }
}

2.6 尾插法總體建立鏈表

思路:

  1. 聲明一個結點p、r, r指向尾部結點。
  2. 初始化一空鏈表L。
  3. 讓L的頭結點的指針指向NULL,即創建一個帶頭結點的單鏈表。
  4. 循環:

    • 生成一個新的結點賦值給p。
    • 給p->data賦值。
    • 將p插入到尾部結點r以後。
  5. 將尾結點r的next設置爲NULL。

代碼實現以下:

/* 隨機產生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;
}

2.7 單鏈表總體刪除

思路:

  1. 聲明一個結點p和q。
  2. 將第一個結點賦值給p。
  3. 循環:

    • 將下一個結點賦值給q。
    • 釋放p。
    • 將q賦值給p。
  4. 將頭結點的next設置爲NULL。

代碼實現以下:

/* 初始條件:順序線性表L已存在。操做結果:將L重置爲空表 */
Status ClearList(LinkList *L)
{
    LinkList p,q;
    p=(*L)->next;           /*  p指向第一個結點 */
    while(p)                /*  沒到表尾 */
    {
        q=p->next;
        free(p);
        p=q;
    }
    (*L)->next=NULL;        /* 頭結點指針域爲空 */
    return OK;
}

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

存儲分配方式:

  • 順序存儲結構用一段連續的存儲單元依次存儲線性表的數據元素。
  • 單鏈表採用鏈式存儲結構,用一組任意的存儲單元存放線性表的元素。

時間性能:

  • 查找

    • 順序存儲結構O(1)。
    • 單鏈表O(n)。
  • 插入與刪除

    • 順序存儲結構須要平均移動表長一半的元素,時間爲O(n)。
    • 單鏈表在找出某位置的指針後,插入和刪除時間爲O(1)。

空間性能:

  • 順序存儲結構須要預分配存儲空間,分大了,浪費,分小了,容易溢出。
  • 單鏈表不須要分配存儲空間,只要有就能夠分配,元素個數也不受限制。

經過對比,咱們可得知:
若線性表須要頻繁查找,不多進行插入和刪除操做時,宜採用順序存儲結構。若須要頻繁插入和刪除時,宜採用單鏈表結構。
當線性表中的元素個數變化較大或者根本不知道有多大的時候,最好採用單鏈表結構,這樣能夠不須要考慮存儲空間大小的問題。而若是事先知道線性表的大體長度,就能夠用這個順序存儲結構,用起來比較高效。

4. 循環鏈表

4.1 循環鏈表定義

將單鏈表中的終端結點的指針從空指針改成指向頭結點,就使整個單鏈表造成一個環,這種頭尾相接的單鏈表稱爲單循環鏈表,簡稱循環鏈表。
爲了使空鏈表與非空鏈表處理一致,咱們一般設一個頭結點。
循環列表中帶有頭結點的空鏈表以下圖:
空循環鏈表示意圖.png
對於非空的循環鏈表以下圖:
非空循環鏈表示意圖.png

4.2 循環鏈表建立

思路:

  1. 判斷鏈表是否已經存在,便是否已經存在頭結點。
  2. 若是存在頭結點,那麼直接依次往裏插入數據。
  3. 若是不存在頭結點,那麼先建立頭結點,而後依次往裏插入數據。
  4. 返回OK。

代碼實現:

/* 建立一個帶有頭結點的循環鏈表 */
Status CreateList(LinkList *L) {
    int item;
    LinkList temp = NULL;
    LinkList r = NULL; // 尾結點指針
    printf("請輸入新的結點, 當輸入0時結束!\n");
    while (1) {
        scanf("%d",&item);
        if (item == 0) {
            break;
        }
        // 當鏈表爲空時,建立鏈表,帶上頭結點。
        if (*L == NULL) {
            *L = (LinkList)malloc(sizeof(Node));
            if (!*L) return ERROR;
            (*L)->next = *L; // 使其next指針指向本身
            r = *L;
        }
        temp = (LinkList)malloc(sizeof(Node));
        if(!temp) return  ERROR;
        temp->data = item;
        temp->next = *L;
        r->next = temp;
        r = temp;
    }
    return OK;
}

4.3 循環鏈表插入數據

思路:

  1. 建立新結點temp,並判斷是否建立成功,成功則賦值,不然返回ERROR。
  2. 先找到插入的位置的前一個結點target,若是插入位置超過鏈表長度,則自動插入隊尾。
  3. 新結點的next指向target原來的next位置,target的next指向新結點。
  4. 返回OK。

代碼實現以下:

/* 循環鏈表插入數據 */
Status ListInsert2(LinkList *L, int place, int num) {
    if (place < 1) {
        return ERROR;
    }
    LinkList target;
    LinkList temp;
    int k;
    temp = (LinkList)malloc(sizeof(Node));
    if (!temp) return ERROR;
    temp->data = num;
    // 查找插入位置的前一個結點
    for (k = 1, target = *L; k < place && target->next != *L; k++, target = target->next);
    temp->next = target->next;
    target->next = temp;
    return OK;
}

4.4 循環鏈表刪除數據

思路:

  1. 建立新結點temp,用於記錄要刪除的元素。
  2. 先找到刪除位置的前一個結點target,若是刪除的位置大於鏈表長度,返回ERROR。
  3. 將target->next賦值給temp,temp->next賦值給target->next。
  4. 釋放temp。
  5. 返回OK。

代碼實現以下:

/* 循環鏈表刪除數據,鏈表表L已存在,1≤place≤ListLength(L) */
Status LinkListDelete(LinkList *L,int place) {
    if (place < 1) {
        return ERROR;
    }
    LinkList temp, target;
    int k;
    for (k = 1, target = *L; k < place && target->next != *L; k++, target = target->next);
    if (place > k) {
        return ERROR;
    }
    temp = target->next;
    target->next = temp->next;
    free(temp);
    return OK;
}

4.5 循環鏈表查詢數據位置

思路:

  1. 建立結點指針p,並將p指向首元結點,聲明變量i,用來記錄位置。
  2. 循環查找結點值等於value的結點,直到遍歷到最後一個節點中止。
  3. 若是已經遍歷到最後一個元素,且尚未找到,那麼直接返回-1.
  4. 返回查找到的位置。

代碼實現以下:

/* 循環鏈表查詢值的位置 */
int findValue(LinkList L, int value) {
    int i = 1;
    LinkList p;
    p = L->next;
    while (p->data != value && p->next != L) {
        i++;
        p = p->next;
    }
    // 若是已經遍歷到最後一個元素,且尚未找到,那麼直接返回-1.
    if (p->next == L && p->data != value) {
        return  -1;
    }
    return i;
}
相關文章
相關標籤/搜索