數據結構之單鏈表的實現

數據結構之單鏈表的實現

 

  在上一節 :數據結構之順序表html

  咱們提到了順序表的一些缺陷,那有沒有什麼數據結構能夠減小這些問題呢?node

  答案天然就是今天咱們所要說的鏈表。面試

 

本節大綱:數據結構

  • 鏈表的概念與結構
  • 單鏈表的實現
  • 完整代碼展現

 

 

一.鏈表的概念與結構

 

  1.概念

  鏈表是一種物理存儲結構上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表中的指針連接次序實現的 。ide

   2.結構及種類

  鏈表有好多種:單雙向鏈表,帶頭不帶頭鏈表,循環非循環鏈表 ;它們兩兩組合能夠組成 8 種鏈表結構,以下圖所示:函數

  鏈表又包括 數據域 和 指針域測試

 

 

 

 

 

 

  雖然有這麼多的鏈表的結構,可是咱們實際中最經常使用仍是兩種結構:
spa

 

 

   1. 無頭單向非循環鏈表:3d

    結構簡單,通常不會單獨用來存數據。實際中更可能是做爲其餘數據結構的子結構,如哈希桶、圖的鄰接表等等。指針

    另外這種結構在筆試面試中出現不少。

  2. 帶頭雙向循環鏈表:

    結構最複雜,通常用在單獨存儲數據。實際中使用的鏈表數據結構,都是帶頭雙向循環鏈表。

    另外這個結構雖然結構複雜,可是使用代碼實現之後會發現結構會帶來不少優點,實現反而簡單了。

 

  因此咱們今天從 無頭單向非循環鏈表 開始:

  

三.單鏈表的實現

   1.自定義數據類型

  首先咱們確定要先建立一個自定義數據類型:

//進行自定義類型,方便修改
typedef int DataType;

//進行自定義數據類型
typedef struct SLTNode
{
    DataType data;//存放數據
    struct SLTNode* next;//指向下一塊空間
}SLTNode;

 

   2.打印函數

  假設咱們如今有一個數據徹底的單鏈表,可是咱們想在看到它的值,這時就須要一個打印函數了。

  如今咱們來想想咱們剛剛所提到的單鏈表結構,它是由數據域和指針域組合而成,而它中的指針指向的是下一塊儲存空間,若是該指針爲NULL,就說明該鏈表已結束。

//單鏈表打印函數
void SLTPrint(SLTNode *p)
{
    if (p == NULL)//若是該鏈表爲空
    {
        printf("該鏈表中並沒有值可供打印\n");
        return;//直接退出
    }
    //正常狀況下
    
    //cur指向當前位置
    SLTNode *cur = p;
    while (cur)//若是cur是NULL,循環就中止
    {
        printf("%d -> ", cur->data);
        cur = cur->next;//cur指向下一塊空間
    }
    printf("NULL\n");
}

  在這會有一些人問 :while 中的判斷條件是否能夠寫爲 cur->next ,咱們來看一下:

 

   從上圖中,咱們能夠明顯看出:若循環條件爲  cur->next ,這樣循環到達最後一個節點就退出了,最後一個節點內的值並未打印,固然要是你在循環結束後,再加一句printf,那天然也沒錯

   

   3.建立節點函數:

  因後面的頭插、尾插、任意位置插入,都須要去建立節點,因此在這就把它單獨列一個模塊:

//建立節點函數
SLTNode *BuySLTNode(DataType data)
{
    //動態開闢一塊空間
    SLTNode *new_node = (SLTNode *) malloc(sizeof(SLTNode));
    if (new_node == NULL)//若是未開闢成功
    {
        perror("BuyNode Error!");//打印錯誤信息
        exit(-1);
    }
    else
    {
        //新結點的值爲傳進來的值
        new_node->data = data;
        new_node->next = NULL;
    }
    //返回新創建的節點
    return new_node;
}

 

   4.單鏈表尾插

  尾插,咱們確定要先找到該鏈表的尾,而後將尾的next指向咱們新創建的結點,將新節點的next指向NULL;

