C++实现单链表
最近在学习数据结构的链表部分,发现大多数网上写的单链表多多少少都有些瑕疵,并且解释不够清晰,对于初学者很不友好。更有甚者C和C++代码混用,对于只学过C++的同学看起来很费劲。虽然这里属于数据结构的基础知识,但是还是建议小白对C++基本的语法能够了解,并且知道单链表的构造,要不然每一句的解释敲的再详细也看不懂。
下面是代码和相应解释:
#include <iostream>
using namespace std;
template <class T>//建立模板,使其能够放入多种数据
class Node//创建结点类
{
public:
T data;//数据域
Node<T>* next;//指针域
};
template<class T>
class linkList //创建链表类
{
private:
//注意每次用Node时要加上模板参数
Node<T>* front;//建立头指针
public:
linkList()
{
front = new Node<T>; //在堆区开辟头结点,并用头指针维护
//front->data = 0;//我觉得这不对,因为data是T型,用0置空合适吗
front->next = NULL;//头结点置空
}
~linkList();
//下面这两种没有参数的链表创建法的局限是只能让T为一些基础类型,
//没有办法传入复杂类型,如果需要传入复杂类型要事先把数据放入到
//数组中,然后给成员函数传入数组和数组长度做形参
void createList1();// 头插法建立链表
void createList2();//尾插法
//按照位序查找元素,找到了返回结点指针(这个返回指针很有用,后面会用到)
//找不到返回NULL
Node<T>* getElem(int No);
//按值查找,找到返回位置,找不到返回-1
int locateElem(T &data);
//插入操作
void insert(int No, T data);
//删除操作
void Delete(int No);
//打印操作
void printList();
};
template <class T>
linkList<T>::~linkList()
{
Node<T>* p = front;//初始化指针p=头指针,让他指向头结点
while (p)//当p不为NULL时,执行语句
{
//这里不直接删除指针p指向的堆区数据的原因是顺序问题
//如果删掉p,而没用front备份,那么p将无法偏移。
front = p;//front随着p走,delete front 来释放堆区数据
p = p->next;//p为工作指针,p不断偏移
delete front;
}
}
template <class T>
void linkList<T>::createList1()//头插法
{
Node<T>* p = this->front;//建立工作指针,初始化指向头结点
T data;
int length = 0;
cout << "输入链表长度" << endl;
cin >> length;
for (int i=0; i < length; i++)
{
cout << "输入第" << i + 1 << "个值" << endl;
cin >> data;
Node<T>* temp = new Node<T>; //每一个结点都在堆区开辟一个新的,用temp指针维护
temp->data = data;
//这里可以先分情况看
//第一种情况是现在表是空表,那么头结点的指针域指向NULL,那么当前结点的指针域也
//要指NULL,即和p->next相等。然后再让p->next(头指针指针域)指向当前结点
//第二种情况是现在不是空表,那么头插时,当前结点要插在头结点和首元结点之间
//由于在第一次插入的时候,p->next已经指向了首元结点,即第一个temp,
//所以当前结点也要指向首元结点,即,temp->next=p->next;
//最后更新p(头结点),让头结点指针域指向当前结点
//所以这两种情况是一种写法。
temp->next = p->next;
p->next = temp;
}
}
template <class T>
void linkList<T>::createList2()//尾插法
{
Node<T>* p = front;//建立工作指针
int length = 0;
T data;
cout << "输入新链表长度" << endl;
cin >> length;
for (int i = 0; i < length; i++)
{
Node<T>* temp = new Node<T>;
cout << "输入第" << i + 1 << "个数据" << endl;
cin >> data;
temp->data = data;
//这两步就不难了,让当前结点的指针域和工作指针的结点指针域相同
//就是让工作结点指向当前结点
//然后更新工作结点p。
p->next=temp;
p = temp;
}
p->next = NULL;
}
template <class T>
Node<T>* linkList<T>::getElem(int No)
{
//这个函数时访问不到头结点的
Node<T>* p = front->next;//这里工作指针就不从头结点开始了,从首元结点开始
int j = 0;//首元结点算0号位置
//太巧妙了,j作为计数器一直++,同时p指针不断偏移,直到和No相等。
//为了防止超出链表长度,&&了p,限制了p不能指空。
while (p && j != No)
{
p = p->next;
//当没有找到得时候,也就是No超出了链表长度,p最后指向了NULL,
//返回了一个空指针,所以这里返回值必需返回指针而不是Node<T>
j++;
}
return p;
}
template <class T>
int linkList<T>::locateElem(T& data)
{
Node<T>* p = front->next;//还是从首元结点开始建立工作指针
int i = 0;
while (p)//遍历链表
{
if (p->data == data)//同样,这里的==只适用于基础变量型,如果是自定义变量,则需要重载==
{
return i;
}
else
{
p = p->next; //不相等时工作指针偏移
i++;//位序偏移
}
}
return -1;//找不到返回-1
}
template <class T>
void linkList<T>::insert(int No, T data)
{
//本质上是在No结点后插入了新结点,然后将No结点数据赋值给新结点
//再让No结点接受形参data,完成了插入
Node<T>* p = this->getElem(No);//初始化工作指针
if (p)
{
Node<T>* newNode = new Node<T>;//堆区新建结点
newNode->data = p->data;//把位序指针数据给新节点
newNode->next = p->next;
p->data = data;
p->next = newNode;
}
else
{
cout << "插入失败" << endl;
}
}
template <class T>
void linkList<T>::Delete(int No)
{
Node<T>* p = front;//初始化工作指针
Node<T>* temp = this->getElem(No);
if (No > 0&&temp)
{
p = this->getElem(No - 1);//工作指针指向被删除结点前一个结点
p->next = temp->next;
delete temp;
}
else if (No == 0)
{
p->next = temp->next;//头结点指向第二个结点
delete temp;
}
}
template <class T>
void linkList<T>::printList()
{
Node<T>* p = front->next;//初始化工作节点,指向首元结点
while (p)
{
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
int main()
{
linkList<int> l1;
l1.createList2();
l1.printList();
Node<int> *p=l1.getElem(3);
cout << p->data << endl;
l1.Delete(2);
l1.printList();
l1.insert(3, 10);
l1.printList();
int find = 5;
int pos = l1.locateElem(find);
cout << pos << endl;
system("pause");
return 0;
}
以下是测试结果:

948

被折叠的 条评论
为什么被折叠?



