1. 程式人生 > 實用技巧 >C/C++程式設計筆記:如何使用C++實現單鏈表?單鏈表第一部分

C/C++程式設計筆記:如何使用C++實現單鏈表?單鏈表第一部分

如何彌補順序表的不足之處?

第一次學習線性表一定會馬上接觸到一種叫做順序表(順序儲存結構),經過上一篇的分析順序表的優缺點是很顯然的,它雖然能夠很快的訪問讀取元素,但是在解決如插入和刪除等操作的時候,卻需要移動大量的元素,效率較低,那麼是否有一種方法可以改善或者解決這個問題呢?

首先我們需要考慮,為什麼順序表中的插入刪除操作會涉及到元素的移動呢?

好傢伙,問題就是圍繞著順序表的最大的特點出現的——順序儲存,相鄰放置元素,也就是說每個元素都是根據編號一個一個挨著的,這就導致了 插入或刪除後,為了仍然呈順序線性儲存,被操作元素後面的元素的位置均需要發生一定的變化,你應該能想象得到,在擁擠的隊伍中突然從中插入一個學生的場景,後面浩浩蕩蕩的人群,口吐芬芳的向後挪了一個空位,如果人群過大,重新排好隊也需要一定的時間

好嘛,人與人之間別這麼擠在一起,每個人與人之間都流出一點空隙來,留一定的位置出來,好了,這好像是個辦法,但是負責一個一個與學生交流填表的老師可就不幹了,這意味著我(找人)遍歷的時候,需要多跑好多路,浪費好多時間,先不說這個,體院館又不行了,你們這麼個擺法,我這小館可放不下,這也就意味著空間複雜度增加了很多。

我們剛才所圍繞的都是在 "排隊" 的基本前提下的,但我們能想到的方法並不是很理想,那麼我們索性就不排隊了,是不是能有更好的解決方式呢?

一個有效的方法:

讓同學們(元素)自己找位置隨便站,不過你要知道相對於自己下一位同學的位置,這樣既解決了空間上的問題,又能通過這種兩兩聯絡的方式訪問(遍歷)到整個隊伍(陣列),最重要的是,插入和離開同學,由於同學(元素)之間不存在了那種排隊,相鄰的特點,所以也不會說影響到過多的同學(元素)只需要和你插入位置的前後兩位同學溝通好就行了,反正別人也不知道你們之間發生了什麼事

好了思路是有了,我們來看一種最常見的連結串列——單鏈表

單鏈表的基本結構

這種連結串列為什麼被稱作單鏈表呢?這是因為它只含有一個地址域,這是什麼意思呢?

我們在連結串列中擯棄了順序表中那種一板一眼的排隊方式,但是我們必須讓兩個應該相鄰的元素之間有一定的相互關係,所以我們選擇讓每一個元素可以聯絡對應的下一個元素

而這個時候我們就需要給每個元素安排一個額外的位置,來儲存它的後繼元素的儲存地址,這個儲存元素資訊的域叫做指標域或地址域,指標域中儲存的資訊也叫作指標或者鏈

我們用一張圖 看一下他的結構

結構中名詞解釋

頭指標:一個指向第一個節點地址的指標變數

頭指標具有標識單鏈表

的作用,所以經常用頭指標代表單鏈表的名字

頭結點:在單鏈表的第一個結點之前附設一個結點,它沒有直接前驅,稱之為頭結點

可不存資訊,也可以作為監視哨,或用於存放線性表的長度等附加資訊

指標域中存放首元結點的地址

首元結點:儲存第一個元素的節點

為什麼要附設一個頭結點

我們來解釋一下:

(1)連結串列如果為空的情況下,如果單鏈表沒有頭結點,那麼頭指標就會指向NULL,如果加上頭結點,無論單鏈表是否為空,頭指標都會指向頭結點,這樣使得空連結串列與非空連結串列處理一致

(2)使首元結點前插入或刪除元素的時候,與後面操作相同,不需要產生額外的判斷分支,使得演算法更加簡單

(以插入為例講解)在帶頭結點的情況下,在首元結點前插入或者刪除元素仍與在其他位置的操作相同,只需要將前一個元素(在這裡是頭結點)的指標域指向插入元素,同時將插入元素的指標域指向原來的第二的元素

而無頭結點的情況由於,首元結點前沒有元素,只能通過修改head的前後關係,所以導致了 與在別的位置插入或刪除元素的操作不同,在實現這兩個功能的時候就需要額外的寫一個判斷語句來判斷插入的位置是不是首元結點之前的位置,增加了分支,程式碼不夠簡潔

總結:頭結點的存在使得空連結串列與非空連結串列處理一致,也方便對連結串列首元結點前結點的插入或刪除操作

單鏈表的型別定義

###線性表的抽象資料型別定義

我們在給出單鏈表的定義之前我們還是需要先引入我們線性表的抽象資料型別定義

#ifndef _SEQLIST_H_
#define _SEQLIST_H_
#include "List.h"
#include<iostream>
using namespace std;

