數據結構圖文解析之:數組、單鏈表、雙鏈表介紹及C++模板實現


0. 數據結構圖文解析系列

數據結構系列文章
數據結構圖文解析之:數組、單鏈表、雙鏈表介紹及C++模板實現
數據結構圖文解析之:棧的簡介及C++模板實現
數據結構圖文解析之:隊列詳解與C++模板實現
數據結構圖文解析之:樹的簡介及二叉排序樹C++模板實現.
數據結構圖文解析之:AVL樹詳解及C++模板實現
數據結構圖文解析之:二叉堆詳解及C++模板實現

1. 線性表簡介

線性表是一種線性結構,它是由零個或多個數據元素構成的有限序列。線性表的特征是在一個序列中,除了頭尾元素,每個元素都有且只有一個直接前驅,有且只有一個直接后繼,而序列頭元素沒有直接前驅,序列尾元素沒有直接后繼。
數據結構中常見的線性結構有數組、單鏈表、雙鏈表、循環鏈表等。線性表中的元素為某種相同的抽象數據類型。可以是C語言的內置類型或結構體,也可以是C++自定義類型。

2. 數組

數組在實際的物理內存上也是連續存儲的,數組有上界和下界。C語言中定義一個數組:

數組下標是從0開始的,a[0]對應第一個元素。其中,a[0]稱為數組a的下界,a[6]稱為數組a的上屆。超過這個范圍的下標使用數組,將造成數組越界錯誤
數組的特點是:數據連續,支持快速隨機訪問。
數組分為固定數組與動態數組。其中固定數組的大小必須在編譯時就能夠確認,動態數組允許在運行時申請數組內存。復雜點的數組是多維數組,多維數組實際上也是通過一維數組來實現的。在C語言中,可以通過malloc來分配動態數組,C++使用new。另外,C++的標准模板庫提供了動態數組類型vector以及內置有固定數組類型array。

3. 單向鏈表

單向鏈表是鏈表的一種。鏈表由節點所構成,節點內含一個指向下一個節點的指針,節點依次鏈接成為鏈表。因此,鏈表這種數據結構通常在物理內存上是不連續的。鏈表的通常含有一個頭節點,頭節點不存放實際的值,它含有一個指針,指向存放元素的第一個節點。

3.1 單向鏈表的節點結構

//節點結構
template <typename T>
class Node
{
public :
    T _value;
    Node* _next;
public:
    Node() = default;
    Node(T value, Node * next)
        : _value(value), _next(next){}
};
  1. _value: 節點的值
  2. _next: 指針,指向下一個節點

3.2 單向鏈表的抽象數據結構

//單鏈表
template <typename T>
class SingleLink
{
public:
    typedef Node<T>*  pointer;
    SingleLink();
    ~SingleLink();
 
    int size();                         //獲取長度
    bool isEmpty();                     //判空
 
    Node<T>* insert(int index, T t); //在指定位置進行插入
    Node<T>* insert_head(T t);         //在鏈表頭進行插入
    Node<T>* insert_last(T t);         //在鏈表尾進行插入
 
    Node<T>*  del(int index);         //在指定位置進行刪除
    Node<T>*  delete_head();         //刪除鏈表頭
    Node<T>*  delete_last();         //刪除鏈表尾
 
    T get(int index);                 //獲取指定位置的元素
    T get_head();                     //獲取鏈表頭元素
    T get_last();                     //獲取鏈表尾元素
 
    Node<T>* getHead();                 //獲取鏈表頭節點
 
private :
    int count;
    Node<T> * phead;                
 
private :
    Node<T> * getNode(int index);      //獲取指定位置的節點
};
  1. phead: 鏈表的頭節點。
  2. count: 鏈表元素個數。

3.3 單鏈表添加節點

鏈表的插入元素操作時間復雜度O(1),只需要進行指針的指向修改操作。

在2之后添加7:

  1. 為元素7構建節點 。
  2. 將節點2 的next指針指向節點7。
  3. 將節點7的next指向節點3。(節點3 的位置要先保留起來)
/*
在指定位置插入新節點
*/
template <typename T>
Node<T>* SingleLink<T>::insert(int index, T t)
{
    Node<T> * preNode = getNode(index);
    if (preNode)
    {
        Node<T> *newNode = new Node<T>(t,preNode->_next);
        preNode->_next = newNode;
        count++;
        return newNode;
    }
    return nullptr;
};
/*
從頭部插入
*/
template <typename T>
Node<T>* SingleLink<T>::insert_head(T t)
{
    return insert(0, t);
};
/*
從尾部進行插入
*/
template <typename T>
Node<T>* SingleLink<T>::insert_last(T t)
{
    return insert(count, t);
};

