单向链表的C++实现
链表作为一种基础的数据结构,在计算机科学中广泛应用。特别是在不支持连续存储的空间中,以及实现灵活的内存动态管理中,都能起到重要作用。链表在首尾端及附近的操作普遍优于一般数组array,而且支持在链表内部找到指定位置并插入和删除元素,没必要像数组一样移动一大串元素,而只是在内部对所改变的结点链接进行固定的几个改变即可;但在链表中搜寻元素要遍历链表需O(n)的时间,而不像数组中用O(1)那么快,而且链表在中间的操作会比首尾端慢上不少。
要组成一整条单向链表,就需要定义好链表上的各个结点,并且将单向链表上各结点串在一起:
- #ifndef SIMPLENODE_HPP
- #define SIMPLENODE_HPP
- template<typename T> //定义一个模板T 写单链表
- class SingleNode
- {
- public:
- T element;
- SingleNode* next;
- SingleNode(const T& theElement,SingleNode* nextone=NULL)
- :element(theElement),next(nextone) {}
- };
- #endif
这个单链表形成后,结构看起来就像这样的:
在组成一个单向链表后,还需要补充一些功能,以便后续实现:
1.定义这个单向链表类的构造函数及析构函数
2.清空链表内所有元素
3.给出元素位置再返回对应结点
4.返回链表内部元素个数
5.判断这个链表是否为空
6.返回链表首尾的元素值
7.查找元素是否在此链表内,如果在则返回所在位置
8.从首端到尾端输出链表上的各元素
9.对链表插入元素以及删除元素
与此同时,顺理成章地给出这些功能的接口们,他们组成了这个单链表类的参考框架:
- #ifndef SINGLELINKLIST_HPP
- #define SINGLELINKLIST_HPP
- #include<iostream>
- #include"simplenode.hpp"
- //这里默认位置pos的开始是从1开始,而不是从0开始
- template<class T>
- class SingleLinkList //单链表类的定义
- {
- private:
- SingleNode<T>* head; //链表头指针
- SingleNode<T>* tail; //链表尾指针
- int size; //元素个数
- SingleNode<T>* GetPointAt(int pos)
- {...} //给出元素位置再返回其对应结点
- public:
- SingleLinkList():head(),tail(),size(0) {}
- ~SingleLinkList() {Clear();}
- void Clear()
- {...}
- int Size() {...} //返回元素个数
- bool isempty() {...} //返回链表是否为空
- //-----------------------------------------------------
- //这里添加元素
- //------在尾部添加元素
- void AddBack(T val)
- {...}
- //------在指定位置插入元素
- bool AddAt(T val,int pos)
- {
- SingleNode<T>* pNode=NULL;
- if (pos<=0 || pos>size) //插入位置越界
- {...}
- if (pos==size) //在尾部插入元素
- AddBack(val);
- else if (pos==1) //在头部插入元素
- {...}
- else
- {...}
- size++;
- return true;
- }
- //-----------------------------------------------
- //这里删除元素
- bool RemoveBack() //删除尾部元素
- {
- return RemoveAt(size);
- }
- bool RemoveAt(int pos) //删除指定位置元素
- {
- SingleNode<T>* pNode=NULL;
- if (isempty())
- {...}
- if (pos<=0 || pos>size)
- {...}
- if (size==1) //只有1个元素时相当于清空链表
- {
- Clear();
- }
- if (pos==1) //并且size!=1, 删除头部元素时
- {...}
- else if (pos==size) //size!=1,删除尾部元素时
- {...}
- else
- {...}
- size--;
- return true;
- }
- //---------------------------------------
- T GetHeadVal() //返回首端元素
- {...}
- T GetTailVal() //返回尾端元素
- {...}
- int Find(T val) //查找元素
- {...}
- void ShowAllVal() //从头到尾输出链表上的元素
- {...}
- };
- #endif
现在,就具体看看这个单向链表怎么实现的:
1.构造函数和析构函数:
- SingleLinkList():head(),tail(),size(0) {}
- ~SingleLinkList() {Clear();}
创建了一个新的空链表,首端和尾端均指向NULL,内部元素个数为0;由于在加入元素时用了new,所以要手动地delete,Clear()中进行,析构函数直接调用Clear()即可。
2.清空链表内所有元素:
- void Clear()
- {
- //从链表头到链表尾的方式逐个删除
- const int nums=Size();
- if (!isempty())
- {
- for (int k=1;k<=nums;++k)
- {
- SingleNode<T>* temp=head->next;
- delete head;
- head=temp;
- size--;
- }
- }
- //如果链表本来就为空,就没必要再进for循环了
- }
3.给出元素位置再返回对应结点:
- SingleNode<T>* GetPointAt(int pos)
- {
- SingleNode<T>* pNode=NULL;
- if (pos<=0 || pos>size)
- std::cout<<"out of range."<<std::endl; //链表当前位置越界,异常
- else
- {
- pNode=head; //当前位置满足条件,则一开始在链表头
- for (int i=1;i<=pos-1;++i)
- pNode=pNode->next;
- }
- return pNode;
- }
要注意的是,遍历元素位置时避免pNode所获得的值越界,队首元素进不了for循环,改变不了pNode;队尾元素如果设置成i=1;i<=pos;++i,pNode会读取队尾的下一个值,那个值越界。最后就会返回一个未定义的元素。
4.返回首尾端元素、查找元素、输出所有元素、返回元素个数、判断链表是否为空:
- T GetHeadVal()
- {
- if (isempty())
- {
- std::cout<<"the link list is empty"<<std::endl;
- return NULL;
- }
- return head->element;
- }
- T GetTailVal()
- {
- if (isempty())
- {
- std::cout<<"the link list is empty"<<std::endl;
- return NULL;
- }
- return tail->element;
- }
- int Find(T val) //查找元素
- {
- int pos=1; //从起始位置1号位开始
- SingleNode<T>* findNode=head;
- while (findNode!=NULL)
- {
- if (findNode->element==val)
- return pos;
- findNode=findNode->next;
- pos++;
- }
- std::cout<<"we can't find it,return -1"<<std::endl;
- return -1;
- }
- void ShowAllVal() //从头到尾输出链表上的元素
- {
- SingleNode<T>* findNode=head;
- while (findNode!=NULL)
- {
- std::cout<<findNode->element<<" ";
- findNode=findNode->next;
- }
- std::cout<<std::endl;
- }
- int Size() {return size;} //返回元素个数
- bool isempty() {return size==0?true:false; } //返回链表是否为空
5.链表中加入元素:
这里分四种情况讨论:
①插入元素位置越界
②在尾部插入元素
③在头部插入元素
④在其他位置插入元素
若插入元素成功,记得元素个数+1。这里用图示例非空链表首部插入元素,其他情况读者们可以自己试着画图:
代码如下:
- //------在尾部添加元素
- void AddBack(T val)
- {
- SingleNode<T>* pNode=new SingleNode<T>(val);
- if (isempty()) //链表为空时
- {
- head=pNode; tail=pNode;
- }
- else
- {
- tail->next=pNode;
- tail=pNode;
- }
- size++;
- }
- //------在指定位置插入元素
- bool AddAt(T val,int pos)
- {
- SingleNode<T>* pNode=NULL;
- if (pos<=0 || pos>size)
- {
- std::cout<<"out of range."<<std::endl;
- return false;
- }
- if (pos==size) //在尾部插入元素
- AddBack(val);
- else if (pos==1) //在头部插入元素
- {
- pNode=new SingleNode<T>(val);
- pNode->next=head;
- head=pNode;
- }
- else
- {
- //返回插入位置前面一个的位置指针
- SingleNode<T>* pNode=GetPointAt(pos-1);
- SingleNode<T>* newNode=new SingleNode<T>(val);
- newNode->next=pNode->next;
- pNode->next=newNode;
- }
- size++;
- return true;
- }
6.链表中删除元素:
这里分五种情况讨论:
①链表为空
②删除元素位置越界
③在尾部删除元素
④在头部删除元素
⑤在其他位置删除元素
若删除元素成功,记得元素个数-1。这里用图示例非空链表尾部删除元素,其他情况读者们可以自己试着画图:
代码如下:
- bool RemoveBack() //删除尾部元素
- {
- return RemoveAt(size);
- }
- bool RemoveAt(int pos) //删除指定位置元素
- {
- SingleNode<T>* pNode=NULL;
- if (isempty())
- {
- std::cout<<"the link list is empty"<<std::endl;
- return false;
- }
- if (pos<=0 || pos>size)
- {
- std::cout<<"out of range."<<std::endl;
- return false;
- }
- if (size==1) //只有1个元素时相当于清空链表
- {
- Clear();
- }
- if (pos==1) //并且size!=1, 删除头部元素时
- {
- pNode=head;
- head=head->next;
- delete pNode;
- }
- else if (pos==size)
- {
- SingleNode<T>* pPreNode=GetPointAt(pos-1);
- std::cout<<"之前一个元素为"<<pPreNode->element<<std::endl;
- pNode=pPreNode->next;
- pPreNode->next=pNode->next;
- delete pNode;
- tail=pPreNode;
- }
- else
- {
- SingleNode<T>* pPreNode=GetPointAt(pos-1);
- std::cout<<"之前一个元素为"<<pPreNode->element<<std::endl;
- pNode=pPreNode->next;
- pPreNode->next=pNode->next;
- delete pNode;
- }
- size--;
- return true;
- }
最后,将上述实现放入之前提到的框架内,就完成了最后的单向链表C++实现。
简易的单向链表,就这么完成了。还有更加完善的单向链表,那就是STL中的<slist>了,坐等后续更新吧。
参考文献及链接:
4.http://blog.youkuaiyun.com/weiwenhp/article/details/8634469