C++数据结构: 线性表之单链表

本文介绍了C++中单链表的实现,包括节点定义、链表类模板的实现,强调了头结点特性及操作注意事项,如指针移动和内存管理。单链表在插入和删除操作上具有O(1)的时间复杂度,但在查找上不如顺序表。实例调用验证了其功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇我们已经实现过了线性表的顺序存储结构:顺序表,现在我们继续来实现基本的线性表的线性存储结构之单链表;
首先,单链表的存储结构是以节点数据为基本存储单元的(一个节点存储一个数据,头结点不存放数据),其次,其内存是向堆中申请的,而不是向栈中申请的,所以单链表的长度是可拓展的。所以其实现方式与上一次顺序表的实现方式大有不同。下面请看:
首先,我们的先实现存储数据的节点(为适应数据的不同类型,使用了类模板机制):

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

调用结果:
调用成功截图

最后一点:
这只是单链表地基本实现方式,要想用来解决实际问题,还要经过添加各种算法(函数)。
以上!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值