C++ 单链表的声明和定义

犹豫了很久,还是决定在这个大佬云集的地方写一些自己的东西。本人只是非计算机专业的一名本科生,在学习程序设计课程和实际应用的过程中,有很多的问题都是靠本社区的分享得以解决的。饮水思源,我也想分享一些自己的感悟,如果能帮助到其他人最好了。但限于个人水平,分享的内容难免有谬误,望谅解和指正。

PS:本人文章中的图片和代码部分来自网络或老师课件。故本人文章仅供学习参考(估计也没有别的用途)

关于内容的形式,我有考虑过:教程(能力不足),具体题目解答(局限性大),学习笔记(懒癌晚期,坚持不下去)。最终决定分享一些问题的通用解决方案和思路,如本文 “用C++ 实现单链表”,其实这也是我第一个面临的需要自行解决的问题。文体上力求简洁明了,突出重点。下面正式开篇。

单链表是一种线性表的链接存储结构。具体的数据结构知识这里就不作赘述了。

关于数据结构的构建问题,我强烈建议画图理解,明确我们需要的各部分结构,才能明确方向和方法。

根据这个结构图,我们可以看出要定义的两个部分“结点Node”和以结点为基本要素构成的“链表LinkList”,其他的函数方法也是基于这两者的定义,故要先巩固基本。这里大家初学时一定要好好明确链表中结点的指针域“next”是指向下一个结点(后继)的。并形成对链表结构根本的操作概念——“从头到尾”和“变指针域”。

结点结构体的声明与定义如下,如果有不懂模板类“template”的小伙伴,建议先去了解一下,很简单也很实用的,尤其是在数据结构这种通用性需求高的领域。

template <class DataType>          // 结点结构体
struct Node {
    DataType data; //数据域
    Node<DataType> *next;//指针域
};

链表类的大体声明如下。根据定义,每一个链表都要有一个头结点“first” 。其他的函数方法,可以按需设计。

template <class DataType>
class LinkList {
public:
    LinkList(); //构造函数
    LinkList(DataType a[], int n);//含参构造函数
    ~LinkList();//析构函数
    int Length();//计算链表产长度
    DataType Get(int i);//按位查找
    int Locate(DataType x);//按值查找
    void Insert(int i, DataType x);//插入值
    DataType Delete(int i);//删除元素
    void PrintList();//遍历链表
private:
    Node<DataType> *first;//头指针
};

 链表默认的构造函数,给你个头,剩下的自行添加。这里要注意避免野指针哦!指针一定要有所指向,如果暂时没有指向,就要将指针赋值为NULL。

 就是这样的空表一个。

template <class DataType>
LinkList<DataType>::LinkList() {
    cout << "链表的默认构造函数" << endl;
    first = NULL;//防止野指针
}

 链表的含参(顺序数组类型输入)构造函数。这里有两种初始化方法,头插法(链表顺序和数组储存顺序相反)和尾插法(链表顺序和数组储存顺序相同),具体的特点这里不做赘述,给出定义。

template <class DataType>
LinkList<DataType>::LinkList(DataType a[], int n) {
    //头插法,链表顺序和数组储存顺序相反
    cout << "链表的有参构造函数:头插法" << endl;
    first = new Node<DataType>; first->next = NULL;
    Node<DataType> *s;
    for (int i = 0; i < n; i++) {
        s = new Node<DataType>; s->data = a[i];
        s->next = first->next;
       first->next = s;
    }
    cout << "链表的有参构造函数:尾插法" << endl;
    //尾插法,链表顺序和数组储存顺序相同
    first = new Node<DataType>;     //生成头结点
    Node<DataType> *s, *r = first;  //尾指针初始化
    for (int i = 0; i < n; i++) {
        s = new Node<DataType>; s->data = a[i];
        r->next = s; r = s;
    }
    r->next = NULL;
}

 析构函数,对于链表,仅需要遍历链表并释放指针即可。尽管C++目前的规范里会自动释放工作栈,也就是自动添加析构函数,但是还是建议大家有这个良好的习惯。

PS:保证链表未处理的部分不断开

template <class DataType>
LinkList<DataType>::~LinkList() {
    cout << "链表的析构函数" << endl;
    Node<DataType> *q;
    while (first != NULL) {
        q = first;
        first = first->next;
        delete q;
    }
}

 计算链表的结点数,就是遍历一遍计个数。其实完全可以在构造链表和插入删除操作中嵌入这一概念,并将length作为链表的一个成员变量,以优化算法,看具体需要吧。

template <class DataType>
int LinkList<DataType>::Length() {//计算长度
    Node<DataType> *p = first->next;  int count = 0;
    while (p != NULL) {
        p = p->next;
        count++;
    }
    return count;   //退出循环表明查找失败
}

 按位查找。查找第 i 位的数据域,遍历方法。

template <class DataType>
DataType LinkList<DataType>::Get(int i) {//按位查找
    Node<DataType> *p = first->next; int count = 1;
    while (p != NULL && count < i){
        p = p->next; count++;
    }
    if (p==NULL) {
        cerr << "查找位置非法" << endl;
        exit(1);
    } else return p->data;
    //注意count的初始化和返回值之间的关系
}

 按值查找。找到第一个出现 值 x 的位置,返回序号。

template <class DataType>
int LinkList<DataType>::Locate(DataType x) {//按值查找
    Node<DataType> *p = first->next;  int count = 1;
    while (p != NULL) {
        if (p->data == x) return count;     //查找成功,返回序号
        p = p->next;
        count++;
    }
    return 0;                                //退出循环表明查找失败
}

 按位插值。在第i 位插入新的 以 x 为数据的结点。遍历方法,在插值的过程中涉及到结点前驱和后继指针域的改变(链表的数据结构知识)。其中的异常处理也很关键哦!

template <class DataType>
void LinkList<DataType>::Insert(int i, DataType x) {//按位置插入值
    Node<DataType> *s, *p = first; int count = 0;     //工作指针p应指向头结点
    while (p != NULL && count < i - 1) {              //查找第 i–1 个结点
        p = p->next;
        count++;
    }
    if (p == NULL) {                                  //没有找到第 i–1 个结点
        cerr << "插入位置异常" << endl;
        return;
    } else {
        s = new Node<DataType>;  s->data = x;         //申请一个结点s
        s->next = p->next; p->next = s;               //结点s插入结点p之后
    }
}

PS:注意边界问题,插在表头、表尾的情况。(表头无前驱,表尾无后继)

 按位删除值。与按位插入值的基本逻辑是一样的。注意前驱后继的改变。

template <class DataType>
DataType LinkList<DataType>::Delete(int i) {//按位删除值
    Node<DataType> *q, *p = first; int count = 0; DataType x;
    while (p != NULL && count < i - 1) {
        p = p->next;
        count++;
    }
    if (p == NULL || p->next == NULL) {
        cerr << "删除位置异常" << endl;
        exit(1);
    } else {
        q = p->next; x = q->data;
        p->next = q->next;
        delete q; return x;
    }
}

 链表的遍历,按序输出链表的内容。PS:这里默认的跳过头节点,因为将头节点数据域定义为空,貌似是一种习惯来的。

从头节点的下一个开始。

template <class DataType>
void LinkList<DataType>::PrintList() {//遍历链表
    cout << "LinkList elements:" << endl;
    Node<DataType> *p = first->next; int i = 1;
    while (p != NULL) {
        cout << "No." << i << " " << p->data << endl;
        p = p->next; i++;
    }
}

至此,单链表的大部分基本声明与定义就结束了。还有很多特殊的链表,如“循环链表”、“约瑟夫环”、“双链表”,也是基于单链表的基本思路去实现的。

下附完整代码,望诸君共勉。

#ifndef LINKLIST_H
#define LINKLIST_H

using namespace std;

template <class DataType>          // 结点结构体
struct Node {
    DataType data; //数据域
    Node<DataType> *next;//指针域
};

