鉴于我个人顺序存储时不使用顺序表,而常常用数组替代(并非好习惯),直接来说链表。
链表,即使用链式存储结构的线性表。可以想象用一根链条将不同位置处的信息连接起来,则链表中相邻元素在内存中可能不相邻。链表的连接,则由元素的指针域完成。根据指针域,可分为单向链表、双向链表、循环链表等等等。
一、单链表(SList)
每个元素(也称之为结点)包含两部分,值与指针,图上默认的是数值型数据,头指针指向第一个结点,结点的指针指向它的下一个结点,则最后一个结点指向空(NULL)。
关于单链表的代码表示,是基于结点的表示的。由于结点元素类型不确定,故采用C++模板机制编写,如下:
// C++模板机制
template <class T>
struct SListNode
{
T data; //值
SListNode<T> *next;//指向下一结点
};
结点已经表示完毕,接下来就是单链表类的属性与方法的代码,同样是用C++实现的,如下:
我设置了一个额外的头指针始终指向第一个结点,若无结点,则指向NULL。
//单链表类
template <class T>
class SList
{
public:
SList(); //无参构造
SList(T a[], int n);//有参构造
~SList(); //析构函数
void output(); //遍历整个链表并打印
bool isEmpty(); //判断链表是否为空
T get(int i); //按位查找
SListNode<T> *find(T x);//查找第一个值为x的结点
int location(T x); //查找第一个值为x的结点的序数
void deleteNode(int i); //删除序数为i的结点
void deleteNode(T x); //删除第一个值为x的结点
void insert(int i, T x);//在第i个位置插入元素
int length(); //返回链表长度
private:
SListNode<T> *head; //头结点
};
关于构造函数的编写如下:
template <class T>
SList<T>::SList()
{
head = new SListNode<T>;
head -> next = NULL;
}
/*
//采用头插法
template <class T>
SList<T>::SList(T a[], int n)
{ //与无参构造相同
head = new SListNode<T>;
head -> next = NULL;
for(int i = 0; i < n; i++)
{
SListNode<T> *temp = new SListNode<T>;//临时结点
temp -> data = a[i];//将数组值赋给该结点
temp -> next = head -> next;//第一步
head -> next = temp;//第二步
}
}*/
其中有参构造部分借助图示可能更好理解(以第一个数据为例)。
-------------------------19.5.20 待续----------------------------
关于头插法,顾名思义,每次将一个数据插到头指针的后面,所以从头指针向后看,顺序是与数组顺序相悖的,即 head ----> a[n-1] ---> a[n-2] ---> ...... ---> a[1] ---> a[0],因而这里注释掉,采用尾插法,即每次插到末指针后。如下:
//采用尾插法
template <class T>
SList<T>::SList(T a[], int n)
{ //与无参构造相同
head = new SListNode<T>;
SListNode<T> *p = head;
for(int i = 0; i < n; i++)
{
SListNode<T> *q = new SListNode<T>;
q -> data = a[i];
p -> next = q; //插到末结点后
p = q;
}
p -> next = NULL; //末结点指向空
}
关于析构操作,当用new生成结点时,用delete来释放它,这是一个较好的习惯。
//析构函数
template <class T>
SList<T>::~SList()
{
while(head -> next != NULL)
{
SListNode <T>*p = head;
head = head -> next;
delete p;
}
}
以及其他方法函数
//遍历整个链表并打印
template <class T>
void SList<T>::output()
{
SListNode<T> *p = head -> next;
cout << "head->";
//不是尾结点点的下一结点
while(p != NULL)
{
cout << p -> data << "->";
p = p -> next;
}
cout << "NULL\n";
}
//判断链表是否为空
template <class T>
bool SList<T>::isEmpty()
{
//不空返回1
return (head -> next != NULL);
}
//按位查找
template <class T>
T SList<T>::get(int i)
{
SListNode<T> *p = head -> next;
int count = 1;
while(p != NULL && count < i)
{
p = p -> next;
++ count;
}
if(p == NULL) throw "位置"; //i超过长度
else return p -> data;
}
//查找第一个值为x的结点
template <class T>
SListNode<T>* SList<T>::find(T x)
{
SListNode<T> *p = head -> next;
while(p != NULL)
{
if(p ->data == x) return p;
p = p -> next;
}
return NULL; //未找到
}
//查找第一个值为x的结点的序数
template <class T>
int SList<T>::location(T x)
{
SListNode<T> *p = head -> next;
int count = 1;
while(p != NULL)
{
if(p -> data == x) return count; //找到即可返回
p = p -> next;
++ count;
}
return 0;
}
-------------------------19.5.21 待续----------------------------
//删除序数为i的结点
template <class T>
void SList<T>::deleteNode(int i)
{
SListNode<T> *p = head;
int count = 0;
while(p != NULL && count < i-1)
{
++ count;
p = p -> next;
}
if(p == NULL || p -> next == NULL )throw "位置";
else
{
SListNode<T> *q = p ->next;
p -> next = q -> next;
delete q;
}
}
//删除第一个值为x的结点
template <class T>
void SList<T>::deleteNode(T x)
{
SListNode<T> *p = head;
while(p != NULL)
{
SListNode<T> *q = p -> next;
if(q -> data == x)
{
p -> next = q -> next;
delete q;
}
p = p -> next;
}
}
//在第i个位置插入元素
template <class T>
void SList<T>::insert(int i, T x)
{
SListNode<T> *p = head;
int count = 0;
while(p != NULL && count < i-1)
{
++ count;
p = p -> next;
}
if(p == NULL || i < 1)throw "位置";
else
{
SListNode<T> *q = new SListNode<T>;
q -> data = x;
q -> next = p -> next;
p -> next = q;
}
}
//返回链表长度
template <class T>
int SList<T>::length()
{
SListNode<T> *p = head -> next;
int count = 0; //计数器
//不是最后一个结点
while(p != NULL)
{
p = p -> next;
++ count;
}
return count;
}
而后,对于链表进行测试使用。
-------------------------19.5.22 待续----------------------------
int main()
{
//无参构造
SList<double> sl;
string str = ((sl.isEmpty()==0)? "空" : "非空");
cout << "sl为空吗?\t" << str << endl;
//sl.insert(0, 0.1); //错误
//sl.insert(4, 4.1); //错误
sl.insert(1, 1.1);
sl.insert(2, 2.1);
sl.output();
str = ((sl.isEmpty()==0)? "空" : "非空");
cout << "插入数据后sl为空吗?\t" << str << endl;
cout << "插入数据后sl的长度是:\t" << sl.length() << endl;
//有参构造
double a[6] = {1.1, 2.1, 3.1, 4.1, 0, 6.1};
SList<double> SL(a,6);
SL.output();
//按位查找
cout << "第" << 4 << "个结点值为:\t" << SL.get(4) << endl;
//查找第一个值为x的结点的序数
cout << "值为1.1的结点序数为:\t" << SL.location(1.1) << endl;
cout << "值为2.7的结点序数为:\t" << SL.location(2.7) << endl;
//删除序数为i的结点
SL.deleteNode(5);
cout << "删除第5个结点后\t";
SL.output();
//删除第一个值为x的结点
SL.deleteNode(6.1);
cout << "删除值为6.1的结点后\t";
SL.output();
}
实验成功!
二、循环链表(CList)
即把尾结点指向第一个结点。形成一个包括所有结点在内的环路。
这样的话,找到终端结点同单链表的时间复杂度相同,都为O(n),并没有什么方便可言。但如果设置尾指针,会方便得多。
------------------------19.5.24 待续----------------------------