C++數據結構: 線性表之單鏈表

上一篇我們已經實現過了線性表的順序存儲結構:順序表,現在我們繼續來實現基本的線性表的線性存儲結構之單鏈表;
首先,單鏈表的存儲結構是以節點數據爲基本存儲單元的(一個節點存儲一個數據,頭結點不存放數據),其次,其內存是向堆中申請的,而不是向棧中申請的,所以單鏈表的長度是可拓展的。所以其實現方式與上一次順序表的實現方式大有不同。下面請看:
首先,我們的先實現存儲數據的節點(爲適應數據的不同類型,使用了類模板機制):

#pragma once   //避免被包含多次
template<typename T>
class Node       /*與書中不同,我這裏並沒有採用結構體類型,而是採用了類,但實際上功能是一樣的*/
{
public:
    T data;       //數據存儲域
    Node *next;    //指向下一個節點的指針
};

實現了單鏈表的數據存放節點之後,我們再來實現單鏈表的類模板實現

 #pragma once      /*避免文件被include多次效果與   #ifndef __SOMEFILE_H__
    #define __SOMEFILE_H__
    #endif   相同*/
 #include<iostream>       //頭文件的引入
 #include"Node.h";
using namespace std;

template<typename T>           /*類模板的聲明,成員函數中實現了基本的增刪查改,數據成員中有數據的頭結點,和計算鏈表長度的int類型的Length成員(這個在書中並沒有)*/
class LinkList
{
public:
    LinkList(T a[], int n);      //有參構造函數 使用了頭插法
    ~LinkList();          //析構函數
    int Length();         //返回單鏈表的長度
    T Get(int i);           //按位查找,查找第i個節點的元素
    int Locate(T x);        //按值查找,查找鏈表中第一個值爲x的元素,並返回序號
    bool Insert(int i, T x);   //插入元素,在第i個位置插入值x
    bool Delete(int i);       //刪除節點,刪除第i個節點
    bool InsertHead(T x);    //頭插法插入節點
    bool InsertTail(T x);    //尾插法插入節點
    void ListTraverse();      //遍歷節點
private:
    Node<T> *first;               //頭結點的指針
    int  m_Length;       //實際使用過程當中,添加多一個數據成員Length會更好
};


**以下是成員函數的實現**

template<typename T>
LinkList<T>::LinkList(T a[], int n)     //有參構造函數,  使用頭插法(注意點:頭插法是將元素放在頭結點的後面)
{
    first = new Node<T>;        //空鏈表的初始化
    first->next = NULL;      
    for (int i=0;i<n;i++)      //有疑問:這樣做,頭結點是不是沒數據?  經過調試,發現頭結點是不存儲數據的
    {
        Node<T> *s = new Node<T>;
        s->data = a[i];
        s->next = first->next;
        first->next = s;
    }
    m_Length = n;
    /*for (int i = 0; i < n; i++)     //直接調用下面的頭插法插入數據更方便!
    {
        InsertHead(a[i]);
    }*/


}
//template<typename T>
//LinkList::LinkList(T a[], int n)         //同樣是有參構造函數,但是使用的是尾插法
//{
//  Node<T> *first = new NOde<T>;
//  Node<T> *r = first;      //將頭指針賦值給變量r
//  for (int i = 0; i < n; i++)
//  {
//      s = new Node;   
//      s->data = a[i];    //創建新節點,賦值
//      r->next = s;     //將r的指針指向s
//      r = s;         //變量r後移到最後一個節點
//  }
//  r->next = NULL;   //將尾節點的指針置空
//}

template<typename T>
bool LinkList<T>::InsertHead(T x)      //頭插發插入數據
{
    Node<T> *Temp = first->next;           //建立指向頭指針的臨時變量
    Node<T> *s = new Node<T>;         //建立新節點
    if (s==NULL)         //判斷新節點是否申請成功
    {
        return false;
    }
    s->data = x;         //賦值
    s->next = Temp;      //上鍊  
    first->next = s;
    m_Length++;       //鏈表的長度加一
    return true;       //插入成功,返回true
}

template<typename T>
bool LinkList<T>::InsertTail(T x)      //使用尾插法插入數據,使數據插入到最後一個
{
    Node<T> *p = first;        //建立臨時遍歷指針
    Node<T> *s = new Node<T>;   //建立新節點
    if (s == NULL)      //判斷新節點是否申請成功,若申請失敗,則退出函數,不插入數據
    {
        return false;
    }
    s->data = x;
    while (p->next != NULL)        //遍歷指針,使臨時指針指向尾節點
    {
        p = p->next;
    }
    p->next = s;         //尾節點重新導向,將新節點上鍊
    s->next = NULL;     //將上鍊後的尾節點的指針域指向空
    m_Length++;
    return true;      //返回true,插入成功
}

