此篇为本人自己学习过程中的笔记,有许多不足之处,仅供参考。
目录
4.8.1 按值查找——根据指定数据获取该数据的位置(地址)
链式表
1. 定义与介绍
有关线性表的定义上一篇已经介绍,所以直接介绍链式储存的特点
(2条消息) 1.数据结构C++学习笔记——线性表(顺序储存)_偏爱晚风_ly的博客-CSDN博客
概念:线性表的链式表示,又称为非顺序映像,或链式映像。
● 用一组物理位置任意的储存单元,来存放线性表的数据元素。
● 这组存储单元即可以是连续的,也可以是不连续的,甚至是零散分布在内存的各个位置上
● 逻辑上相邻的数据元素,在物理上不一定相邻。
2. 有关术语
链表:n个节结点由指针域组成一个链表。
结点:数据元素的储存映像,由数据域和指针域组成。
数据域:存储数据元素本身。
指针域:储存直接后继结点的储存位置。最后一个元素的指针域为空。
头结点:在首元结点之前附加的一个节点。头结点的数据域为可以空,也可以存放数据表长度等附加信息,但此结点不能计入链表长度值。
头指针:指向链表中第一个节点的指针。单链表由头指针唯一确定,因此单链表可以用头指针的名字来命名。
首元节点:链表中储存第一个数据元素的节点。
图2.1 指针节点相关示意图
单链表:结点只有一个指针域的链表,也叫线性链表。
图2.2 带头节点的单链表示意图
双链表:结点有两个指针域的链表。
图2.3 带头节点的双链表示意图
循环链表:首尾相连的链表。
图2.4 带头节点的循环链表示意图
3. 链表存储结构的特点
(1)结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。
(2)访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等。这种存取元素的方法称为顺序存储法。我们之前学过的顺序表,是随机储存法,时间复杂度为。
讨论1——如何表示空表
无头结点时,头指针为空的表为空表。
有头结点时,头结点的指针域为空,则表示空表。
讨论2——在链表中设置头结点有什么好处
1. 便于首元结点的处理,首元结点的地址保存在头结点的指针域中,所以在链表第一个位置的操作,与其他位置一样,不需要进行特殊处理,
2. 便于空表与非空表的统一处理,无论链表是否为空,头指针都是指向头结点的非空指针,因此,空表与非空表的处理也就统一了。
4. 以C++模版类实现链式存储
4.1 链表类与节点类的定义
template<class T>
class LinkList; //先声明,方便引用
template<class T>
class Node
{
friend class LinkList<T>; //设为友元
public:
T data; //数据域
Node<T> * next; //指针域
};
template<class T>
class LinkList
{
public:
LinkList(); // 构造函数
~LinkList(); // 析构函数
void AddtoHead(); //头插法
void AddToTail(); //尾插法
void IsEmpty(); //判断链表是否为空
void Destroy(); //销毁链表
void Clear(); //清空链表
int Length(); //求单链表表长
public:
Node<T>* head;//头指针
Node<T>* tail;//尾指针
int length; //单链表长度
};
4.2 判断链表是否为空
template<class T>
void LinkList<T>::IsEmpty()
{
if (!head)
{
cout<<"链表不存在" << endl;
}
else if (head->next == NULL)
{
return TRUE;
}
else
{
return FALSE;
}
}
4.3 单链表的销毁
从头指针开始依次释放所有节点
template<class T>
void LinkList<T>::Destroy()
{
Node<T>* p; //创建一个临时指针
while (p != NULL)
{
p = head; //head头指针中存放头节点的地址,指针p指向头节点
head = head->next; //关键步骤,将head向后移,指向下一个结点
delete p; //释放p
}
}
4.4 清空单链表
链表仍然存在,但是链表中无元素 (头指针,头结点仍然在),从首元结点依次释放所有结点,并将头结点的指针域置空。
template<class T>
void LinkList<T>::Clear()
{
Node<T>* p,*q; //创建临时指针
p = head->next; //p指针指向首元节点
while (p==NULL)
{
q = p->next; //q指针指向p指针的下一个结点
delete p; //释放p
p = q; //再令p指针和q指针指向同一结点
}
head->next = NULL; //头节点的指针域置空
}
4.5 求表长
从首元节点开始,依次计数所有结点
template<class T>
int LinkList<T>::Length()
{
Node<T>* p; //创建临时指针
p = head->next; //p指针指向首元节点
int i = 0; // i为计数变量
while (p == NULL)
{
i++;
p = p->next;
}
return i;
}
4.6 在链表的头部或者尾部插入节点建立单链表
4.6.1头插法
链表是一种动态管理的储存结构,链表中每个节点占用的储存空间不是预先分配的,而是运行时系统根据需求生成的。因此,建立单链表从空表开始,每次读入一个数据元素(结点)就申请一次指针,然后插在链表的头部,因为是从头部插入,所以读入数据元素的顺序与线性表中的逻辑顺序是相反的。算法的时间复杂度为。
template<class T>
void LinkList<T>::AddtoHead()
{
Node<T>* newNode; // newNode 是一个指针,类型为Node<T>
int x;
cin >> x; //输入要插入的元素值x
while (x != FLAG) //x=FLAG时候退出循环
{
newNode = new Node<T>; //新申请节点,newNode指向该节点
newNode->data = x; //将x存放到新节点中的数据域
newNode->next = head->next; //头节点的指针域赋值给新节点的指针域
head->next = newNode; //将新节点插入到链表前端
cin >> x;
}
}
4.6.2尾插法
头插入建立单链表简单,但读入的数据元素的顺序与生成的链表中元素的顺序是相反的,若希望顺序一致,则用尾插入的方法。因为每次是将新结点插入到链表的尾部,所以用法尾指针始终指向链表中的尾结点,以便能够将新结点插入到链表的尾部。
template<class T>
void LinkList<T>::AddToTail()
{
Node<T>* newNode; // newNode 是一个指针,类型为Node<T>
int x;
cin >> x; //输入要插入的元素值x
this->tail = this->head; //尾指针指向头节点
while (x != FLAG) //x=FLAG时候退出循环
{
newNode = new Node<T>; //新申请节点,newNode指向该节点
newNode->data = x; //将x存放到新节点中的数据域
tail->next = newNode; // 插入到表尾
tail = newNode; //tail指向新的尾结点
cin >>x; //继续输入要插入的元素
}
}
4.7 取值 取链表中第i个元素的内容
从链表的头指针出发,顺着链域next逐个结点往下搜索,直至搜索到第i个结点为止。也因此,链表不是随机存储结构。
T LinkList<T>::GetElem(int i)
{
Node<T>* p = this->head->next; //p指向首元节点
int j = 0; //初始化
while (p && p == NULL) //向后扫描,直到p指向第i个元素或p为空
{
p = p->next;
j++;
}
if (!p || j > i) //第i个元素不存在
{
return FALSE;
}
else
{
return p->data; //取第i个元素
}
}
4.8 查找
4.8.1 按值查找——根据指定数据获取该数据的位置(地址)
从第一个结点开始,依次与e进行比较,如果找到一个其值与e相等的数据元素,则返回其在链表中的位置或地址,如果查遍整个链表都没有找到其值和e相等的元素,则返回0或NULL。
template<class T>
Node<T>* LinkList<T>::LocateElem(T e)
{
Node<T>* p = this->head->next; //p指向首元节点
while (p&&p->data!=e)
{
p = p->next;
return p;
}
}
4.8.2 按值查找——根据指定数据获取该数据的位置序号
template<class T>
int LinkList<T>::LocateElem_L(T e)
{
Node<T>* p = this->head->next; //p指向首元节点
int j = 1;
while (p && p->data != e)
{
p = p->next;
j++;
}
if (p)
{
return j;
}
else
{
return 0;
}
}
4.9 插入
在第i个结点前插入值为e的新节点,首先找到ai-1的储存位置p,生成一个数据域为e的新节点S插入新的节点:新节点的指针域指向ai,结点ai-1的指针域指向新节点。
template<class T>
void LinkList<T>::ListInsert(int i, T e)
{
Node<T>* p = this->head;
int j = 0;
if (p && j > i - 1)
{
return FALSE;
}
while (p && j < i - 1) //寻找第i-1个结点,p指向i-1结点
{
p = p->next;
j++;
}
Node<T> s; //生成新节点
s->data = e; //将e放在新节点的数据域中
s->next = p->next; //将结点插入L中
p->next = s;
}
4.10 删除
删除第i个节点,首先找到ai-1的储存位置p,保存要删除的ai的值。令p->next指向ai+1。释放节点ai的空间。
template<class T>
void LinkList<T>::ListDelete(int i)
{
Node<T>* p = this->head;
int j = 0;
if (!(p->next)||j>i-1)
{
cout << "删除位置不合理" << endl;
return;
}
while (p->next && j < i - 1) //寻找第i个节点
{
p = p->next; //p指向前驱
j++;
}
Node<T> *q = p->next;
p->next = q->next;
delete q;
}