//單鏈表尾插函數
void SLTPushBack(SLTNode *p, DataType InsertData)
{
    //建立一個新節點
    SLTNode *new_node = BuySLTNode(InsertData);
    if (p == NULL)//說明該鏈表爲空
    {
        p = new_node;
    }
    else//非空就找尾
    {
        SLTNode *cur = p;
        while (cur->next)//循環遍歷找尾,注意在這,咱們的循環條件爲cur->next,而非cur
            // 由於咱們要用最後一個節點的next
            // 不然,如果cur的話,就直接走到NULL了,咱們沒法給將一個節點加在它上面
        {
            cur = cur->next;//指向下一個
        }
        cur->next = new_node;//尾插
    }
}

  但是,上面這段代碼有沒有問題呢? 咱們來運行一下:

void test1()
{
    SLTNode *p = NULL;
    SLTPushBack(p,0);
    SLTPushBack(p,1);
    SLTPushBack(p,2);
    SLTPushBack(p,3);
    SLTPrint(p);
}
測試代碼

 

   咱們明明尾插了4次,最後爲何仍是無值可供打印呢? 咱們再來調試一下:

 

   咱們發現,它在函數裏都換過了,卻出了函數又變爲了NULL。咱們思來想去總以爲這種情形好像在哪見過 --- 哦,原來曾在這個例子中見過:

#include<stdio.h>

void swap(int a, int b)
{
    int c = a;
    a = b;
    b = c;
}

int main()
{
    int a = 2, b = 3;
    printf("before:\na=%d, b=%d\n", a, b);
    swap(a, b);
    printf("after:\na=%d, b=%d\n", a, b);
    return 0;
}

 

   當時,咱們便已提到,改變其值時,要傳值,但要改變其指向,要傳址;

  在舉一個例子:

void change(char *p)
{
    p[0] = '!';
}

void change_2(char **p)
{
    static char *a = "12334";
    *p = a;
}

int main()
{
    char a[] = "dadasfsfa";
    char *p = a;

    //改變指針所指向的值
    change(p);
    printf("%s\n", p);//!adasfsfa

    //改變指針的指向
    change_2(&p);
    printf("%s\n", p);//12334
    return 0;
}
示例

  因此正確的尾插怎麼寫呢:

    1.函數參數爲STLNode** ,即改成址傳遞,由於當 p爲空的時候,咱們須要改變其指向,指向咱們新建的new_node

//單鏈表尾插函數
void SLTPushBack(SLTNode **p, DataType InsertData)
{
    //建立一個新節點
    SLTNode *new_node = BuySLTNode(InsertData);
    if (*p == NULL)//說明該鏈表爲空
    {
        *p = new_node;//改變p的指向
    }
    else//非空就找尾
    {
        SLTNode *cur = *p;
        while (cur->next)//循環遍歷找尾,注意在這,咱們的循環條件爲cur->next,而非cur
            // 由於咱們要用最後一個節點的next
            // 不然,如果cur的話,就直接走到NULL了,咱們沒法給將一個節點加在它上面
        {
            cur = cur->next;//指向下一個
        }
        cur->next = new_node;//尾插
    }
}

    5.單鏈表頭插

  如今有了以前的錯誤經驗,是否是一下就知道這個確定是傳址啊:

//單鏈表頭插函數
void SLTPushFront(SLTNode **p, DataType InsertData)
{
    SLTNode *new_node = BuySLTNode(InsertData);//建立新節點

    //在頭插中,並不關心*p爲不爲空,反正最後它們的處理都是同樣的

    //將原鏈表內容鏈接在new_node上
    new_node->next = *p;
    //改變指向
    *p = new_node;
}

   6.單鏈表尾刪

  循環遍歷找尾,釋放,置NULL