template<class elemType>
//elemType為單鏈表儲存元素型別 
class linkList:public List<elemType> {
private:
    //節點型別定義 
    struct Node {
        //節點的資料域 
        elemType data;
        //節點的指標域 
        Node *next;
        //兩個建構函式 
        Node(const elemType value, Node *p = NULL) {
            data = value;
            next = p;
        } 
        Node(Node *p = NULL) {
            next = p;
        } 
    };
    
    //單鏈表的頭指標 
    Node *head;
    //單鏈表的尾指標 
    Node *tail;
    //單鏈表的當前長度 
    int curLength;
    //返回指向位序為i的節點的指標 
    Node *getPostion(int i)const; 
public:
    linkList();
    ~linkList();
    //清空單鏈表,使其成為空表 
    void clear();
    //帶頭結點的單鏈表,判空 
    bool empty()const {return head -> next == NULL;} 
    //返回單鏈表的當前實際長度
    int size()const {return curLength;}
    //在位序i處插入值為value的節點表長增1 
    void insert(int i, const elemType &value); 
    //刪除位序為i處的節點,表長減1
    int search(const elemType&value)const;
    //查詢值為value的節點的前驅的位序
    int prior(const elemType&value)const;
    //訪問位序為i的節點的值,0定位到首元結點
    elemType visit(int i)const;
    //遍歷單鏈表
    void traverse()const;
    //頭插法建立單鏈表
    void headCreate();
    //尾插法建立單鏈表
    void tailCreate();
    //逆置單鏈表 
    void inverse();
};

單鏈表的型別定義

#ifndef _SEQLIST_H_
#define _SEQLIST_H_
#include "List.h"
#include<iostream>
using namespace std;

template<class elemType>
//elemType為單鏈表儲存元素型別 
class linkList:public List<elemType> {
private:
    //節點型別定義 
    struct Node {
        //節點的資料域 
        elemType data;
        //節點的指標域 
        Node *next;
        //兩個建構函式 
        Node(const elemType value, Node *p = NULL) {
            data = value;
            next = p;
        } 
        Node(Node *p = NULL) {
            next = p;
        } 
    };
    
    //單鏈表的頭指標 
    Node *head;
    //單鏈表的尾指標 
    Node *tail;
    //單鏈表的當前長度 
    int curLength;
    //返回指向位序為i的節點的指標 
    Node *getPostion(int i)const; 
public:
    linkList();
    ~linkList();
    //清空單鏈表,使其成為空表 
    void clear();
    //帶頭結點的單鏈表,判空 
    bool empty()const {return head -> next == NULL;} 
    //返回單鏈表的當前實際長度
    int size()const {return curLength;}
    //在位序i處插入值為value的節點表長增1 
    void insert(int i, const elemType &value); 
    //刪除位序為i處的節點,表長減1
    int search(const elemType&value)const;
    //查詢值為value的節點的前驅的位序
    int prior(const elemType&value)const;
    //訪問位序為i的節點的值,0定位到首元結點
    elemType visit(int i)const;
    //遍歷單鏈表
    void traverse()const;
    //頭插法建立單鏈表
    void headCreate();
    //尾插法建立單鏈表
    void tailCreate();
    //逆置單鏈表 
    void inverse();
};

單鏈表上的基本運算實現

(一) 單鏈表的初始化-建構函式

單鏈表的初始化就是建立一個帶頭節點空連結串列,我們不需要設定其指標域,為空即可

template<class elemType>
linkList<elemType>::linkList() {
    head = tail = new Node();
    curLength=0;
}

注意:new 操作符代表申請堆記憶體空間,上述程式碼中應該判斷是否申請成功,為簡單,預設為申請成功,實際上如果系統沒有足夠的記憶體可供使用,那麼在申請記憶體的時候會報出一個 bad_alloc exception 異常

(二) 解構函式

當單鏈表物件脫離其作用域時,系統自動執行解構函式來釋放單鏈表空間,其實也就是清空單鏈表內容,同時釋放頭結點

template<class elemType>
linkList<elemType>::~linkList() {
    clear();
    delete head;
}

(三) 清空單鏈表

清空單鏈表的主要思想就是從頭結點開始逐步將後面節點釋放掉,但是我們又不想輕易的修改頭指標head的指向,所以我們引入一個工作指標,從頭結點一直移動到表尾,逐步釋放節點

template<class elemType>
void linkList<elemType>::clear() {
    Node *p, *tmp;
    p - head -> next;
    while(p != NULL) {
        tmp = p;
        p = p -> next();
        delete tmp; 
    }
    head -> next = NULL;
    tail = head;
    curLength = 0;    
}

下節知識分享我們將會講到,如何求單鏈表的表長以及遍歷單鏈表、插入,刪除節點等方法的實現,記得關注哦~

自學C/C++程式設計難度很大,不妨和一些志同道合的小夥伴一起學習成長!

C語言C++程式設計學習交流圈子,【點選進入】QQ群:757874045,微信公眾號:C語言程式設計學習基地

有一些原始碼和資料分享,歡迎轉行也學習程式設計的夥伴,和大家一起交流成長會比自己琢磨更快哦!