數據結構 - 動態單鏈表的實行(C語言)


動態單鏈表的實現

1 單鏈表存儲結構代碼描述

若鏈表沒有頭結點,則頭指針是指向第一個結點的指針。

若鏈表有頭結點,則頭指針是指向頭結點的指針。

空鏈表的示意圖:

img

帶有頭結點的單鏈表:

img

不帶頭結點的單鏈表的存儲結果示意圖:

img

在C語言中可用結構指針來描述單鏈表:

/* 線性表的單鏈表存儲結構 */
typedef struct node
{
    ElemType data;
    struct node *next;
}Node;
typedef Node* LinkList; //定義單鏈表指針

從這個結構定義中可以看出,結點由存放數據元素的數據域和存放后繼結點地址的指針域組成。


2 單鏈表的讀取

在單鏈表中,由於第i個元素到底在哪?沒辦法一開始就知道,必須得從頭開始找。

獲得鏈表第i個數據的算法思路:

  1. 聲明一個結點指針cur指向鏈表第一個結點,初始化j從1開始;
  2. 當 j<i 時,就遍歷鏈表,讓cur的指針向后移動,不斷指向下一結點,j 累加1;
  3. 若到鏈表末尾cur為空,則說明第i個元素不存在;
  4. 否則查找成功,返回結點cur的數據。

注意:cur最開始指向第一個結點,而不是頭結點,cur與j同步。

/******************************************
名稱: 獲取元素操作
功能: 獲取鏈表第i個結點數據,賦給e
返回值: 查找成功返回true,否則返回false
******************************************/
Status GetElem(LinkList pList, int i, ElemType *e)
{
    LinkList cur;

    cur = pList->next; //讓cur指向鏈表pList的第1個結點
    int j = 1; //j為計數器,賦值為1,對應cur指向結點
    //cur不為空或者計數器j還沒有等於i時,循環繼續
    while (cur!=NULL && j < i) //若i=1,則不用進入循環遍歷,就可找到了
    {
        cur = cur->next;
        j++;
    }

    if (cur == NULL || j>i) //j>i,為了避免輸入i小於1的情況
        return FALSE; //到鏈表尾,仍未找到第i個元素

    *e = cur->data; //取第i個元素的數據

    return TRUE;
}

說白了,就是從頭開始找,直到第 i 個元素為止。由於這個算法的時間復雜度取決於 i 的位置,當 i=1 肘,則不用進入循環遍歷,第一個就取出數據了,而當 i=n 時則遍歷 n-1
次才可以。 因此最壞情況的時間復雜度是O(n)。

由於單鏈表的結構中沒有定義表長,所以不能事先知道要循環多少次,因此也就不方便使用for來控制循環。其主要核心思想就是"工作指針后移',這其實也是很多算法的常用技術。


3 單鏈表的插入與刪除

單鏈表的插入操作核心代碼只有兩句(在結點p后面插入結點s):

s->next = p->next; /* 將p的后繼結點賦值給s的后繼  */
p->next = s; /* 將s賦值給p的后繼 */

解讀這兩句代碼,也就是說讓p的后繼結點改成s的后繼結點,再把結點s變成p的后繼結點,如下圖所示:

img

對於單鏈裴的表頭和表尾的特殊情況,操作是相同的,如下圖所示:

img

單鏈表第i個位置插入結點的算法思路:

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

注意:front最開始指向頭結點,而不是第一個結點,而j賦值為1,讓front與j不同步,front始終指向i位置前一個結點。

實現代碼算法如下:

/******************************************
名稱: 插入元素操作
功能: 在鏈表第i個位置插入新的數據元素e
返回值: 插入成功返回true,否則返回false
******************************************/
Status ListInsert(LinkList pList, int i, const ElemType e)
{
    LinkList front; //指向插入位置前一個結點
    LinkList pTemp;
    int j;

    front = pList; //讓front指向鏈表pList的頭結點
    j = 1; //j為計數器,賦值為1,讓front能指向插入位置前一個結點
    //找到i位置所在的結點
    while (front != NULL && j < i)
    {
        front = front->next;
        j++;
    }

    if (front == NULL || j>i) //j>i,為了避免輸入i小於1的情況
        return FALSE; //到鏈表尾,仍未找到第i個元素

    pTemp = (LinkList)malloc(sizeof(Node));
    pTemp->data = e;

    //插入結點s
    pTemp->next = front->next;
    front->next = pTemp;

    return TRUE;
}