//單鏈表尾刪函數
void SLTPopBack(SLTNode **p)
{
    if (*p == NULL)//表示該鏈表爲空
    {
        printf("該鏈表中並沒有值可供刪除!\n");
        return;
    }
    else if ((*p)->next == NULL)//表示該鏈表中只有一個值,刪除以後就爲空
    {
        free(*p);//釋放空間
        *p = NULL;//置爲NULL;
    }
    else
    {
        //循環遍歷找尾
        SLTNode *cur = *p;//指向當前位置
        SLTNode *prev = NULL;//指向前一個位置
        while (cur->next)
        {
            prev = cur;
            cur = cur->next;
        }
        free(cur);//釋放空間
        cur = NULL;//及時置NULL
        //刪除以後prev即是最後一個節點了
        prev->next = NULL;
    }
}

   7.單鏈表頭刪

  釋放改指向

//單鏈表頭刪函數
void SLTPopFront(SLTNode **p)
{
    if (*p == NULL)//表示該鏈表爲空
    {
        printf("該鏈表中並沒有值可供刪除!\n");
        return;
    }
    else
    {
        SLTNode* pop_node=*p;//指向頭節點
        *p=(*p)->next;//指向下一個元素
        free(pop_node);//釋放空間
        pop_node=NULL;//及時置NULL
    }
}

   8.單鏈表查找函數

  循環遍歷

//單鏈表查找函數
SLTNode *SLTFindNode(SLTNode *p, DataType FindData)
{
    assert(p);//確保傳進來的指針不爲空
    SLTNode *cur = p;
    while (cur)
    {
        if (cur->data == FindData)
        {
            return cur;//找到就返回該節點的地址
        }
        cur = cur->next;//循環遍歷
    }
    return NULL;//找不到就返回NULL
}

   9.單鏈表修改函數

//單鏈表修改函數
void SLTModify(SLTNode *pos, DataType ModifyData)
{
    assert(pos);//確保pos不爲空
    pos->data = ModifyData;//修改
}

   10.任意位置插入

  1.單鏈表在pos位置以後插入insertdata

//單鏈表在pos位置以後插入InsertData
void SLTInsertAfter(SLTNode* pos,DataType InsertData)
{
    assert(pos);//保證pos不爲NULL
    SLTNode* new_node=BuySLTNode(InsertData);//建立一個新節點
    //該節點的下一個指向本來該位置的下一個
    new_node->next=pos->next;
    //該位置的下一個就是new_node
    pos->next=new_node;
}

  2.單鏈表在pos位置以前插入insertdata

    這時就要考慮到若位置是第一個位置時,那就至關於頭插了

    此外,咱們還得找到它的前一個位置

 

//單鏈表在pos位置以前插入InsertData
void SLTInsertBefore(SLTNode **p, SLTNode *pos, DataType InsertData)
{
    assert(pos);
    SLTNode *new_node = BuySLTNode(InsertData);
    if (pos == *p)//這就說明是第一個位置前插入
    {
        //頭插
        new_node->next = *p;
        *p = new_node;
    }
    else
    {
        SLTNode *cur = *p;
        SLTNode *prev = NULL;
        //新節點的下一個指向這個位置
        new_node->next = pos;
        //循環遍歷找到它的前一個位置
        while (cur != pos)
        {
            prev = cur;
            cur = cur->next;
        }
        //前一個位置指向新節點
        prev->next = new_node;
    }
}

    除此以外,還有哦另一種方法,能夠不用去知道它鏈表的第一個位置

    咱們在傳過去的那個位置,來進行一個後插,再交換這兩個節點的數據 ,這也是一種方法:

//單鏈表在pos位置以前插入InsertData
void SLTInsertBefore_2(SLTNode *pos, DataType InsertData)
{
    assert(pos);

    SLTNode *new_node = BuySLTNode(InsertData);
    new_node->next = pos->next;
    pos->next = new_node;
    //進行了一個後插

    //交換這兩個變量的值
    DataType tmp = pos->data;
    pos->data = new_node->data;
    new_node->data = tmp;
}

  如圖:

 

    11.任意位置刪除

   1.刪除pos位後的數據