3.4 單鏈表刪除節點

單鏈表的刪除操作同樣是一個時間復雜度O(1)的操作,它也只需要修改節點的指針指針后即可銷毀被刪除節點。
例如我們刪除鏈表元素7:

相應的代碼:

/*
刪除鏈表指定位置元素
*/
template <typename T>
Node<T>* SingleLink<T>::del(int index)
{
    if (isEmpty())
        return nullptr;
    Node<T>* ptrNode = getNode(index);
    Node<T>* delNode = ptrNode->_next;
    ptrNode->_next = delNode->_next;
    count--;
    delete delNode;
    return ptrNode->_next;
};
/*
刪除頭節點
*/
template<typename T>
Node<T>* SingleLink<T>::delete_head()
{
    return del(0);
};
/*
刪除尾節點
*/
template<typename T>
Node<T>*SingleLink<T>::delete_last()
{
    return del(count);
};

3.5 單鏈表代碼測試

int main()
{
    SingleLink<int> link;
    for (int i = 0; i < 10; i++)
    {
        link.insert(i, i);
    }
    cout << link.size() << endl;
 
    link.insert_head(1111);
    link.insert_last(2222);
 
    SingleLink<int>::pointer ptr = link.getHead();
    while (ptr != nullptr)
    {
        cout << ptr->_value << endl;
        ptr = ptr->_next;
    }
 
    getchar();
    return 0;
}

測試結果:

10
1111
0
1
2
3
4
5
6
7
8
9
2222

其他的操作較為簡單,不在這里貼出代碼,文章底部有完整鏈表類的代碼鏈接。

4. 雙向鏈表

單鏈表的節點鏈接是單方向的,要得到指定節點的前一個節點,必須從頭遍歷鏈表。
雙向鏈表是鏈表的一種。與單鏈表一樣,雙向節點由節點鏈接而成,每個節點含有兩個指針,分別指向直接前驅與直接后繼。從雙向鏈表的任何一個節點開始都能夠遍歷整個鏈表。
我們將雙向鏈表實現為雙向循環鏈表,也即是最后一個元素的后繼將指向頭節點,整個鏈表形成一個循環
例如,我們為元素1,2,3,4,5 構建一個雙向循環鏈表

在圖中:
表頭為空。
表頭的前驅節點是節點5,表頭的后繼節點是節點1;
節點1的前驅節點是表頭,節點1的后繼節點是節點2;
節點2的前驅節點是節點1,節點2的后繼節點是節點3;
...

4.1 雙向鏈表節點結構

雙向循環的節點中,比單向鏈表中多了一個指向直接前驅的指針

/*
雙向鏈表的節點結構
*/
template <typename T>
struct Node
{
public:
    Node()= default;
    Node(T value, Node<T>* preptr, Node<T>* nextptr)
        :_value(value), pre_ptr(preptr), next_ptr(nextptr){}
 
public:
    T _value;
    Node<T>* pre_ptr;
    Node<T>* next_ptr;
};
  1. _value: 節點元素的值
  2. pre_ptr:指向直接前驅的指針
  3. next_ptr:指向直接后繼的指針

4.2 雙向鏈表的抽象數據結構

雙向鏈表類的定義與單鏈表相似。

/*
* 雙向鏈表類
*/
template<typename T>
class DoubleLink
{
public:
    typedef Node<T>* pointer;
public:
    DoubleLink();
    ~DoubleLink(){};
public:
    Node<T>* insert(int index, T value);
    Node<T>* insert_front(T value);
    Node<T>* insert_last(T value);
 
    Node<T>* del(int index);
    Node<T>* delete_front();
    Node<T>* delete_last();
 
    bool isEmpty();
    int size();
 
    T get(int index);
    T get_front();
    T get_last();
    Node<T>* getHead();
 
private:
    Node<T>* phead;
    int count;
private :
    Node<T>* getNode(int index);
};

4.3 雙向鏈表添加節點

與單鏈表一樣,雙向鏈表添加節點的時間復雜度為O(1),它也只需要修改相關指針的指向。