template<typename T>
LinkList<T>::~LinkList()         //析構函數
{
    while (first!=NULL)
    {
        Node<T> *q = first;        //遍歷刪除頭指針指向的節點,將頭指針暫存
        first = first->next;      //將頭指針後移
        delete q;        //從鏈表中脫離出來的指針刪除,釋放內存
    }
    m_Length = 0;
}
template<typename T>
int LinkList<T>::Length()          /*返回鏈表的長度的算法,實現思想:設定循環函數,將節點的指針從頭指針開始依次後移,
後移一次便將計數器自加1,直至到尾節點的指針爲空,此時結束循環,返回計數器*/
{
    /*int num=0;
    Node<T> *p = first->next;
    while (p!= NULL)
    {
        p = p->next;
        num++;
    }*/
    return m_Length;   //添加數據成員length後,使得返回鏈表的長度函數更簡單,代碼更少  
    /*return num;*/
}
template<typename T>
T LinkList<T>::Get(int i)    //按位查找,返回第i個節點的元素
{
    Node<T> *p = first->next;
    int count = 1;
    while (p!= NULL&&count < i)
    {
        p = p->next;
        count++;
    }
    if (p == NULL)
    {
        throw"位置";
    }
    else
    {
        return p->data;
    }
}
template<typename T>
int LinkList<T>::Locate(T x)          //按值查找,返回d第一個匹配值的序號
{
    Node<T> *p = first->next;
    int count = 1;
    while (p != NULL)
    {
        if (p->data == x)
        {
            return count;
        }
        p = p->next;
        count++;
    }
    return 0;
}
template<typename T>
bool LinkList<T>::Insert(int i,T x)      //往鏈表中插入元素,i爲要插入的位置,x爲要插入的值
{
    Node<T> *p = first;
    int count = 0;
    int num = i - 1;
    while (p!= NULL&&count <num)
    {
        p = p->next;
        count++;
    } 
    if (p == NULL)
    {
        return false;
    }
    else
    {
        Node<T> *s = new Node<T>; 
        s->data = x;
        s->next = p->next;
        p->next = s;
        m_Length++;
        return true;
    }

}
template<typename T>
void LinkList<T>::ListTraverse()
{
    Node<T> *p = first->next;
    while (p != NULL)
    {
        cout << p->data<<",";
        p = p->next;              //遍歷的指針的後移,注意不能寫成p++,因爲這是節點
    }
}
template<typename T>
bool LinkList<T>::Delete(int i)
{
    Node<T> *p = first;
    int count = 0;
    while (p != NULL&&count < i - 1)
    {
        p = p->next;
        count++;
    }
    if (p == NULL)
    {
        return false;
    }
    else
    {
        Node<T> *q;
        q = p->next;
        p->next = q->next;
        delete q;
        m_Length--;
        return true;
    }
}

以上就是單鏈表類模板的實現。

需要注意的幾個點:

  • 頭結點並不實際存放實際的數據,所以在實現個別成員函數(如插入指定位置的函數,查找指定值所在位置的函數,刪除函數)的時候需要特別注意,頭節點並不是第一個數據節點,而是,頭結點的下一個節點,所以,實現的時候需要注意一下,比如,計算位置的num數該從何時開始計數?該從何時開始循環?循環何時結束?
  • 指針的移動,不是p++,而是p->next
  • 節點數據中存放的指針指向的是下一個一整塊的節點,而不僅僅是下一個節點的存儲數據的域、
  • 時間複雜度方面:插入和刪除時無需移動元素時間複雜度爲o(1) (優點) ,而按位查找方面則不如順序表方便。時間函數爲o(n),所以解決問題時根據需求選擇存儲結構
  • 刪除和析構函數方面:由於這是向堆中申請的內存,所以不能單純地摘鏈,還要將申請到地空間釋放出來

實例調用:

#include<iostream>
#include"LinkList.h"
using namespace  std;

int main()
{
    int a[5] = {1,2,3,4,5};
    LinkList<int>  MyList(a,5);
    cout << MyList.Length() << endl;         //鏈表長度函數調用成功
    cout << "第5個節點的元素爲:" << MyList.Get(5) << endl;     //輸入有效長度的數據可成功調用,但是,若輸入不合法的數據將出錯,throw的語句未完成
    if (MyList.Locate(5) != 0)
    {
        cout << "元素5所在的位置爲:" << MyList.Locate(5) << endl;
    }    //輸入合法的數據,測試成功
    else if(MyList.Locate(5)==0){
        cout << "輸入數據不合法,輸入的節點位置超過鏈表超度" << endl;
    }
    if (MyList.Insert(3, 2))
    {
        cout << "插入元素成功!" << endl;
    }//插入元素測試成功
    else
    {
        cout << "插入元素失敗!" << endl;
    }
    MyList.ListTraverse();
    if(MyList.Delete(3))
    { 
        cout << "刪除節點成功" << endl;
    }
    else
    {
        cout << "刪除節點失敗" << endl;
    }
    MyList.ListTraverse();     //單鏈表的遍歷成功
    cout << endl;
    MyList.InsertHead(7);       //調用頭插法
    cout << "調用頭插法成功!";
    MyList.ListTraverse();
    MyList.InsertTail(4);     //調用尾插法
    cout << endl;
    cout << "調用尾插法成功!";
    MyList.ListTraverse();
    return 0;
}

調用結果:
調用成功截圖

最後一點:
這只是單鏈表地基本實現方式,要想用來解決實際問題,還要經過添加各種算法(函數)。
以上!

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