//刪除pos位後的數據
void SLTEraseAfter(SLTNode *pos)
{
    assert(pos);
    if (pos->next == NULL)
    {
        //說明後面並無元素了
        printf("該位置後無數據能夠刪除\n");
    }
    else
    {
        //建立一個指針指向下一個位置
        SLTNode *next = pos->next;
        //原位置的下一個位置,指向 下一個位置 的下一個位置
        pos->next = next->next;
        //釋放內存
        free(next);
        next = NULL;
    }
}

    2.刪除pos位的數據 --- 這就包含了頭刪

//刪除pos位的數據 --- 含有頭刪
void SLTEraseCur(SLTNode **p, SLTNode *pos)
{
    assert(pos);
    if (*p == pos)
    {
        //說明是頭刪
        *p = pos->next;
        free(pos);
        pos = NULL;
    }
    else
    {
        //普通位置
        SLTNode *prev = *p;
        SLTNode *cur = *p;
        //找到頭一個位置
        while (cur != pos)
        {
            prev = cur;
            cur = cur->next;
        }
        //頭一個位置指向下下個位置
        prev->next = pos->next;
        free(pos);
        pos = NULL;
    }
}

    3.一樣能夠用以前所提到的方法進行帶有頭刪的任意位置刪除

      1.把該位置 後一個節點的數據存下;

      2.刪除該位置後的節點

      3.將頭節點的值改成以前存下的數據

  

   單鏈表的實現到這便就結束了

 

三.完整代碼展現

//確保頭文件只包含一次
#ifndef FINASLT_SLT_H
#define FINASLT_SLT_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>


//進行自定義類型,方便修改
typedef int DataType;

//進行自定義數據類型
typedef struct SLTNode
{
    DataType data;//存放數據
    struct SLTNode *next;//指向下一塊空間
} SLTNode;

//單鏈表打印函數
void SLTPrint(SLTNode *p);

//建立節點函數
SLTNode *BuySLTNode(DataType data);

//單鏈表尾插函數
void SLTPushBack(SLTNode **p, DataType InsertData);

//單鏈表頭插函數
void SLTPushFront(SLTNode **p, DataType InsertData);

//單鏈表尾刪函數
void SLTPopBack(SLTNode **p);

//單鏈表頭刪函數
void SLTPopFront(SLTNode **p);

//單鏈表查找函數
SLTNode *SLTFindNode(SLTNode *p, DataType FindData);

//單鏈表修改函數
void SLTModify(SLTNode *pos, DataType ModifyData);

//單鏈表在pos位置以後插入InsertData
void SLTInsertAfter(SLTNode *pos, DataType InsertData);

//單鏈表在pos位置以前插入InsertData
void SLTInsertBefore(SLTNode **p, SLTNode *pos, DataType InsertData);


//單鏈表在pos位置以前插入InsertData
void SLTInsertBefore_2( SLTNode *pos, DataType InsertData);

//刪除pos位後的數據
void SLTEraseAfter(SLTNode *pos);

//刪除pos位的數據 --- 含有頭刪
void SLTEraseCur(SLTNode **p, SLTNode *pos);

#endif //FINASLT_SLT_H
SLT.h
#include "SLT.h"

//單鏈表打印函數
void SLTPrint(SLTNode *p)
{
    if (p == NULL)//若是該鏈表爲空
    {
        printf("該鏈表中並沒有值可供打印\n");
        return;//直接退出
    }
    //正常狀況下

    //cur指向當前位置
    SLTNode *cur = p;
    while (cur)//若是cur是NULL,循環就中止
    {
        printf("%d -> ", cur->data);
        cur = cur->next;//cur指向下一塊空間
    }
    printf("NULL\n");
}

//建立節點函數
SLTNode *BuySLTNode(DataType data)
{
    //動態開闢一塊空間
    SLTNode *new_node = (SLTNode *) malloc(sizeof(SLTNode));
    if (new_node == NULL)//若是未開闢成功
    {
        perror("BuyNode Error!");//打印錯誤信息
        exit(-1);
    }
    else
    {
        //新結點的值爲傳進來的值
        new_node->data = data;
        new_node->next = NULL;
    }
    //返回新創建的節點
    return new_node;
}

////單鏈表尾插函數
//void SLTPushBack(SLTNode *p, DataType InsertData)
//{
//    //建立一個新節點
//    SLTNode *new_node = BuySLTNode(InsertData);
//    if (p == NULL)//說明該鏈表爲空
//    {
//        p = new_node;
//    }
//    else//非空就找尾
//    {
//        SLTNode *cur = p;
//        while (cur->next)//循環遍歷找尾,注意在這,咱們的循環條件爲cur->next,而非cur
//            // 由於咱們要用最後一個節點的next
//            // 不然,如果cur的話,就直接走到NULL了,咱們沒法給將一個節點加在它上面
//        {
//            cur = cur->next;//指向下一個
//        }
//        cur->next = new_node;//尾插
//    }
//}

//單鏈表尾插函數
void SLTPushBack(SLTNode **p, DataType InsertData)
{
    //建立一個新節點
    SLTNode *new_node = BuySLTNode(InsertData);
    if (*p == NULL)//說明該鏈表爲空
    {
        *p = new_node;//改變p的指向
    }
    else//非空就找尾
    {
        SLTNode *cur = *p;
        while (cur->next)//循環遍歷找尾,注意在這,咱們的循環條件爲cur->next,而非cur
            // 由於咱們要用最後一個節點的next
            // 不然,如果cur的話,就直接走到NULL了,咱們沒法給將一個節點加在它上面
        {
            cur = cur->next;//指向下一個
        }
        cur->next = new_node;//尾插
    }
}

//單鏈表頭插函數
void SLTPushFront(SLTNode **p, DataType InsertData)
{
    SLTNode *new_node = BuySLTNode(InsertData);//建立新節點

    //在頭插中,並不關心*p爲不爲空,反正最後它們的處理都是同樣的

    //將原鏈表內容鏈接在new_node上
    new_node->next = *p;
    //改變指向
    *p = new_node;
}

//單鏈表尾刪函數
void SLTPopBack(SLTNode **p)
{
    if (*p == NULL)//表示該鏈表爲空
    {
        printf("該鏈表中並沒有值可供刪除!\n");
        return;
    }
    else if ((*p)->next == NULL)//表示該鏈表中只有一個值,刪除以後就爲空
    {
        free(*p);//釋放空間
        *p = NULL;//置爲NULL;
    }
    else
    {
        //循環遍歷找尾
        SLTNode *cur = *p;//指向當前位置
        SLTNode *prev = NULL;//指向前一個位置
        while (cur->next)
        {
            prev = cur;
            cur = cur->next;
        }
        free(cur);//釋放空間
        cur = NULL;//及時置NULL
        //刪除以後prev即是最後一個節點了
        prev->next = NULL;
    }
}

//單鏈表頭刪函數
void SLTPopFront(SLTNode **p)
{
    if (*p == NULL)//表示該鏈表爲空
    {
        printf("該鏈表中並沒有值可供刪除!\n");
        return;
    }
    else
    {
        SLTNode *pop_node = *p;//指向頭節點
        *p = (*p)->next;//指向下一個元素
        free(pop_node);//釋放空間
        pop_node = NULL;//及時置NULL
    }
}

//單鏈表查找函數
SLTNode *SLTFindNode(SLTNode *p, DataType FindData)
{
    assert(p);//確保傳進來的指針不爲空
    SLTNode *cur = p;
    while (cur)
    {
        if (cur->data == FindData)
        {
            return cur;//找到就返回該節點的地址
        }
        cur = cur->next;//循環遍歷
    }
    return NULL;//找不到就返回NULL
}

//單鏈表修改函數
void SLTModify(SLTNode *pos, DataType ModifyData)
{
    assert(pos);//確保pos不爲空
    pos->data = ModifyData;//修改
}

//單鏈表在pos位置以後插入InsertData
void SLTInsertAfter(SLTNode *pos, DataType InsertData)
{
    assert(pos);//保證pos不爲NULL
    SLTNode *new_node = BuySLTNode(InsertData);//建立一個新節點
    //該節點的下一個指向本來該位置的下一個
    new_node->next = pos->next;
    //該位置的下一個就是new_node
    pos->next = new_node;
}

