您的位置:首页 > 编程语言 > C语言/C++

单向链表的C++实现

2013-10-17 15:13 495 查看
单向链表的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>了,坐等后续更新吧。

参考文献及链接:

1.《算法导论》2nd Thomas H.Cormen , Charles E.Leiserson , Ronald L.Rivest , Cliford Stein 著,潘金贵、顾铁成、李成法、叶懋译
2.《数据结构与算法分析 C++描述》 第3版 Mark Allen Weiss著,张怀勇等译
3.《C++标准模板库 -自修教程及参考手册-》 Nicolai M.Josuttis著,侯捷/孟岩译
4./article/8115603.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: