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

雙鏈表

雙鏈表的意義

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

刪除操做

單鏈表:

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

可是問題就在於咱們如何獲得待刪除節點的前驅,也就是咱們圖中的首元結點,咱們給出兩種方法函數

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

可是不管咱們選擇哪種方法,指針的總移動數都會是 2n 次,而雙鏈表卻在這一類型問題上作出了很好的處理spa

雙鏈表:

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

注意:爲了統一運算,避免特殊狀況的出現,咱們也經常在尾部設置一個 「尾部頭結點」 其 next 指針域爲空code

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

咱們在給出雙鏈表的定義以前咱們仍是須要先引入咱們線性表的抽象數據類型定義blog

#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;
    } 
}

結尾:

若是文章中有什麼不足,或者錯誤的地方,歡迎你們留言分享想法,感謝朋友們的支持!開發

若是能幫到你的話,那就來關注我吧!若是您更喜歡微信文章的閱讀方式,能夠關注個人公衆號rem

在這裏的咱們素不相識,卻都在爲了本身的夢而努力 ❤get

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

相關文章
相關標籤/搜索