4 單鏈表的刪除

單鏈表的刪除操作核心代碼只有一句(刪除結點p后面一個結點):

p->next = p->next->next; /* 將p的后繼結點的后繼賦值給p的后繼 */

img

單鏈表第 i 個數據之后刪除結點的算法思路(刪除的是i+1位置的結點):

  1. 聲明一結點p指向鏈表頭結點, 初始化 j 從1開始;
  2. 當 j<i 時,就遍歷鏈表,讓front的指針向后移動,不斷指向下一個結點,j 累加1;
  3. 若到鏈表末尾front為空,則說明第 i 個元素不存在;
  4. 否則查找成功,查找到要刪除位置的前一個結點front,並賦值給q;
  5. 執行鏈表的刪除結點語句front->next = front->next->next;
  6. 將q結點中的數據賦值給e, 作為返回;
  7. 釋放q結點,並指向NULL;
  8. 返回成功。

注意:front最開始指向頭結點,而不是第一個結點,而j賦值為1,讓front與j不同步,front始終指向i位置前一個結點。

實現代碼算法如下:

/******************************************
名稱: 刪除元素操作
功能: 在鏈表第i個位置刪除結點
返回值: 刪除成功返回true,否則返回false
******************************************/
Status ListDelete(LinkList pList, int i, ElemType *e)
{
    LinkList front, pTemp;

    front = pList; //讓front指向鏈表pList的頭結點
    int j = 1; //j為計數器,賦值為1,讓front能指向插入位置前一個結點
    //找到i位置所在的結點
    while (front != NULL && j < i)
    {
        front = front->next;
        j++;
    }

    if (front == NULL || j > i) //j>i,為了避免輸入i小於1的情況
        return FALSE; //到鏈表尾,仍未找到第i個元素

    LinkList q = front->next;

    //刪除結點
    front->next = front->next->next;

    //使用q結點,保存已經刪除的結點
    *e = q->data; //將要刪除結點的數據賦給e
    free(q);
    q = NULL;

    return TRUE;
}

分析一下剛才我們講解的單鏈表插入和刪除算法,我們發現,它們其實都是由兩部分組成;第一部分就是遍歷查找第i個元素;第二部分就是插入和刪除元素。從整個算法來說,我們很容易推導出:它們的時間復雜度都是O(n)。

從第i個位置插入10個元素,對於順序存儲結構意味着,每一次插入都需要移動 n-i 個元素,每次都是O(n)。而單鏈表,我們只需要在第一次時,找到第i個位置的指針,此時為O(n),接下來只是簡單地通過賦值移動指針而已,時間復雜度都是O(1)。顯然,對於插入或刪除數據越頻繁的操作,單鏈表的效率優勢就越是明顯。


5 單鏈表的頭部插入與尾部插入

頭部插入,就是始終讓新結點在第一個結點的位置,這種算法簡稱為頭插法,如下圖所示:

img

實現代碼算法如下:

/******************************************
名稱: 頭部后插入元素操作
功能: 在鏈表頭部后插入存儲數據元素的結點
返回值: 插入成功返回true,否則返回false
******************************************/
Status ListInsertHead(LinkList pList, const ElemType e)
{
    LinkList head, pTemp;

    //檢測鏈表是否存在
    if (!pList)
    {
        printf("pList not exist!\n");
        return FALSE;
    }

    pTemp = (LinkList)malloc(sizeof(Node));
    if (!pTemp)
    {
        printf("malloc error!\n");
        return -1;
    }
    pTemp->data = e;

    head = pList; //讓head指向鏈表pList的頭結點
    //頭結點后插入結點
    pTemp->next = head->next;
    head->next = pTemp;

    return TRUE;
}


尾部插入,將數據元素插入到尾節點后面,這種簡稱為尾插法。

