C/C++编程笔记:如何使用C++实现单链表?单链表第一部分

如何弥补顺序表的不足之处?

第一次学习线性表一定会马上接触到一种叫做顺序表(顺序存储结构),经过上一篇的分析顺序表的优缺点是很显然的,它虽然能够很快的访问读取元素,但是在解决如插入和删除等操作的时候,却需要移动大量的元素,效率较低,那么是否有一种方法可以改善或者解决这个问题呢?

C/C++编程笔记:如何使用C++实现单链表?单链表第一部分_第1张图片

首先我们需要考虑,为什么顺序表中的插入删除操作会涉及到元素的移动呢?

好家伙,问题就是围绕着顺序表的最大的特点出现的——顺序存储,相邻放置元素,也就是说每个元素都是根据编号一个一个挨着的,这就导致了 插入或删除后,为了仍然呈顺序线性存储,被操作元素后面的元素的位置均需要发生一定的变化,你应该能想象得到,在拥挤的队伍中突然从中插入一个学生的场景,后面浩浩荡荡的人群,口吐芬芳的向后挪了一个空位,如果人群过大,重新排好队也需要一定的时间

好嘛,人与人之间别这么挤在一起,每个人与人之间都流出一点空隙来,留一定的位置出来,好了,这好像是个办法,但是负责一个一个与学生交流填表的老师可就不干了,这意味着我(找人)遍历的时候,需要多跑好多路,浪费好多时间,先不说这个,体院馆又不行了,你们这么个摆法,我这小馆可放不下,这也就意味着空间复杂度增加了很多。

我们刚才所围绕的都是在 "排队" 的基本前提下的,但我们能想到的方法并不是很理想,那么我们索性就不排队了,是不是能有更好的解决方式呢?

一个有效的方法:

让同学们(元素)自己找位置随便站,不过你要知道相对于自己下一位同学的位置,这样既解决了空间上的问题,又能通过这种两两联系的方式访问(遍历)到整个队伍(数组),最重要的是,插入和离开同学,由于同学(元素)之间不存在了那种排队,相邻的特点,所以也不会说影响到过多的同学(元素)只需要和你插入位置的前后两位同学沟通好就行了,反正别人也不知道你们之间发生了什么事

好了思路是有了,我们来看一种最常见的链表——单链表

单链表的基本结构

这种链表为什么被称作单链表呢?这是因为它只含有一个地址域,这是什么意思呢?

我们在链表中摈弃了顺序表中那种一板一眼的排队方式,但是我们必须让两个应该相邻的元素之间有一定的相互关系,所以我们选择让每一个元素可以联系对应的下一个元素

而这个时候我们就需要给每个元素安排一个额外的位置,来存储它的后继元素的存储地址,这个存储元素信息的域叫做指针域或地址域,指针域中储存的信息也叫作指针或者链

我们用一张图 看一下他的结构

结构中名词解释

头指针:一个指向第一个节点地址的指针变量

        头指针具有标识单链表的作用,所以经常用头指针代表单链表的名字

头结点:在单链表的第一个结点之前附设一个结点,它没有直接前驱,称之为头结点

        可不存信息,也可以作为监视哨,或用于存放线性表的长度等附加信息

        指针域中存放首元结点的地址

首元结点:存储第一个元素的节点

为什么要附设一个头结点

我们来解释一下:

(1)链表如果为空的情况下,如果单链表没有头结点,那么头指针就会指向NULL,如果加上头结点,无论单链表是否为空,头指针都会指向头结点,这样使得空链表与非空链表处理一致

(2)使首元结点前插入或删除元素的时候,与后面操作相同,不需要产生额外的判断分支,使得算法更加简单

C/C++编程笔记:如何使用C++实现单链表?单链表第一部分_第2张图片

(以插入为例讲解)在带头结点的情况下,在首元结点前插入或者删除元素仍与在其他位置的操作相同,只需要将前一个元素(在这里是头结点)的指针域指向插入元素,同时将插入元素的指针域指向原来的第二的元素

C/C++编程笔记:如何使用C++实现单链表?单链表第一部分_第3张图片

而无头结点的情况由于,首元结点前没有元素,只能通过修改head的前后关系,所以导致了 与在别的位置插入或删除元素的操作不同,在实现这两个功能的时候就需要额外的写一个判断语句来判断插入的位置是不是首元结点之前的位置,增加了分支,代码不够简洁

总结:头结点的存在使得空链表与非空链表处理一致,也方便对链表首元结点前结点的插入或删除操作

单链表的类型定义

###线性表的抽象数据类型定义

我们在给出单链表的定义之前我们还是需要先引入我们线性表的抽象数据类型定义

#ifndef _SEQLIST_H_
#define _SEQLIST_H_
#include "List.h"
#include
using namespace std;