template <class DataType>
class LinkList {
public:
    LinkList(); //构造函数
    LinkList(DataType a[], int n);//含参构造函数
    ~LinkList();//析构函数
    int Length();//计算链表产长度
    DataType Get(int i);//按位查找
    int Locate(DataType x);//按值查找
    void Insert(int i, DataType x);//插入值
    DataType Delete(int i);//删除元素
    void PrintList();//遍历链表
private:
    Node<DataType> *first;//头指针
};

template <class DataType>
LinkList<DataType>::LinkList() {
    cout << "链表的默认构造函数" << endl;
    first = NULL;//防止野指针
}

template <class DataType>
LinkList<DataType>::LinkList(DataType a[], int n) {
    //头插法,链表顺序和数组储存顺序相反
    cout << "链表的有参构造函数:头插法" << endl;
    first = new Node<DataType>; first->next = NULL;
    Node<DataType> *s;
    for (int i = 0; i < n; i++) {
        s = new Node<DataType>; s->data = a[i];
        s->next = first->next;
       first->next = s;
    }
    cout << "链表的有参构造函数:尾插法" << endl;
    //尾插法,链表顺序和数组储存顺序相同
    first = new Node<DataType>;     //生成头结点
    Node<DataType> *s, *r = first;  //尾指针初始化
    for (int i = 0; i < n; i++) {
        s = new Node<DataType>; s->data = a[i];
        r->next = s; r = s;
    }
    r->next = NULL;
}

template <class DataType>
LinkList<DataType>::~LinkList() {
    cout << "链表的析构函数" << endl;
    Node<DataType> *q;
    while (first != NULL) {
        q = first;
        first = first->next;
        delete q;
    }
}

template <class DataType>
int LinkList<DataType>::Length() {//计算长度
    Node<DataType> *p = first->next;  int count = 0;
    while (p != NULL) {
        p = p->next;
        count++;
    }
    return count;   //退出循环表明查找失败
}

template <class DataType>
DataType LinkList<DataType>::Get(int i) {//按位查找
    Node<DataType> *p = first->next; int count = 1;
    while (p != NULL && count < i){
        p = p->next; count++;
    }
    if (p==NULL) {
        cerr << "查找位置非法" << endl;
        exit(1);
    } else return p->data;
    //注意count的初始化和返回值之间的关系
}

template <class DataType>
int LinkList<DataType>::Locate(DataType x) {//按值查找
    Node<DataType> *p = first->next;  int count = 1;
    while (p != NULL) {
        if (p->data == x) return count;     //查找成功,返回序号
        p = p->next;
        count++;
    }
    return 0;                                //退出循环表明查找失败
}

template <class DataType>
void LinkList<DataType>::Insert(int i, DataType x) {//按位置插入值
    Node<DataType> *s, *p = first; int count = 0;     //工作指针p应指向头结点
    while (p != NULL && count < i - 1) {              //查找第 i–1 个结点
        p = p->next;
        count++;
    }
    if (p == NULL) {                                  //没有找到第 i–1 个结点
        cerr << "插入位置异常" << endl;
        return;
    } else {
        s = new Node<DataType>;  s->data = x;         //申请一个结点s
        s->next = p->next; p->next = s;               //结点s插入结点p之后
    }
}

template <class DataType>
DataType LinkList<DataType>::Delete(int i) {//按位删除值
    Node<DataType> *q, *p = first; int count = 0; DataType x;
    while (p != NULL && count < i - 1) {
        p = p->next;
        count++;
    }
    if (p == NULL || p->next == NULL) {
        cerr << "删除位置异常" << endl;
        exit(1);
    } else {
        q = p->next; x = q->data;
        p->next = q->next;
        delete q; return x;
    }
}

template <class DataType>
void LinkList<DataType>::PrintList() {//遍历链表
    cout << "LinkList elements:" << endl;
    Node<DataType> *p = first->next; int i = 1;
    while (p != NULL) {
        cout << "No." << i << " " << p->data << endl;
        p = p->next; i++;
    }
}

#endif // LINKLIST_H
  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值