/******************************************
名稱: 尾部后插入元素操作
功能: 在鏈表尾部后插入存儲數據元素的結點
返回值: 插入成功返回true,否則返回false
******************************************/
Status ListInsertTail(LinkList pList, const ElemType e)
{
    LinkList cur, pTemp;

    //檢測鏈表是否存在
    if (!pList)
    {
        printf("pList not exist!\n");
        return FALSE;
    }

    pTemp = (LinkList)malloc(sizeof(Node));
    if (!pTemp)
    {
        printf("malloc error!\n");
        return -1;
    }
    pTemp->data = e;

    cur = pList; //讓head指向鏈表pList的頭結點
    //找到鏈表尾節點
    while (cur->next)
    {
        cur = cur->next;
    }

    //尾結點后插入結點
    pTemp->next = cur->next;
    cur->next = pTemp;

    return TRUE;
}


6 單鏈表的整表刪除

單鏈表整表刪除的算法思路如下:

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

實現代碼算法如下:

/******************************************
名稱: 整表刪除操作
功能: 銷毀整個線性表,即釋放所有結點申請的內存
返回值: 刪除成功返回true,否則返回false
******************************************/
Status ClearList(LinkList pList)
{
    LinkList p; //當前結點
    LinkList q; //用來保存下一結點,防止釋放當前結點后導致“掉鏈”

    //檢測鏈表是否存在
    if (!pList)
    {
        printf("pList not exist!\n");
        return FALSE;
    }

    p = pList; //q指向頭結點
    while (p)
    {
        q = p->next; //事先保存下一結點,防止釋放當前結點后導致“掉鏈”
        free(p); //釋放當前結點
        p = q; //將下一結點賦給當前結點p
    }
    pList->next = NULL; //頭結點指針域指向空

    return TRUE;
}


7 單鏈表的完整實現

#include "stdafx.h"
#include <stdlib.h>

#define TRUE 1
#define FALSE 0
typedef int Status; //Status是函數結果狀態,成功返回TRUE,失敗返回FALSE

typedef int ElemType;
/* 線性表的單鏈表存儲結構 */
typedef struct node
{
    ElemType data;
    struct node *next;
}Node;
typedef Node* LinkList; //定義單鏈表指針

/******************************************
名稱: 初始化單鏈表操作
******************************************/
void InitList(LinkList *pList) //必須使用雙重指針,一重指針申請會出錯
{
    *pList = (LinkList)malloc(sizeof(Node));

    (*pList)->data = 0;
    (*pList)->next = NULL;
}

/******************************************
名稱: 獲取元素操作
功能: 獲取鏈表第i個結點數據,賦給e
返回值: 查找成功返回true,否則返回false
******************************************/
Status GetElem(LinkList pList, int i, ElemType *e)
{
    LinkList cur;

    cur = pList->next; //讓cur指向鏈表pList的第1個結點
    int j = 1; //j為計數器,賦值為1,對應cur指向結點
    //cur不為空或者計數器j還沒有等於i時,循環繼續
    while (cur != NULL && j < i) //若i=1,則不用進入循環遍歷,就可找到了
    {
        cur = cur->next;
        j++;
    }

    if (cur == NULL || j>i) //j>i,為了避免輸入i小於1的情況
        return FALSE; //到鏈表尾,仍未找到第i個元素

    *e = cur->data; //取第i個元素的數據

    return TRUE;
}

/******************************************
名稱: 插入元素操作
功能: 在鏈表第i個位置插入新的數據元素e
返回值: 插入成功返回true,否則返回false
******************************************/
Status ListInsert(LinkList pList, int i, const ElemType e)
{
    LinkList front; //指向插入位置前一個結點
    LinkList pTemp;
    int j;

    front = pList; //讓front指向鏈表pList的頭結點
    j = 1; //j為計數器,賦值為1,讓front能指向插入位置前一個結點
    //找到i位置所在的結點
    while (front != NULL && j < i)
    {
        front = front->next;
        j++;
    }

    if (front == NULL || j>i) //j>i,為了避免輸入i小於1的情況
        return FALSE; //到鏈表尾,仍未找到第i個元素

    pTemp = (LinkList)malloc(sizeof(Node));
    pTemp->data = e;

    //插入結點s
    pTemp->next = front->next;
    front->next = pTemp;

    return TRUE;
}

