C++实现线性表的链式存储结构:
为了解决顺序存储不足
:用线性表另外一种结构-链式存储
。在顺序存储结构(数组描述)中,元素的地址是由数学公式决定的,而在链式储存结构中,元素的地址是随机分布的,每个元素都有一个明确的指针指向线性表的下一个元素的位置(即地址)。
线性表的链式存储结构的特点
是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。在顺序结构中,每个数据元素只需要存数据元素信息就行了,而在链式结构中,除了存储数据元素信息外,还要存储它的后继元素的存储地址。所以一般结点包括两个信息:数据和指针。链表就是n个节点组成的,如果每个结点只包含一个指针,那么就是单链表
。
有头有尾
:我们把链表中第一个结点的存储位置叫作头指针,那么整个链表的存取就必须是从头指针开始进行的。而线性链表的最后一个结点指针为空(NULL)。从图一中可以看到,结点都是由两部分组成,数据域和指针域。
有时,为了更方便对链表进行操作,会在单链表的第一个结点前加一个头结点。头结点的数据域可以不存储任何信息,也可以存储如线性表长度等附加信息,头结点的指针域存储指向第一个结点的指针。
头指针和头结点的异同
指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。
头指针具有标识作用,所以常用头指针冠以链表的名字。
无论链表是否为空,头指针均不为空。头指针是链表的必要元素。
(这句话真的歧义,若没有头结点,头指针head指向第一个节点,当空表时,head=NULL。应该是必须要有头指针。)`
头结点
(1)头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(也可存放链表的长度)
(2)有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其它结点的操作就统一了。
(3)头结点不一定是链表必须的要素。
链式存储结构的线性表进行的基本操作。主要包括:
- 插入:操作方式为在指定元素前插入、在指定元素之后插入、在指定位置完成插入
- 删除:操作方式可分为删除指定元素、删除指定位置的元素,删除第一个元素,删除最后一个元素
- 显示数据
- 查找:查询指定的元素(可根据某个数据成员完成查询操作)
- 定位操作:定位指定元素的序号
- 更新:修改指定元素的数据
- 数据文件的读写操作
- 计算链表的长度
定义一个结点类
/*Define a friend class to facilitate direct manipulation of data*/
template
class LinkList;
template
class LinkNode
{
friend class LinkList;
private:
T _data;
LinkNode *_next;
public:
LinkNode(T x )
{
_data = x;
_next = NULL;
}
};
链表的主要函数构成
template
class LinkList
{
private:
LinkNode * _head;
public:
LinkList()
{_head = NULL;}
LinkNode* _CreateNode(const T& x);//Create a new node
void clear(LinkNode* &cur);//Delete a new node
void PushBack(const T& x);//tail insertion to create a linked list
void PushFront(const T& x);//Head insertion to create a linked list
void PopBack(); //Remove an element from the tail
void PopFront();//Remove an element from the head
int Length();//Find the length of the linear table
LinkNode* Find(T x);//Find a number
void Insert_right(int pos, const T& x);//Insert after the nth
void Insert_cur(int pos, const T& x);//Insert at the specified location
void Insert_left(int pos, const T& x);//Insert in front of the nth
void Delete_pos(int pos);//Delete the nth element
void Delete_val(const T& x);//Delete specified element
void reset(const T &x,const T &y);//Update an element
int located(const T &x);//Locate the serial number of the specified element
void Print();// Print linear table
bool writeToFile();//Write file
T* readFromFile();//Read in data file
int readlen();
};
各个函数的实现
Print()函数
template
void LinkList::Print()
{
LinkNode *tmp = _head;
while (tmp != NULL)
{
cout << tmp->_data << "-->";
tmp = tmp->_next;
}
cout << "NULL" << endl;
}
创建一个新的结点,并为其分配空间
template
LinkNode* LinkList:: _CreateNode(const T& x)
{
LinkNode* tmp = new LinkNode(x);
return tmp;
}
清除某一个节点,释放空间
template
void LinkList::clear(LinkNode *&cur)
{
cur->_next = NULL;
delete cur;
cur = NULL;
}
获得链表的长度
template
int LinkList ::Length()
{
int len = readlen();/*调用一个读文件的函数,来判别链表状态,Call a function that reads the file to determine the state of the linked list*/
if(len>0){
return len;
}
else {
len=0;
if (_head == NULL)
{
return 0;
}
else
{
LinkNode * begin = _head;
while (begin != NULL)
{
begin = begin->_next;
len++;
}
}
}
return len;
}
前插法建立链表
从一个空表开始,重复读入数据,执行以下两步
(1)生成新的结点,将读入数据存放在新节点的的_data域中
(2)将该节点插入到链表的前端,直到读入到结束符为止。
template
void LinkList :: PushFront(const T& x)
{
if (_head == NULL)
{
_head = _CreateNode(x);
}
else
{
LinkNode * prev = _CreateNode(x);
prev->_next = _head;
_head = prev;
}
}
用后插法建立链表
需要设置一个尾部指针end,总是指向新链表的最后一个节点,新节点链接到它所指链尾节点的后面。end最初要置于附加头节点位置
template
void LinkList ::PushBack(const T& x)
{
if (_head == NULL)
{
_head = _CreateNode(x);
}
else
{
LinkNode * end = _head;
while (end->_next != NULL)
{
end = end->_next;
}
end->_next = _CreateNode(x);
}
}
从尾部删除一个数据
考虑只有一个节点情况,多个结点的情况。
多个结点,首先找到尾部元素,然后调用clear()函数,清理掉尾部第一个元素
template
void LinkList :: PopBack()
{
if (_head == NULL)
{
cout << "The List is empty!!!" << endl;
return;
}
else if (_head->_next == NULL)
{
clear(_head);
}
else
{
LinkNode * temp = _head;
LinkNode * end = _head;
while (end->_next != NULL)
{
temp = end;
end = end->_next;
}
temp->_next = NULL;
clear(end);
}
}
从头部删除一个元素
实现方法和从尾部删除一个元素基本相似,不多加以解释
template
void LinkList ::PopFront()
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
return;
}
else if (_head ->_next == NULL)
{
clear(_head);
}
else
{
LinkNode * tmp = _head;
_head = _head->_next;
clear(tmp);
tmp = NULL;
}
}
查找某一个元素
遍历整个链表,并将其数据_data与x进行比对,如果是其他类型就需要重载运算符==
template
LinkNode* LinkList ::Find (T x)
{
if (_head == NULL)
{
cout << "List is empty,not found!!!" << endl;
return NULL;
}
else if(_head->_data==x)
{
return _head;
}
else
{
LinkNode * n = _head ;
while (n->_next != NULL && n->_data != x )
{
n = n->_next;
if (n->_data == x)
{
return n;
}
}
}
return NULL;
}
在第pos个元素后插入一个新元素
创建一个新的结点,通过移动begin指针,pos控制指针最终位置,将新元素插入到之后
template
void LinkList ::Insert_right(int pos,const T& x)
{
int len = Length();
if (pos <= len)
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
return;
}
else
{
LinkNode * begin = _head;
LinkNode * tmp = _CreateNode(x);
while (--pos)
{
if (begin->_next != NULL)
{
begin = begin->_next;
}
}
tmp->_next = begin->_next;
begin->_next = tmp;
}
}
else
{
cout << "Input Error!!!" << endl;
}
}
在第pos个元素插入一个新元素
因为写了一个在后面插入的函数,为了偷懒,就直接调用了Insert_right()这个函数
template
void LinkList ::Insert_cur(int pos,const T& x)
{
int len = Length();
if (pos <= len)
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
return;
}
else
{
Insert_right(pos-1, x);
}
}
else
{
cout << "Input Error!!!" << endl;
}
}
在第pos个元素前插入一个新元素
创建一个新的结点,通过移动begin指针,pos控制指针最终位置,将新元素插入到之前,与插入之后实现方法基本类似,只是需要注意pos的控制
template
void LinkList ::Insert_left(int pos,const T& x)
{
int len = Length();
int temp=pos-1;
if (pos <= len)
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
return;
}
else
{
LinkNode * begin = _head;
LinkNode * tmp = _CreateNode(x);
while (--temp)
{
if (begin->_next != NULL)
{
begin = begin->_next;
}
}
tmp->_next = begin->_next;
begin->_next = tmp;
}
}
else
{
cout << "Input Error!!!" << endl;
}
}
删除第pos个元素
依旧利用pos控制指针位置,然后删除就ok
template
void LinkList ::Delete_pos(int pos)
{
int len = Length();
if (pos <= len)
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
}
else if (_head->_next == NULL)
{
clear(_head);
}
else
{
LinkNode * begin = _head->_next;
LinkNode * temp = _head;
pos = pos - 1;
while (--pos)
{
if (begin->_next != NULL)
{
begin = begin->_next;
temp = temp->_next;
}
}
temp->_next = begin->_next;
begin->_next = temp;
}
}
else
{
cout << "Input Error!!!" << endl;
}
}
查找某一个元素位置并返回其位置
用一个temp进行计数,遍历整个链表,一一进行比对其——data数据(若是其它类型就需要重载运算符“=”,后面的函数提到比对的都需要实现,才能进行比对)比对成功就返回,其它类的实现依然需要重载
template
int LinkList ::located(const T &x)
{
if (_head == NULL)
{
cout << "List is empty,not found!!!" << endl;
return -1;
}
else if(_head->_data==x)
{
return 1;
}
else
{
LinkNode * n = _head ;
int temp=0;
while (n->_next != NULL && n->_data != x )
{
n = n->_next;
temp++;
if (n->_data == x)
{
return temp+1;
}
}
}
return -1;
}
删除指定的元素遍历链表
一一比对,找到就调用chear()函数删除并清理空间
template
void LinkList ::Delete_val(const T &x)
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
return;
}
else if (_head->_next == NULL && _head->_data == x)
{
clear(_head);
return;
}
else
{
if(_head->_data==x){
while(1){
if(_head->_data!=x)break;
PopFront();
}
}
else
{
while(1)
{
LinkNode * n = Find(x);
if(n==NULL)break;
else{
LinkNode * begin = _head;
while (begin->_next != n && begin->_next != NULL)
{
begin = begin->_next;
}
begin->_next = n->_next;
clear(n);
}
}
return ;
}
}
return;
}
更新指定元素的值
遍历链表,一一比对(前面提到了重载的),找到要更新的元素后就将新的值赋值给它
template
inline void LinkList::reset(const T &x,const T &y)
{
if (_head == NULL)
{
cout << "List is empty!!!" << endl;
return ;
}
else
{
while(1)
{
LinkNode * n = Find(x);
if(n==NULL)break;
else{
n->_data=y;
}
}
}
}
链表的数据写入文件
能实现所有数据的储存,但是如果是其他类,写入没有问题,如果想从同一个文件中读入之前写的数据,遇到一些困难没有实现,但是基本数据类型是ok的,测试函数就表现出来了的(其它类的读入需要重载输入流,输出流)
template
inline bool LinkList::writeToFile()
{
ofstream writefile("data.txt");
int len;
len=Length();
writefile< * begin=_head;
while(begin!=NULL)
{
writefile<_data<_next;
}
return true;
}
从文件读入先前写入的数据,上面有提到只能满足基本数据类型
template
inline T* LinkList::readFromFile()
{
ifstream readfile("data.txt");
int length;
readfile>>length;
T *temp=new T[length];
for(int i=0;i>temp[i];
}
return temp;
}
为了更好的操作,测试相关函数的功能,写了这样一个函数来从文件得到链表长度的函数
template
inline int LinkList::readlen()
{
ifstream readfile("data.txt");
int length;
readfile>>length;
return length;
}
写到这基本上函数的功能都实现了,这个链表结构肯定有很多不完善的地方,第一次写博客,还望大家见谅。上面提到了其它类的重载问题,我就把写的一个point类分享给大家。
class Point
{
private:
double x;
double y;
public:
Point(double x=0.0,double y=0.0)
{this->x=x,this->y=y;}
void setdata(double a,double b)
{this->x=a;this->y=b;}
Point operator=(Point a);//重载运算符“=”
int operator==(Point a);//重载运算符“==”
int operator!=(Point a);
double getx()
{return x;}
double gety()
{return y;}
//重载输入流
friend istream &operator>>(istream &is,Point &p)
{
is>>p.x;
is>>p.y;
return is;
}//重载输出流
friend ostream &operator<<(ostream &os,Point p)
{
os<<"("<x=a.x;
this->y=a.y;
return *this;
}
inline int Point::operator==(Point a)
{
if(this->x==a.getx()&&this->y==a.gety())
{
return 1;
}
else {
return 0;
}
}
inline int Point::operator!=(Point a)
{
if(this->x!=a.getx()&&this->y!=a.gety())
{
return 1;
}
else if(this->x!=a.getx()||this->y!=a.gety()){
return 1;
}
else {
return 0;
}
}
一个输入之后的展示
以上是C++顺序表使用过程中最常用的主要操作,相关完整代码已经push到GitHub,需要的小伙伴自行clone,如果觉得还不错的话,欢迎Star,这里是传送门C++链式顺序表,除此之外,想要了解更多的C,C++,Java,Python等相关知识的童鞋,欢迎来我的博客相逢的博客,我们一起讨论!