1. 程式人生 > >資料結構圖文解析之:陣列、單鏈表、雙鏈表介紹及C++模板實現

資料結構圖文解析之:陣列、單鏈表、雙鏈表介紹及C++模板實現

0. 資料結構圖文解析系列

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. 單鏈表、雙向連結串列原始碼

另外宣告:

  1. C++模板不支援分離編譯,因此類定義與成員函式的實現都在.h檔案中完成;
  2. 可以看到程式碼中new一個新節點之後,並沒有使用(prt!=nullptr)來檢查記憶體分配是否成功,這是因為new失敗時直接丟擲異常,不同於C語言malloc記憶體分配失敗返回NULL。

相關推薦

資料結構圖文解析陣列單鏈介紹C++模板實現

0. 資料結構圖文解析系列 1. 線性表簡介 線性表是一種線性結構,它是由零個或多個數據元素構成的有限序列。線性表的特徵是在一個序列中,除了頭尾元素,每個元素都有且只有一個直接前驅,有且只有一個直接後繼,而序列頭元素沒有直接前驅,序列尾元素沒有直接後繼。 資料結構中常見的線性結構有陣列、單鏈表、雙鏈表、迴圈

資料結構圖文解析樹的簡介二叉排序樹C++模板實現.

  閱讀目錄 0. 資料結構圖文解析系列 1. 樹的簡介 1.1 樹的特徵 1.2 樹的相關概念 2. 二叉樹簡介 2.1 二叉樹的定義 2.2 斜樹、滿二叉樹、完全二叉樹、二叉查詢樹 2

資料結構圖文解析佇列詳解與C++模板實現

正文 回到頂部 0. 資料結構圖文解析系列 回到頂部 1. 佇列簡介 回到頂部 1.1 佇列的特點 佇列(Queue)與棧一樣,是一種線性儲存結構,它具有如下特點: 佇列中的資料元素遵循“先進先出”(First In First Out)的原則,簡稱FI

資料結構圖文解析二叉堆詳解C++模板實現

0. 資料結構圖文解析系列 1. 二叉堆的定義 二叉堆是一種特殊的堆,二叉堆是完全二叉樹或近似完全二叉樹。二叉堆滿足堆特性:父節點的鍵值總是保持固定的序關係於任何一個子節點的鍵值,且每個節點的左子樹和右子樹都是一個二叉堆。 當父節點的鍵值總是大於或等於任何一個子節點的鍵值時為最大堆。 當父節點的鍵值總是小於

資料結構圖文解析棧的簡介C++模板實現

0. 資料結構圖文解析系列 1. 棧的簡介 1.1棧的特點 棧(Stack)是一種線性儲存結構,它具有如下特點: 棧中的資料元素遵守”先進後出"(First In Last Out)的原則,簡稱FILO結構。 限定只能在棧頂進行插入和刪除操作。 1.2棧的相關概念 棧的相關概念: 棧頂與棧底:允許元素

資料結構圖文解析哈夫曼樹與哈夫曼編碼詳解C++模板實現

0. 資料結構圖文解析系列 1. 哈夫曼編碼簡介 哈夫曼編碼(Huffman Coding)是一種編碼方式,也稱為“赫夫曼編碼”,是David A. Huffman1952年發明的一種構建極小多餘編碼的方法。 在計算機資料處理中,霍夫曼編碼使用變長編碼表對源符號進行編碼,出現頻率較高的源符號採用較短的編碼,

資料結構圖文解析直接插入排序及其優化(二分插入排序)解析C++實現

0. 資料結構圖文解析系列 1. 插入排序簡介 插入排序是一種簡單直觀的排序演算法,它也是基於比較的排序演算法。它的工作原理是通過不斷擴張有序序列的範圍,對於未排序的資料,在已排序中從後向前掃描,找到相應的位置並插入。插入排序在實現上通常採用就地排序,因而空間複雜度為O(1)。在從後向前掃描的過程中,需要反

資料結構圖文解析二分查詢與其相關的幾個問題解析

0. 資料結構圖文解析系列 1. 二分查詢簡介 二分查詢大家都不陌生,可以說除了最簡單的順序查詢之外,我們第二個接觸的查詢演算法就是二分查找了。順序查詢的時間複雜度是O(n),二分查詢的時間複雜度為O(logn)。在面試中二分查詢被考察的概率還是比較高的,上次去面試時就遇到手寫二分查詢的題目。二分查詢不難,

資料結構(1)陣列

1.陣列優勢 (1)快速查詢 (2)適用於有語境的情況 2.製作私有陣列 (1)使用泛型,從而可以實現儲存多種型別資料 (2)可以動態擴容或者縮容 (3)實現增刪改查基本操作 3.java實現 public class ArrayDynamic<E> {

資料結構與演算法二叉搜尋樹插入查詢與刪除

1 二叉搜尋樹(BSTree)的概念   二叉搜尋樹又被稱為二叉排序樹,那麼它本身也是一棵二叉樹,那麼滿足以下性質的二叉樹就是二叉搜尋樹,如圖: 若左子樹不為空,則左子樹上所有節點的值都小於根節點的值; 若它的右子樹不為空,則它的右子樹上所有節點的值都大於

資料結構AVL樹詳解C++模板實現

AVL樹簡介AVL樹的名字來源於它的發明作者G.M. Adelson-Velsky 和 E.M. Landis。AVL樹是最先發明的自平衡二叉查詢樹(Self-Balancing Binary Search Tree,簡稱平衡二叉樹)。一棵AVL樹有如下必要條件:條件一:它必

資料結構與演算法有序陣列(2)——in dart

本文比第一篇,採用了類實現。增加了運算子過載等功能。本來有序陣列是不能修改某個位置的值的,因為這樣會打破陣列的有序性;但為了演示,保留了修改的方法,但為此增加了排序。   1 import 'dart:math' show Random; 2 3 final _rnd = R

Java資料結構與演算法Array陣列

目錄:1.陣列概述 2.java中陣列的初始化3.java中針對陣列的API4.陣列排序5.字串轉陣列(char[]/int[])6.二維陣列的操作 1.陣列概述陣列是重要的資料結構之一,以線性結構來儲存固定數量大小,資料型別相同的資料 2.java中陣列的初始化(1)宣告

一本正經的聊資料結構(2)陣列與向量

前文傳送門: 一本正經的聊資料結構(1):時間複雜度 引言 這個系列沒有死,我還在更新。 最近事情太多了,這篇文章也是斷斷續續寫了好幾天才湊完。 上一篇我們介紹了一個基礎概念「時間複雜度」,這篇我們來看第一個真正意義上的資料結構「陣列」。 那為什麼題目中還會有一個向量呢?這個是什麼東西? 不要急,且聽我慢

陣列單鏈介紹 以及 雙向連結串列的C/C++/Java實現

1 #include <stdio.h> 2 #include <malloc.h> 3 4 /** 5 * C 語言實現的雙向連結串列,能儲存任意資料。 6 * 7 * @author skywang 8 * @date 2

演算法題(十九)二叉搜尋樹轉

題目描述 輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。要求不能建立任何新的結點,只能調整樹中結點指標的指向。 輸入輸出示例: {10,6,4,8,14,12,16} from left to right:4,6,8,10,12,14,16 from right

影象去霧何凱明暗通道先驗去霧演算法原理c++程式碼實現

http://blog.csdn.net/s12244315/article/details/50292049 何凱明博士,2007年清華大學畢業,2011年香港中文大學博士畢業,可謂是功力深厚,感嘆於國內一些所謂博士的水平,何這樣的博士才可以真正叫做

資料結構和演算法陣列奇數偶數分離

        今日,博主在面試一家外企的時候,要求白板寫程式。其中就有一道演算法設計題目,下面就來分享一下這道題的演算法思路和相關示例程式碼。         題目:要求將一個整形陣列中的奇數和偶數進行分離,偶數在

資料結構與演算法美(三)陣列

陣列看起來簡單基礎,但是很多人沒有理解這個資料結構的精髓。帶著為什麼陣列要從0開始編號,而不是從1開始的問題,進入主題。 一、 如何實現隨機訪問 1) 陣列是一種線性資料結構,用連續的儲存空間儲存相同型別資料: I) 線性表:陣列、連結串列、佇列、棧 ;非線性表

小朋友學資料結構(1)約瑟夫環的連結串列解法陣列解法和數學公式解法

約瑟夫環(Josephus)問題是由古羅馬的史學家約瑟夫(Josephus)提出的,他參加並記錄了公元66—70年猶太人反抗羅馬的起義。約瑟夫作為一個將軍,設法守住了裘達伯特城達47天之久,在城市淪陷之後,他和40名死硬的將士在附近的一個洞穴中避難。在那裡,這些