template<class elemType>
//elemType为单链表存储元素类型 
class linkList:public List {
private:
    //节点类型定义 
    struct Node {
        //节点的数据域 
        elemType data;
        //节点的指针域 
        Node *next;
        //两个构造函数 
        Node(const elemType value, Node *p = NULL) {
            data = value;
            next = p;
        } 
        Node(Node *p = NULL) {
            next = p;
        } 
    };
    
    //单链表的头指针 
    Node *head;
    //单链表的尾指针 
    Node *tail;
    //单链表的当前长度 
    int curLength;
    //返回指向位序为i的节点的指针 
    Node *getPostion(int i)const; 
public:
    linkList();
    ~linkList();
    //清空单链表,使其成为空表 
    void clear();
    //带头结点的单链表,判空 
    bool empty()const {return head -> next == NULL;} 
    //返回单链表的当前实际长度
    int size()const {return curLength;}
    //在位序i处插入值为value的节点表长增1 
    void insert(int i, const elemType &value); 
    //删除位序为i处的节点,表长减1
    int search(const elemType&value)const;
    //查找值为value的节点的前驱的位序
    int prior(const elemType&value)const;
    //访问位序为i的节点的值,0定位到首元结点
    elemType visit(int i)const;
    //遍历单链表
    void traverse()const;
    //头插法创建单链表
    void headCreate();
    //尾插法创建单链表
    void tailCreate();
    //逆置单链表 
    void inverse();
};

 

单链表的类型定义

#ifndef _SEQLIST_H_
#define _SEQLIST_H_
#include "List.h"
#include
using namespace std;

template<class elemType>
//elemType为单链表存储元素类型 
class linkList:public List {
private:
    //节点类型定义 
    struct Node {
        //节点的数据域 
        elemType data;
        //节点的指针域 
        Node *next;
        //两个构造函数 
        Node(const elemType value, Node *p = NULL) {
            data = value;
            next = p;
        } 
        Node(Node *p = NULL) {
            next = p;
        } 
    };
    
    //单链表的头指针 
    Node *head;
    //单链表的尾指针 
    Node *tail;
    //单链表的当前长度 
    int curLength;
    //返回指向位序为i的节点的指针 
    Node *getPostion(int i)const; 
public:
    linkList();
    ~linkList();
    //清空单链表,使其成为空表 
    void clear();
    //带头结点的单链表,判空 
    bool empty()const {return head -> next == NULL;} 
    //返回单链表的当前实际长度
    int size()const {return curLength;}
    //在位序i处插入值为value的节点表长增1 
    void insert(int i, const elemType &value); 
    //删除位序为i处的节点,表长减1
    int search(const elemType&value)const;
    //查找值为value的节点的前驱的位序
    int prior(const elemType&value)const;
    //访问位序为i的节点的值,0定位到首元结点
    elemType visit(int i)const;
    //遍历单链表
    void traverse()const;
    //头插法创建单链表
    void headCreate();
    //尾插法创建单链表
    void tailCreate();
    //逆置单链表 
    void inverse();
};

 

单链表上的基本运算实现

(一) 单链表的初始化-构造函数

单链表的初始化就是创建一个带头节点空链表,我们不需要设置其指针域,为空即可

template<class elemType>
linkList::linkList() {
    head = tail = new Node();
    curLength=0;
}

 

注意:new 操作符代表申请堆内存空间,上述代码中应该判断是否申请成功,为简单,默认为申请成功,实际上如果系统没有足够的内存可供使用,那么在申请内存的时候会报出一个 bad_alloc exception 异常

(二) 析构函数

当单链表对象脱离其作用域时,系统自动执行析构函数来释放单链表空间,其实也就是清空单链表内容,同时释放头结点

template<class elemType>
linkList::~linkList() {
    clear();
    delete head;
}

 

(三) 清空单链表

清空单链表的主要思想就是从头结点开始逐步将后面节点释放掉,但是我们又不想轻易的修改头指针head的指向,所以我们引入一个工作指针,从头结点一直移动到表尾,逐步释放节点

template<class elemType>
void linkList::clear() {
    Node *p, *tmp;
    p - head -> next;
    while(p != NULL) {
        tmp = p;
        p = p -> next();
        delete tmp; 
    }
    head -> next = NULL;
    tail = head;
    curLength = 0;    
}

 

下节知识分享我们将会讲到,如何求单链表的表长以及遍历单链表、插入,删除节点等方法的实现,记得关注哦~

自学C/C++编程难度很大,不妨和一些志同道合的小伙伴一起学习成长!

C语言C++编程学习交流圈子,【点击进入】QQ群:757874045,微信公众号:C语言编程学习基地

有一些源码和资料分享,欢迎转行也学习编程的伙伴,和大家一起交流成长会比自己琢磨更快哦!C/C++编程笔记:如何使用C++实现单链表?单链表第一部分_第4张图片C/C++编程笔记:如何使用C++实现单链表?单链表第一部分_第5张图片

你可能感兴趣的:(C/C++编程笔记:如何使用C++实现单链表?单链表第一部分)