//單鏈表在pos位置以前插入InsertData
void SLTInsertBefore(SLTNode **p, SLTNode *pos, DataType InsertData)
{
    assert(pos);
    SLTNode *new_node = BuySLTNode(InsertData);
    if (pos == *p)//這就說明是第一個位置前插入
    {
        //頭插
        new_node->next = *p;
        *p = new_node;
    }
    else
    {
        SLTNode *cur = *p;
        SLTNode *prev = NULL;
        //新節點的下一個指向這個位置
        new_node->next = pos;
        //循環遍歷找到它的前一個位置
        while (cur != pos)
        {
            prev = cur;
            cur = cur->next;
        }
        //前一個位置指向新節點
        prev->next = new_node;
    }
}

//單鏈表在pos位置以前插入InsertData
void SLTInsertBefore_2(SLTNode *pos, DataType InsertData)
{
    assert(pos);

    SLTNode *new_node = BuySLTNode(InsertData);
    new_node->next = pos->next;
    pos->next = new_node;
    //進行了一個後插

    //交換這兩個變量的值
    DataType tmp = pos->data;
    pos->data = new_node->data;
    new_node->data = tmp;
}

//刪除pos位後的數據
void SLTEraseAfter(SLTNode *pos)
{
    assert(pos);
    if (pos->next == NULL)
    {
        //說明後面並無元素了
        printf("該位置後無數據能夠刪除\n");
    }
    else
    {
        //建立一個指針指向下一個位置
        SLTNode *next = pos->next;
        //原位置的下一個位置,指向 下一個位置 的下一個位置
        pos->next = next->next;
        //釋放內存
        free(next);
        next = NULL;
    }
}

//刪除pos位的數據 --- 含有頭刪
void SLTEraseCur(SLTNode **p, SLTNode *pos)
{
    assert(pos);
    if (*p == pos)
    {
        //說明是頭刪
        *p = pos->next;
        free(pos);
        pos = NULL;
    }
    else
    {
        //普通位置
        SLTNode *prev = *p;
        SLTNode *cur = *p;
        //找到頭一個位置
        while (cur != pos)
        {
            prev = cur;
            cur = cur->next;
        }
        //頭一個位置指向下下個位置
        prev->next = pos->next;
        free(pos);
        pos = NULL;
    }

}
SLT.c
#include "SLT.h"

void test1()
{
    SLTNode *p = NULL;
    SLTPushBack(&p, 0);
    SLTPushBack(&p, 1);
    SLTPushBack(&p, 2);
    SLTPrint(p);

    SLTPushFront(&p, 0);
    SLTPushFront(&p, 1);
    SLTPushFront(&p, 2);
    SLTPrint(p);

    SLTPopBack(&p);
    SLTPrint(p);

    SLTPopFront(&p);
    SLTPrint(p);

    SLTNode *pos = SLTFindNode(p, 1);
    if (pos == NULL)
    {
        printf("該鏈表中無所查找的值!\n");
    }
    else
    {
        SLTModify(pos,123);
    }
    SLTPrint(p);

    SLTInsertAfter(pos,321);
    SLTPrint(p);

    SLTInsertBefore(&p,pos,000);
    SLTPrint(p);

    SLTInsertBefore_2(SLTFindNode(p,1),666);

    SLTPrint(p);

    SLTEraseAfter(SLTFindNode(p,1));
    SLTEraseAfter(SLTFindNode(p,666));
    SLTEraseCur(&p,SLTFindNode(p,0));
    SLTEraseCur(&p,SLTFindNode(p,123));
    SLTEraseCur(&p,SLTFindNode(p,321));
    SLTEraseCur(&p,SLTFindNode(p,0));
    SLTEraseCur(&p,SLTFindNode(p,0));
    SLTEraseCur(&p,SLTFindNode(p,666));
    SLTPrint(p);

}

int main()
{
    setbuf(stdout, NULL);
    test1();
    return 0;
}
main.c

 

 

 


|--------------------------------------------------------

關於單鏈表的知識到這便結束了

因筆者水平有限,如有錯誤,還望指正!

相關文章
相關標籤/搜索