/******************************************
名稱: 刪除元素操作
功能: 在鏈表第i個位置刪除結點
返回值: 刪除成功返回true,否則返回false
******************************************/
Status ListDelete(LinkList pList, int i, ElemType *e)
{
    LinkList front, pTemp;

    front = pList; //讓front指向鏈表pList的頭結點
    int j = 1; //j為計數器,賦值為1,讓front能指向插入位置前一個結點
    //找到i位置所在的結點
    while (front != NULL && j < i)
    {
        front = front->next;
        j++;
    }

    if (front == NULL || j > i) //j>i,為了避免輸入i小於1的情況
        return FALSE; //到鏈表尾,仍未找到第i個元素

    LinkList q = front->next;

    //刪除結點
    front->next = front->next->next;

    //使用q結點,保存已經刪除的結點
    *e = q->data; //將要刪除結點的數據賦給e
    free(q);
    q = NULL;

    return TRUE;
}

/******************************************
名稱: 頭部后插入元素操作
功能: 在鏈表頭部后插入存儲數據元素的結點
返回值: 插入成功返回true,否則返回false
******************************************/
Status ListInsertHead(LinkList pList, const ElemType e)
{
    LinkList head, pTemp;

    //檢測鏈表是否存在
    if (!pList)
    {
        printf("pList not exist!\n");
        return FALSE;
    }

    pTemp = (LinkList)malloc(sizeof(Node));
    if (!pTemp)
    {
        printf("malloc error!\n");
        return -1;
    }
    pTemp->data = e;

    head = pList; //讓head指向鏈表pList的頭結點
    //頭結點后插入結點
    pTemp->next = head->next;
    head->next = pTemp;

    return TRUE;
}

/******************************************
名稱: 尾部后插入元素操作
功能: 在鏈表尾部后插入存儲數據元素的結點
返回值: 插入成功返回true,否則返回false
******************************************/
Status ListInsertTail(LinkList pList, const ElemType e)
{
    LinkList cur, pTemp;

    //檢測鏈表是否存在
    if (!pList)
    {
        printf("pList not exist!\n");
        return FALSE;
    }

    pTemp = (LinkList)malloc(sizeof(Node));
    if (!pTemp)
    {
        printf("malloc error!\n");
        return -1;
    }
    pTemp->data = e;

    cur = pList; //讓head指向鏈表pList的頭結點
    //找到鏈表尾節點
    while (cur->next)
    {
        cur = cur->next;
    }

    //尾結點后插入結點
    pTemp->next = cur->next;
    cur->next = pTemp;

    return TRUE;
}

/******************************************
名稱: 整表刪除操作
功能: 銷毀整個線性表,即釋放所有結點申請的內存
返回值: 刪除成功返回true,否則返回false
******************************************/
Status ClearList(LinkList pList)
{
    LinkList p; //當前結點
    LinkList q; //用來保存下一結點,防止釋放當前結點后導致“掉鏈”

    //檢測鏈表是否存在
    if (!pList)
    {
        printf("pList not exist!\n");
        return FALSE;
    }

    p = pList; //q指向頭結點
    while (p)
    {
        q = p->next; //事先保存下一結點,防止釋放當前結點后導致“掉鏈”
        free(p); //釋放當前結點
        p = q; //將下一結點賦給當前結點p
    }
    pList->next = NULL; //頭結點指針域指向空

    return TRUE;
}

/******************************************
名稱: 遍歷鏈表並顯示元素操作
******************************************/
void ListShow(LinkList pList)
{
    LinkList cur = pList->next;
    while (cur != NULL)
    {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
}

int main()
{
    LinkList pList;

    //初始化單鏈表
    InitList(&pList);

    //插入結點
    ListInsert(pList, 1, 0);
    ListInsert(pList, 2, 1);
    ListInsert(pList, 3, 2);

    //刪除結點
    int val;
    ListDelete(pList, 2, &val);
    printf("刪除結點的數據: %d\n", val);

    //頭部后插入元素
    ListInsertHead(pList, 5);
    ListInsertHead(pList, 4);

    //尾部后插入元素
    ListInsertTail(pList, 8);
    ListInsertTail(pList, 9);

    //遍歷鏈表並顯示元素操作
    ListShow(pList);

    //刪除整個單鏈表
    ClearList(pList);

    return 0;
}

/*
輸出結果:

刪除結點的數據: 1
4 5 0 2 8 9
*/

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com