雙鏈表的基本實現與講解(C++描述)

雙鏈表

 

雙鏈表的意義

單鏈表相對於順序表,確實在某些場景下解決了一些重要的問題,例如在需要插入或者刪除大量元素的時候,它並不需要像順序表一樣移動很多元素,只需要修改指針的指向就可以了,其時間複雜度爲 O(1) 但是這可是有前提的,那就是這一切都基於確定節點後,純粹考慮刪除和插入的情況下,但是如果我們仍未確定節點的位置,那麼單鏈表就會出現一些問題了,例如我們來看一下刪除這個操作

刪除操作

單鏈表:

對應圖中的節點,想要刪除第2個節點 a1 只需要 將首元結點的指針指向到第三個節點的地址去

 
17955338-d592cf1d77875f74
image

但是問題就在於我們如何得到待刪除節點的前驅,也就是我們圖中的首元結點,我們給出兩種方法

  • A:定位待刪除節點的同時,一直順便保存當前節點的前驅
  • B:刪除節點後,重新回到單鏈表表頭,定位到其指定前驅

但是無論我們選擇哪一種方法,指針的總移動數都會是 2n 次,而雙鏈表卻在這一類型問題上做出了很好的處理

雙鏈表:

 
17955338-22276f87d14a0a0c
image

單鏈表中之所以出現問題,就是因爲各個節點只有一個指向後繼的指針域 next,只能向後移動查找,一旦我們想要查詢前一節點,就變得很麻煩,所以雙鏈表就在每個節點前面增加一個指向前驅的指針域 prior,這樣我們就可以直接定位到我們的前一個節點了,這也就是雙鏈表

注意:爲了統一運算,避免特殊情況的出現,我們也常常在尾部設置一個 “尾部頭結點” 其 next 指針域爲空

線性表的抽象數據類型定義

我們在給出雙鏈表的定義之前我們還是需要先引入我們線性表的抽象數據類型定義

#ifndef _LIST_H_
#define _LIST_H_
#include<iostream>
using namespace std;

class outOfRange{};
class badSize{};
template<class T>
class List {
public:
    // 清空線性表
    virtual void clear()=0;
    // 判空,表空返回true,非空返回false
    virtual bool empty()const=0;
    // 求線性表的長度
    virtual int size()const=0;
    // 在線性表中,位序爲i[0..n]的位置插入元素value
    virtual void insert(int i,const T &value)=0;
    // 在線性表中,位序爲i[0..n-1]的位置刪除元素
    virtual void remove(int i)=0;
    // 在線性表中,查找值爲value的元素第一次出現的位序
    virtual int search(const T&value)const=0;
    // 在線性表中,查找位序爲i的元素並返回其值
    virtual T visit(int i)const=0;
    // 遍歷線性表
    virtual void traverse()const=0;
    // 逆置線性表
    virtual void inverse()=0;                   
    virtual ~List(){};
};

/*自定義異常處理類*/ 


class outOfRange :public exception {  //用於檢查範圍的有效性
public:
    const char* what() const throw() {
        return "ERROR! OUT OF RANGE.\n";
    }
};

class badSize :public exception {   //用於檢查長度的有效性
public:
    const char* what() const throw() {
        return "ERROR! BAD SIZE.\n";
    }
};

#endif

雙鏈表類型的定義

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

template<class elemType>
//elemType爲雙鏈表存儲元素類型 
class doubleLinkList:public List<elemType> {
private:
    //節點類型定義 
    struct Node {
        //節點的數據域 
        elemType data;
        //節點的兩個指針域 
        Node *prior, *next;
        //兩個構造函數 
        Node(const elemType &value, Node *p = NULL, Node *n = NULL) {
            data = value;
            prior = p;
            next = n;
        }
         
        Node():next(NULL), prior(NULL) {}
        ~Node(){} 
    };
    
