数据结构是初学编程时候的一个天坑,去年大一的时候学的那叫一个惨烈,虽然C++提供标准的list类库,但是自己动手实现这些常用的数据结构,不仅能对数据结构的认识更深,也对C++中深浅复制,重载,复用等内容更好的理解。
线性表的链式存储结构是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的也可以是不连续的),因此为了表示每个数据元素与其直接后继的逻辑关系,除了存储本身的信息之外,还需要一个存储一个直接后继的位置信息。这两部分组成的存储映射,称为结点(node).
一个结点包含数据域和指针域两个部分,一个链表由若干个结点依次链接构成,其中第一个结点为头结点,最后一个节点的直接后继为NULL。
那么实现一个单链表的第一步就是选择一个合适的结构来实现node。
这里有很多种方法,比如将node声明为struct可直接被LinkList类使用,或者将node声明为类,LinkList类public继承node类或是用嵌套类定义等方法。这里好像没什么优劣之分吧,根据个人喜好选择吧。
using namespace std;
template <class Temp>
struct LinkNode
{
Temp data;
LinkNode<Temp> *next; //struct构造函数
LinkNode(const Temp &item, LinkNode<Temp> *ptr = NULL)
//函数参数表中的形参允许有默认值,但是带默认值的参数需要放后面
{
next = ptr;
data = item;
}
};
看到这里,可能有朋友会问了,这玩意是啥?结构还是类,在上述LinkNode结构中,居然出现了模板类的定义方法和构造函数,其实结构可以看做为一种默认为public的类。这里的构造函数提供的默认值为NULL可以省去检测链表尾结点是否指向NULL的麻烦。
好了,现在结点有了,那我们来看看怎么定义LinkList这个模板类吧,先给出来,在慢慢解释。
template <class Temp>
struct LinkNode
{
Temp data;
LinkNode<Temp> *next;
LinkNode(LinkNode<Temp> *ptr = NULL){ next = ptr; } //struct构造函数
LinkNode(const Temp &item, LinkNode<Temp> *ptr = NULL) //函数参数表中的形参允许有默认值,但是带默认值的参数需要放后面
{
next = ptr;
data = item;
}
};
template <class Temp>
class LinkList
{
public:
LinkList(){
Head = new LinkNode<Temp>;
if (NULL == Head)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
};
//无参构造函数
LinkList(const Temp &item);
//含参构造函数
LinkList(const LinkList ©);
//拷贝构造函数
LinkList<Temp>& operator= (LinkList<Temp> &List);
~LinkList();
//析构函数
void CreateLink(int nLinkList);
//创建长度为nLinkList的单链表
void DestoryList();
//摧毁线性表
void ClearList();
//清空单链表
bool ListIsLoop() const;
//链表是否有环
bool ListEnpty() const;
//判断链表是否为空,为空返回true,否则返回false
int ListLength() const;
//返回链表中数据元素个数
bool GetElem(int pos, Temp &item);
//用item获取第pos个数据元素的值,false表示失败
void SetHead(LinkNode<Temp> *p);
//设置链表头结点
LinkNode<Temp>* Find(Temp &item);
// 返回第一个找到的满足该数据元素的结点对应的指针
LinkNode<Temp>* Locate(int pos);
//返回第pos个数据元素对于的指针
bool LinkInsert(Temp item, int pos);
//将数据元素item插入到第i个结点的位置,成功返回true,否则返回false
bool LinkDelete(Temp &item,int pos);
//将第pos个结点删除,并用item返回该数据对象
void Print() const;
//打印链表
void Sort();
//链表排序
void Reverse();
void Reverse(int nStart, int nEnd);
//链表逆置
void LinkSwap(int pos1, int pos2);
void LinkSwap(LinkNode<Temp> * p1, LinkNode<Temp> * p2);
//交换两个结点内容
bool LinkIsPalindrome();
//单链表是不回文
private:
LinkNode<Temp> *Head;
};
好了我们的链表完成了,整个链表的数据有什么?只有一个类型为LinkNode的Head指针!!!
曾经看到不少对于单链表的提问是这样的
不管是用类的继承或是嵌套类,创建一个单链表只有唯一的一个LinkList对象,它会链接多个LinkNode对象。
①构造函数
I.无参构造函数
LinkList(){
Head = new LinkNode<Temp>;
if (NULL == Head)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
};
没什么好说的,用new从堆分配一个结点的空间给我们都头结点指针,注意检查堆是否满是一个很好的习惯。
II.含参构造函数
这里是函数通过参数列表复用,可以将头结点的数据域利用起来保存一些例如链表长度等信息。
template <class Temp>
LinkList<Temp>::LinkList(const Temp &item)
{
Head = new ListNode<Temp>(item);
if (NULL == Head)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
}
此处定义是放在类定义之外的,所有要再声明模板类型,并提供名称空间。
②创建单链表
按照大多数数据结构的书会先介绍某个位置节点的插入和删除,再在链表的创建中调用这些方法,但是连就一个头结点你就来介绍插入、删除某个结点了,我接受不了,虽然代码复用性会降低,但我们还是一步一步的来。
首先定义一个指向Head的指针q;
然后不断重复以下三个步骤完成单链表的构造:
①用new申请一个LinkNode的空间返回指针p,并输入数据元素;
②q->next=p,将q指针对应结点的后继修改为p;
③q=q->next,将指针指向其直接后继,这样一个结点就被链接进了链表之中。
template <class Temp>
void LinkList<Temp>::CreateLink(int n)
{
//输入n个元素的值,建立包含头结点的单链表
LinkNode<Temp> *q = Head;
for (int i = n; i >0; i--)
{
cout << "Enter the " << i << "th element: ";
LinkNode<Temp> * p = new LinkNode<Temp>;
if (NULL == p)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
cin >> p->data;
p->next = q->next;
q->next = p;
q = q->next;
}
}
这里就有个更大的问题了!!!你new了这么多次,都不释放,这不是会导致内存泄露吗?
new发生内存泄露的原因是当指针的生命周期结束后,会自动消亡,而再内存池中分配的内存却无法被释放,但是对于链表,虽然原来指向每一个结点对应地址的指针都没了,但在LinkList的生命周期内,只要头结点存在,且链路正常,就可以依次找到所有的存储空间并释放它。所有我们只需要在链表对象生命周期结束时释放全部内存即可。
③析构函数(单链表的删除)
单链表的删除很简单用两个指针,从头结点开始,一前一后依次释放申请的内存即可。
template <class Temp>
void LinkList<Temp>::DestoryList()
{
LinkNode<Temp> *p=Head;
LinkNode<Temp> *t;
while (p)
{
t = p->next;
delete p;
p = t;
}
}
析构函数直接调用了链表的删除操作即可
template <class Temp>
LinkList<Temp>::~LinkList()
{
DestoryList();
}
好了写到这里,虽然还有很多的操作没写但是基本上单链表的框架有了啊,我们来测试一下吧。
#include <iostream>
#include "LinkList.h"
int main()
{
using namespace std;
LinkList<int> text;
text.CreateLink(5);
LinkList<int> c=text;
cin.get();
return 0;
}
虽然无法输,但是运行应该没问题吧!
咕~~(╯﹏╰)b
什么鬼,深浅拷贝的问题,一张图说明什么是深浅拷贝
浅拷贝只是将对象B也指向A存储的内容,那么问题来了,A对象析构之后,B对象怎么办?
而深拷贝就是分配了资源,数据有自己独立不同的存储地址和空间,两个对象之间的析构不会互相干扰。
因此好的习惯的显示定义类的构造函数,拷贝构造函数,和赋值函数。
我们首先来看看拷贝赋值函数。
④拷贝构造函数和赋值函数
操作都很简单,依次分配内存拷贝链接即可,类似于链表的构建。区别在于拷贝构造还没有LinkList对象,需要创建,而赋值已经有了LinkList对象,需要将其链表删除再重新构造。
template <class Temp>
LinkList<Temp>::LinkList(const LinkList ©)
{
LinkNode<Temp> *p = copy.Head->next;
Head = new LinkNode<Temp>;
if (NULL == Head)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
LinkNode<Temp> *h = Head;
while (p!=NULL)
{
LinkNode<Temp> *t = new LinkNode<Temp>;
if (NULL == t)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
h->next = t;
t->data = p->data;
p = p->next;
h = h->next;
}
}
template <class Temp>
LinkList<Temp> & LinkList<Temp>::operator=(LinkList<Temp> &List)
{
using std::cout;
DestoryList();
LinkNode<Temp> *p = List.Head;
LinkNode<Temp> *h = Head;
while (p != NULL)
{
LinkNode<Temp> *t = new LinkNode<Temp>;
if (NULL == t)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
h->next = t;
t->data = p->data;
p = p->next;
h = h->next;
}
return *this;
}
这里要注意对=的重载返回的是类的引用。
LinkList c=text ; 会调用拷贝构造函数
LinkList c;
c=text; 会调用赋值函数
⑤插入和删除
插入
将前一节点的后继指向新结点,在将新结点的后继指向前一结点的后继即可;
template <class Temp>
bool LinkList<Temp>::LinkInsert(Temp item, int pos)
{
using std::cerr;
using std::endl;
LinkNode<Temp> *p = Locate(pos-1);
if (p == NULL)
{
return false;
}
LinkNode<Temp> *node = new LinkNode<Temp>(item);
if (NULL == node)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
node->next = p->next;
p->next = node;
return true;
}
template <class Temp>
bool LinkList<Temp>::LinkDelete(Temp &item, int pos)
{
LinkNode<Temp> *p = Locate(pos-1);
if (NULL == p || NULL == p->next)
return false;
LinkNode<Temp> *del = p->next;
p->next = del->next;
item = del->data;
delete del;
return true;
}
其他不在单独介绍了,需要注意的是单链表的翻转需要三个指针才能完成(可能是比较蠢的方法就不多说了),完整的单链表文件如下:
//ListNode.h
#ifndef LISTNODE_H_
#define LISTNODE_H_
//单链表的结点定义
using namespace std;
template <class Temp>
struct LinkNode
{
Temp data;
LinkNode<Temp> *next;
LinkNode(LinkNode<Temp> *ptr = NULL){ next = ptr; } //struct构造函数
LinkNode(const Temp &item, LinkNode<Temp> *ptr = NULL) //函数参数表中的形参允许有默认值,但是带默认值的参数需要放后面
{
next = ptr;
data = item;
}
};
template <class Temp>
class LinkList
{
public:
LinkList(){
Head = new LinkNode<Temp>;
if (NULL == Head)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
};
//无参构造函数
LinkList(const Temp &item);
//含参构造函数
LinkList(const LinkList ©);
//拷贝构造函数
LinkList<Temp>& operator= (LinkList<Temp> &List);
~LinkList();
//析构函数
void CreateLink(int nLinkList);
//创建长度为nLinkList的单链表
void DestoryList();
//摧毁线性表
void ClearList();
//清空单链表
bool ListIsLoop() const;
//链表是否有环
bool ListEnpty() const;
//判断链表是否为空,为空返回true,否则返回false
int ListLength() const;
//返回链表中数据元素个数
bool GetElem(int pos, Temp &item);
//用item获取第pos个数据元素的值,false表示失败
void SetHead(LinkNode<Temp> *p);
//设置链表头结点
LinkNode<Temp>* Find(Temp &item);
// 返回第一个找到的满足该数据元素的结点对应的指针
LinkNode<Temp>* Locate(int pos);
//返回第pos个数据元素对于的指针
bool LinkInsert(Temp item, int pos);
//将数据元素item插入到第i个结点的位置,成功返回true,否则返回false
bool LinkDelete(Temp &item,int pos);
//将第pos个结点删除,并用item返回该数据对象
void Print() const;
//打印链表
void Sort();
//链表排序
void Reverse();
void Reverse(int nStart, int nEnd);
//链表逆置
void LinkSwap(int pos1, int pos2);
void LinkSwap(LinkNode<Temp> * p1, LinkNode<Temp> * p2);
//交换两个结点内容
bool LinkIsPalindrome();
//单链表是不回文
private:
LinkNode<Temp> *Head;
};
template <class Temp>
LinkList<Temp>::LinkList(const Temp &item)
{
Head = new ListNode<Temp>(item);
if (NULL == Head)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
}
template <class Temp>
LinkList<Temp>::LinkList(const LinkList ©)
{
LinkNode<Temp> *p = copy.Head->next;
Head = new LinkNode<Temp>;
if (NULL == Head)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
LinkNode<Temp> *h = Head;
while (p!=NULL)
{
LinkNode<Temp> *t = new LinkNode<Temp>;
if (NULL == t)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
h->next = t;
t->data = p->data;
p = p->next;
h = h->next;
}
}
template <class Temp>
LinkList<Temp> & LinkList<Temp>::operator=(LinkList<Temp> &List)
{
using std::cout;
cout << "*";
DestoryList();
LinkNode<Temp> *p = List.Head;
LinkNode<Temp> *h = Head;
while (p != NULL)
{
LinkNode<Temp> *t = new LinkNode<Temp>;
if (NULL == t)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
h->next = t;
t->data = p->data;
p = p->next;
h = h->next;
}
return *this;
}
template <class Temp>
LinkList<Temp>::~LinkList()
{
DestoryList();
}
template <class Temp>
void LinkList<Temp>::CreateLink(int n)
{
//输入n个元素的值,建立包含头结点的单链表
LinkNode<Temp> *q = Head;
for (int i = n; i >0; i--)
{
cout << "Enter the " << i << "th element: ";
LinkNode<Temp> * p = new LinkNode<Temp>;
if (NULL == p)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
cin >> p->data;
p->next = q->next;
q->next = p;
q = q->next;
}
}
template <class Temp>
void LinkList<Temp>::DestoryList()
{
LinkNode<Temp> *p=Head;
LinkNode<Temp> *t;
while (p)
{
t = p->next;
delete p;
p = t;
}
}
template <class Temp>
void LinkList<Temp>::ClearList()
{
LinkNode<Temp> *p = Head;
while (p)
{
p->data = NULL;
p= p->next;
}
}
template <class Temp>
void LinkList<Temp>::Print() const
{
using std::cout;
using std::endl;
LinkNode<Temp> *p = Head->next;
while (p)
{
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
template <class Temp>
bool LinkList<Temp>::ListEnpty() const
{
LinkNode<Temp> *p = Head->next;
while (p)
{
if (p->data==NULL)
{
return true;
}
p = p->next;
}
return false;
}
template <class Temp>
int LinkList<Temp>::ListLength() const
{
LinkNode<Temp> *p = Head->next;
int nLink=0;
while (p)
{
nLink++;
p = p->next;
}
return nLink;
}
template <class Temp>
bool LinkList<Temp>::GetElem(int pos, Temp &item)
{
LinkNode<Temp> *p = Locate(pos);
if (p == NULL)
{
return false;
}
item = p->data;
return true;
}
template <class Temp>
void LinkList<Temp>::SetHead(LinkNode<Temp> *p)
{
Head->data = p->data;
Head->next = p->next;
}
template <class Temp>
LinkNode<Temp>* LinkList<Temp>::Find(Temp &item)
{
LinkNode<Temp> *p = Head->next;
while (p)
{
if (p->data==item)
{
break;
}
p = p->next;
}
return p;
}
template <class Temp>
LinkNode<Temp>* LinkList<Temp>::Locate(int pos)
{
LinkNode<Temp> *p = Head;
while (pos--)
{
p = p->next;
}
return p;
}
template <class Temp>
bool LinkList<Temp>::LinkInsert(Temp item, int pos)
{
using std::cerr;
using std::endl;
LinkNode<Temp> *p = Locate(pos-1);
if (p == NULL)
{
return false;
}
LinkNode<Temp> *node = new LinkNode<Temp>(item);
if (NULL == node)
{
cerr << "分配内存失败!" << endl;
exit(1);
}
node->next = p->next;
p->next = node;
return true;
}
template <class Temp>
bool LinkList<Temp>::LinkDelete(Temp &item, int pos)
{
LinkNode<Temp> *p = Locate(pos-1);
if (NULL == p || NULL == p->next)
return false;
LinkNode<Temp> *del = p->next;
p->next = del->next;
item = del->data;
delete del;
return true;
}
template<class Temp>
void LinkList<Temp>::Reverse()
{
LinkNode<Temp> *pre = Head->next;
LinkNode<Temp> *curr = pre->next;
LinkNode<Temp> *next = NULL;
Head->next->next = NULL;
while (curr)
{
next = curr->next;
curr->next = pre;
pre = curr;
curr = next;
}
Head->next = pre;
}
template<class Temp>
void LinkList<Temp>::Reverse(int nStart, int nEnd)
{
LinkNode<Temp> *start = Locate(nStart-1);
LinkNode<Temp> *end = Locate(nEnd);
LinkNode<Temp> *pre = Locate(nStart);
LinkNode<Temp> *curr = pre->next;
LinkNode<Temp> *next = NULL;
pre->next =end->next;
while (pre!=end)
{
next = curr->next;
curr->next = pre;
pre = curr;
curr = next;
}
start->next = pre;
}
template<class Temp>
void LinkList<Temp>::LinkSwap(int pos1, int pos2)
{
LinkNode<Temp> *p1 = Locate(pos1);
LinkNode<Temp> *p2 = Locate(pos2);
Temp t = p1->data;
p1->data = p2->data;
p2->data = t;
}
template<class Temp>
void LinkList<Temp>::LinkSwap(LinkNode<Temp> * p1, LinkNode<Temp> * p2)
{
Temp t = p1->data;
p1->data = p2->data;
p2->data = t;
}
template<class Temp>
void LinkList<Temp>::Sort()
{
LinkNode<Temp> *p =Head;
for (int i =1; i <ListLength(); i++)
{
p = Locate(1);
for (int j = 1; j <= ListLength()-i; j++)
{
if (p->data >( p->next->data))
{
LinkSwap(p, p->next);
}
p = p->next;
}
}
}
template<class Temp>
bool LinkList<Temp>::ListIsLoop() const
{
LinkNode *slow = Head;
LinkNode *fast = Head;
while (fast->next!=NULL)
{
slow = slow->next;
fast = fast->next->next;
if (slow==fast)
{
return true;
}
}
return false;
}
template<class Temp>
bool LinkList<Temp>::LinkIsPalindrome()
{
int len = ListLength();
if (len%2==0)
{
Reverse((len / 2) + 1, len);
LinkNode<Temp> *p1 = Locate(1);
LinkNode<Temp> *p2 = Locate((len / 2) + 1);
while (p2->next!=NULL)
{
if (p1->data!=p2->data)
{
return false;
}
p2 = p2->next;
p1 = p1->next;
}
return true;
}
else
{
Reverse((len / 2) + 2, len);
LinkNode<Temp> *p1 = Locate(1);
LinkNode<Temp> *p2 = Locate((len / 2) + 2);
while (p2->next != NULL)
{
if (p1->data != p2->data)
{
return false;
}
p2 = p2->next;
p1 = p1->next;
}
return true;
}
}
#endif
因为也是初学者,力求直观简单了,可能很多地方不够优化
(^U^)ノ~YO