/*
*將新節點插到第一個位置
*/
template <typename T>
Node<T>* DoubleLink<T>::insert_front(T value)
{
    Node<T>* newNode = new Node<int>(value, phead, phead->next_ptr);
    phead->next_ptr ->pre_ptr= newNode;
    phead->next_ptr = newNode;
    count++;
    return newNode;
};
/*
*將新節點插到鏈表尾部
*/
template <typename T>
Node<T>* DoubleLink<T>::insert_last(T value)
{
    Node<T> * newNode = new Node<int>(value, phead->pre_ptr, phead);
    phead->pre_ptr->next_ptr = newNode;
    phead->pre_ptr = newNode;
    count++;
    return newNode;
};
/*
*將節點位置插到index位置之前
*/
 
template <typename T>
Node<T>* DoubleLink<T>::insert(int index, T value)
{
    if (index == 0)
        return insert_front(value);
 
    Node<T>* pNode = getNode(index);
    if (pNode == nullptr)
        return nullptr;
    Node<T>* newNode = new Node<T>(value, pNode->pre_ptr, pNode);
    pNode->pre_ptr->next_ptr = newNode;
    pNode->pre_ptr = newNode;
    count++;
 
    return newNode;
};

4.4 雙向鏈表刪除節點

雙向鏈表的刪除操作時間復雜度為O(1).我們刪除節點7:

/*
*刪除鏈表第一個節點
*返回刪除后鏈表第一個節點
*/
template<typename T>
Node<T>* DoubleLink<T>::delete_front()
{
    if (count == 0)
    {
        return nullptr;
    }
    Node<T>* pnode = phead->next_ptr;
    phead->next_ptr = pnode->next_ptr;
    pnode->next_ptr->pre_ptr = phead;
    delete pnode;
    count--;
    return phead->next_ptr;
};
/*
*刪除鏈表的末尾節點
*返回刪除后鏈表尾部元素
*/
template<typename T>
Node<T>* DoubleLink<T>::delete_last()
{
    if (count == 0)
    {
        return nullptr;
    }
    Node<T>*pnode = phead->pre_ptr;
    pnode->pre_ptr->next_ptr = phead;
    phead->pre_ptr = pnode->pre_ptr;
    delete pnode;
    count--;
    return phead->pre_ptr;
}
/*
*刪除指定位置的元素
*
*/
template <typename T>
Node<T>* DoubleLink<T>::del(int index)
{
    if (index == 0)
        return delete_front();
    if (index == count - 1)
        return delete_last();
    if (index >= count)
        return nullptr;
    Node<T>* pnode = getNode(index);
    pnode->pre_ptr->next_ptr = pnode->next_ptr;
    pnode->next_ptr->pre_ptr = pnode->pre_ptr;
 
    Node<T>* ptemp = pnode->pre_ptr;
    delete pnode;
    count--;
    return ptemp;
};

其他的接口實現都很簡單,這里不再講解。下面有提供完整的工程項目及源代碼。

4.5 雙向鏈表代碼測試


int main()
{
    DoubleLink<int> dlink;
    //插入測試
    for (int i = 0; i < 10; i++)
    {
        dlink.insert(0, i+10);
    }
    dlink.insert(0,  100);
    dlink.insert_last(1000);
    cout <<"鏈表長度:"<< dlink.size() << endl;
 
    //刪除測試
    dlink.delete_front();
    dlink.delete_last();
    dlink.del(3);
 
 
    DoubleLink<int>::pointer ptr = dlink.getHead();
    ptr = ptr->next_ptr;
    while (ptr != dlink.getHead())
    {
        cout << ptr->_value<<endl;
        ptr = ptr->next_ptr;
    }
 
    getchar();
    return 0;
}

測試結果:

鏈表長度:12
19
18
17
15
14
13
12
11
10

5. 單鏈表、雙向鏈表源代碼

單鏈表github源代碼:https://github.com/huanzheWu/Data-Structure/blob/master/singleList/singleList/singleList.h
雙鏈表github源代碼:https://github.com/huanzheWu/Data-Structure/blob/master/DoubleLink/DoubleLink/DoubleLink.h

另外聲明:

  1. C++模板不支持分離編譯,因此類定義與成員函數的實現都在.h文件中完成;
  2. 可以看到代碼中new一個新節點之后,並沒有使用(prt!=nullptr)來檢查內存分配是否成功,這是因為new失敗時直接拋出異常,不同於C語言malloc內存分配失敗返回NULL。

原創文章,轉載請注明出處:http://www.cnblogs.com/QG-whz/p/5170147.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM