其他类型的线性表
1 循环链表
循环链表是另一种形式的链式存储结构。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。(通常为了使空表和非空表的处理一致,通常也附加一个头结点)
一般都使用尾指针来指示循环链表,因为使用尾指针查找开始结点和终端结点都很方便。
循环链表没有增加任何存储量,仅对链接方式稍作改变,循环链表仅在循环条件与单链表不同。从循环链表的任一结点出发可扫描到其他结点,增加了灵活性。但是,由于循环链表没有明显的尾端,所以链表操作有进入死循环的危险。通常以判断指针是否等于某一指定指针来判定是否扫描了整个循环链表。
1.1 模板
#include<iostream>
using namespace std;
template<class DataType>
struct Node //创建一个链表的节点,数据域和指针域
{
DataType data;
Node<DataType> *next;
};
template<class DataType>
class LinkList
{
public:
LinkList(); //无参构造
LinkList(DataType a[], int n); //有参构造
~LinkList(); //析构函数
int Length(); //长度函数
DataType Get(int i); //按位查找
int Locate(DataType x); //按值查找
void Insert(int i, DataType x); //插入
DataType Delete(int i); //删除
void PrintList(); //打印输出
private:
Node<DataType> *first; //第一个空节点
};
1.2 无参构造,第一个结点空结点
template<class DataType>
LinkList<DataType>::LinkList()
{
first = new Node<DataType>;
first->next = NULL;
}
1.3 有参构造,其他结点包括空结点
template<class DataType>
LinkList<DataType>::LinkList(DataType a[], int n)
{
first = new Node<DataType>;
first->next = NULL; //空结点
for (int i = 0; i < n; i++) //循环创建其他结点,最后一个结点的指针域指向第一个空结点
{
Node<DataType> *s = new Node<DataType>;
s->data = a[i];
s->next = first->next;//这边这个first->next;指的是原来还没有插入的时候的节点的数据,这个时候正好就把要插入的s节点指向了s后面的节点的数据了。
first->next = s;
}
}
1.4 析构函数
template<class DataType>
LinkList<DataType>::~LinkList()
{
while (first != NULL)
{
Node<DataType>* q = first;
first = first->next;
delete q;
}
}
1.5 链表长度
template<class DataType>
int LinkList<DataType>::Length()
{
Node<DataType>* p = first->next;
int count = 0;
while (p != NULL)
{
p = p->next;
count++;
}
return count;
}
1.6 按位查找
template<class DataType>
DataType LinkList<DataType>::Get(int i)
{
Node<DataType>* p = first->next;
int count = 1;
while (p != NULL && count<i)
{
p = p->next;
count++;
}
if (p == NULL) throw "Location";
else return p->data;
}
1.7 按值查找
template<class DataType>
int LinkList<DataType>::Locate(DataType x)
{
Node<DataType> *p = first->next;
int count = 1;
while (p != NULL)
{
if (p->data == x) return count;
p = p->next;
count++;
}
return 0;
}
1.8 插入
template<class DataType>
void LinkList<DataType>::Insert(int i, DataType x)
{
Node<DataType> *p = first;
int count = 0;
while (p != NULL && count<i - 1)
{
p = p->next;
count++;
}
if (p == NULL) throw "Location";
else {
Node<DataType> *s = new Node<DataType>;
s->data = x;
s->next = p->next;
p->next = s;
}
}
1.9 删除
template<class DataType>
DataType LinkList<DataType>::Delete(int i)
{
Node<DataType> *p = first;
int count = 0;
while (p != NULL && count<i - 1)
{
p = p->next;
count++;
}
if (p == NULL || p->next == NULL) throw "Location";
else {
Node<DataType> *q = p->next;
int x = q->data;
p->next = q->next;
return x;
}
}
1.10 遍历
template<class DataType>
void LinkList<DataType>::PrintList()
{
Node<DataType> *p = first->next;
while (p != NULL)
{
cout << p->data << endl;
p = p->next;
}
}
1.11测试
int main()
{
LinkList<int> p;
p.Insert(1, 6);
p.Insert(2, 9);
p.PrintList();
p.Insert(2, 3);
p.PrintList();
cout << p.Get(2) << endl;
cout << p.Locate(9) << endl;
cout << p.Length() << endl;
p.Delete(1);
p.PrintList();
return 0;
}
2. 双链表
循环链表虽然可以从任意结点出发扫描其他结点,但是如果要查找其前驱结点,则需遍历整个循环链表。为了快速确定任意结点的前驱结点,可以再每个节点中再设置一个指向前驱结点的指针域,这样就形成了双链表。
2.1 存储方法
template<class DataType>
struct Node
{
DataType data;
Node<DataType> *prior,*next;
};
结点p的地址既存储在其前驱结点的后继指针域内,又存储在它后继结点的前驱指针域中
注意:
循环双链表中求表长、按位查找、按值查找、遍历等操作的实现与单链表基本相同。
插入操作需要修改4个指针,并且要注意修改的相对顺序。
3. 静态链表
静态链表是用数组来表示单链表,用数组元素的下标来模拟单链表的指针。
3.1 静态链表的存储结构
const int MaxSize = 100;
template<class DataType>
struct Node{
DataType data;
int next;
}SList[MaxSize];
静态链表虽然是用数组来存储线性表的元素,但在插入和删除操作时,只需要修改游标,不需要移动表中的元素,从而改进了在顺序表中插入和删除操作需要移动大量元素的缺点,但是它并没有解决连续存储分配带来的表长难以确定的问题。
4.间接寻址
间接寻址是将数组和指针结合起来的一种方法,它将数组中存储的单元改为存储指向该元素的指针。
该算法的时间复杂度仍为O(n)
但当每个元素占用较大空间时,比顺序表的插入快的多。线性表的间接寻址保持了顺序表随机存取的优点,同时改进了插入和删除操作的时间性能,但是它也没有解决连续存储分配带来的表长难以确定的问题。