第一次学习线性表必定会立刻接触到一种叫作顺序表(顺序存储结构),通过上一篇的分析顺序表的优缺点是很显然的,它虽然可以很快的访问读取元素,可是在解决如插入和删除等操做的时候,却须要移动大量的元素,效率较低,那么是否有一种方法能够改善或者解决这个问题呢?ios
首先咱们须要考虑,为何顺序表中的插入删除操做会涉及到元素的移动呢?算法
好家伙,问题就是围绕着顺序表的最大的特色出现的——顺序存储,相邻放置元素,也就是说每一个元素都是根据编号一个一个挨着的,这就致使了 插入或删除后,为了仍然呈顺序线性存储,被操做元素后面的元素的位置均须要发生必定的变化,你应该能想象获得,在拥挤的队伍中忽然从中插入一个学生的场景,后面浩浩荡荡的人群,口吐芬芳的向后挪了一个空位,若是人群过大,从新排好队也须要必定的时间编程
好嘛,人与人之间别这么挤在一块儿,每一个人与人之间都流出一点空隙来,留必定的位置出来,好了,这好像是个办法,可是负责一个一个与学生交流填表的老师可就不干了,这意味着我(找人)遍历的时候,须要多跑好多路,浪费好多时间,先不说这个,体院馆又不行了,大家这么个摆法,我这小馆可放不下,这也就意味着空间复杂度增长了不少。数组
咱们刚才所围绕的都是在 "排队" 的基本前提下的,但咱们能想到的方法并非很理想,那么咱们索性就不排队了,是否是能有更好的解决方式呢?微信
一个有效的方法:函数
让同窗们(元素)本身找位置随便站,不过你要知道相对于本身下一位同窗的位置,这样既解决了空间上的问题,又能经过这种两两联系的方式访问(遍历)到整个队伍(数组),最重要的是,插入和离开同窗,因为同窗(元素)之间不存在了那种排队,相邻的特色,因此也不会说影响到过多的同窗(元素)只须要和你插入位置的先后两位同窗沟通好就好了,反正别人也不知道大家之间发生了什么事学习
好了思路是有了,咱们来看一种最多见的链表——单链表spa
这种链表为何被称做单链表呢?这是由于它只含有一个地址域,这是什么意思呢?指针
咱们在链表中摈弃了顺序表中那种一板一眼的排队方式,可是咱们必须让两个应该相邻的元素之间有必定的相互关系,因此咱们选择让每个元素能够联系对应的下一个元素code
而这个时候咱们就须要给每一个元素安排一个额外的位置,来存储它的后继元素的存储地址,这个存储元素信息的域叫作指针域或地址域,指针域中储存的信息也叫做指针或者链,
咱们用一张图 看一下他的结构
结构中名词解释
头指针:一个指向第一个节点地址的指针变量
头指针具备标识单链表的做用,因此常常用头指针表明单链表的名字
头结点:在单链表的第一个结点以前附设一个结点,它没有直接前驱,称之为头结点
可不存信息,也能够做为监视哨,或用于存放线性表的长度等附加信息
指针域中存放首元结点的地址
首元结点:存储第一个元素的节点
咱们来解释一下:
(1)链表若是为空的状况下,若是单链表没有头结点,那么头指针就会指向NULL,若是加上头结点,不管单链表是否为空,头指针都会指向头结点,这样使得空链表与非空链表处理一致
(2)使首元结点前插入或删除元素的时候,与后面操做相同,不须要产生额外的判断分支,使得算法更加简单
(以插入为例讲解)在带头结点的状况下,在首元结点前插入或者删除元素仍与在其余位置的操做相同,只须要将前一个元素(在这里是头结点)的指针域指向插入元素,同时将插入元素的指针域指向原来的第二的元素
而无头结点的状况因为,首元结点前没有元素,只能经过修改head的先后关系,因此致使了 与在别的位置插入或删除元素的操做不一样,在实现这两个功能的时候就须要额外的写一个判断语句来判断插入的位置是否是首元结点以前的位置,增长了分支,代码不够简洁
总结:头结点的存在使得空链表与非空链表处理一致,也方便对链表首元结点前结点的插入或删除操做
###线性表的抽象数据类型定义
咱们在给出单链表的定义以前咱们仍是须要先引入咱们线性表的抽象数据类型定义
#ifndef _SEQLIST_H_ #define _SEQLIST_H_ #include "List.h" #include<iostream> using namespace std; template<class elemType> //elemType为单链表存储元素类型 class linkList:public List<elemType> { 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<iostream> using namespace std; template<class elemType> //elemType为单链表存储元素类型 class linkList:public List<elemType> { 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<elemType>::linkList() { head = tail = new Node(); curLength=0; }
注意:new 操做符表明申请堆内存空间,上述代码中应该判断是否申请成功,为简单,默认为申请成功,实际上若是系统没有足够的内存可供使用,那么在申请内存的时候会报出一个 bad_alloc exception 异常
(二) 析构函数
当单链表对象脱离其做用域时,系统自动执行析构函数来释放单链表空间,其实也就是清空单链表内容,同时释放头结点
template<class elemType> linkList<elemType>::~linkList() { clear(); delete head; }
(三) 清空单链表
清空单链表的主要思想就是从头结点开始逐步将后面节点释放掉,可是咱们又不想轻易的修改头指针head的指向,因此咱们引入一个工做指针,从头结点一直移动到表尾,逐步释放节点
template<class elemType> void linkList<elemType>::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语言编程学习基地
有一些源码和资料分享,欢迎转行也学习编程的伙伴,和你们一块儿交流成长会比本身琢磨更快哦!