    //單鏈表的頭指針 
    Node *head;
    //單鏈表的尾指針 
    Node *tail;
    //單鏈表的當前長度 
    int curLength;
    //返回指向位序爲i的節點的指針 
    Node *getPosition(int i)const; 
    
public:
    doubleLinkList();
    ~doubleLinkList();
    //清空單鏈表,使其成爲空表 
    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 
    void remove(int i);
    //查找值爲value的節點的第一次出現的位置 
    int search(const elemType &value)const;
    //查找值爲value的節點的前驅的位序
    int prior(const elemType&value)const;
    //訪問位序爲i的節點的值,0定位到首元結點
    elemType visit(int i)const;
    //遍歷單鏈表
    void traverse()const;
    //逆置單鏈表 
    void inverse();
    //合併單鏈表 
};

雙鏈表基本運算的實現

(一) 構造與析構函數

template <class elemType>
doubleLinkList<elemType>::doubleLinkList() {
    //頭尾節點分別指向 頭結點和尾部頭結點 
    head = new Node;
    tail = new Node;
    head -> next = tail;
    tail -> prior = head;
} 

template <class elemType>
doubleLinkList<elemType>::~doubleLinkList() {
    Node *p = head -> next, *tmp;
    //頭結點的後繼是尾部頭結點 
    head -> next = tail;
    //尾部頭結點的前驅是頭結點 
    tail -> prior = tail;
    
    while(p != tail) {
        tmp = p -> next;
        delete p;
        p = tmp;
    } 
    curLength = 0;  
}

(二) 查找位序爲i的節點的地址

template <class elemType>
typename doubleLinkList<elemType>::Node *doubleLinkList<elemType>::getPosition(int i) const {
    Node *p = head;
    int count = 0;
    if(i < -1 || i > curLength) 
        return NULL;
    while(count <= -1) {
        p = p -> next;
        count++;
    }
    return p;
}

(三) 查找值爲value的節點的位序

template <class elemType>
int doubleLinkList<elemType>::search(const elemType &value) const {
    Node *p = head -> next;
    int i = 0;
    while(p != tail && p -> data != value) {
        p = p -> next;
        i++;
    }
    if(p == tail)
        return -1;
    else 
        return i;
} 

(四) 插入元素

template <class elemType>
void doubleLinkList<elemType>::insert(int i, const elemType &value) {
    Node *p, * tmp;
    if(i < 0 || i > curLength)
        throw outOfRange();
    p = getPosition(i);
    tmp = new Node(value, p -> prior, p);
    //p原先的前驅的後繼指向tmp 
    p -> prior -> next = tmp;
    //修改p的前驅爲tmp
    p -> prior = tmp;
    ++curLength;
} 

(五) 刪除位序爲i的節點

template <class elemType>
void doubleLinkList<elemType>::remove(int i) {
    Node *p;
    if(i < 0 || i > curLength)
        throw outOfRange();
    p = getPosition(i);
    p -> prior -> next = p -> next;
    p -> next -> prior = p -> prior;
    delete p;
    --curLength;
} 

(六) 訪問位序爲 i的節點的值

template <class elemType>
elemType doubleLinkList<elemType>::visit(int i) const {
    //visit 不嫩直接用getPosition判斷範圍是否合法,因爲其範圍爲[-1,curLength]
    if(i < 0 || i > curLength -1)
        throw outOfRange();
    //合法以後 
    Node *p = getPosition(i);
    return p -> data; 
}

(七) 遍歷雙鏈表

template <class elemType>
void doubleLinkList<elemType>::traverse() const {
    Node *p = head -> next;
    cout << "traverse: ";
    while(p != tail) {
        cout << p -> data << " ";
        p = p -> next;
    }
    cout << endl;
}   

(八) 遍歷雙鏈表

template <class elemType>
void doubleLinkList<elemType>::inverse() {
    Node *tmp, *p = head -> next;
    //構成雙空鏈表 
    head -> next = tail;
    tail -> prior = head;
    while(p != tail) {
        tmp = p -> next;
        p -> next = head -> next;
        p -> prior = head;
        head -> next -> prior = p;
        head -> next = p;
        p = tmp;
    } 
} 

結尾:

如果文章中有什麼不足,或者錯誤的地方,歡迎大家留言分享想法,感謝朋友們的支持!

如果能幫到你的話,那就來關注我吧!如果您更喜歡微信文章的閱讀方式,可以關注我的公衆號

在這裏的我們素不相識,卻都在爲了自己的夢而努力 ❤

一個堅持推送原創開發技術文章的公衆號:理想二旬不止

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