数据结构之线性表 – 链式描述
概述
与数组描述不同,在链式描述中,线性表的元素在内存中的存储位置是随机的。每个元素都有一个明确的指针(链)指向线性表的下一个元素的位置(地址)
链表的种类
一般分为单链表,双链表和循环链表(单向或双向)。
单链表
双链表
链表中每个节点有两个链,一个指向上一个节点,一个指向下一个节点
循环链表
循环链表是单链表的一种变种,单链表是将尾节点置为NULL,而循环链表是将尾节点指向头结点。
链表的节点结构
这里我们以单链表为例,构建它的结构,如上图所示一个单链表需要有数据域(用来存放元素),链域(用来指向下一个节点),所以代码如下:
// 定义节点的结构
template <typename T>
struct chainNode
{
T element;
chainNode<T> *next;
chainNode() {}
chainNode(const T& element)
{
this->element = element;
}
chainNode(const T& element, chainNode<T>* next)
{
this->element = element;
this->next = next;
}
};
链表的类(class chain)
当我们定义一个单链表的时候,我们考虑到要如何创建,如何添加,如何删除等等操作,因此编写代码如下。
// 链表的操作
template <typename T>
class chain
{
public:
chain();
chain(const chain<T>&);
~chain();
//ADT实现
void add(int element); // 向尾端添加元素
bool empty() const { return listSize == 0; } // 判断是否为空
int size() const { return listSize; } // 返回链表长度
T& get(int theIndex) const; // 根据下标获取链表元素
int indexof(const T& theElement) const; // 根据元素获取下标
void erase(int theIndex); // 根据下标删除元素
void insert(int theIndex, const T& theElement); // 插入操作
void output() const; // 输出链表
private:
bool checkIndex(int theIndex) // 检查下标
{
if(theIndex < 0 || theIndex > listSize)
return false;
return true;
}
chainNode<T>* firstNode;
int listSize;
};
链表类的实现
构造函数和析构函数
template <typename T>
chain<T>::chain()
{
firstNode = NULL;
listSize = 0;
}
template <typename T>
chain<T>::chain(const chain<T>& theList)
{
listSize = theList.listSize;
if(listSize == 0)
{
firstNode = NULL;
listSize = 0;
//return;
}
// 进行拷贝
// 中间变量
chainNode<T>* sourceNode = theList.firstNode;
firstNode = new chainNode<T>(sourceNode->element);
// 因为要保证firstNode始终指向头指正,并且能够正常赋值,
// 采取另一个指针来拷贝
chainNode<T>* temp = firstNode;
sourceNode = sourceNode->next;
while(sourceNode != NULL)
{
temp->next = new chainNode<T>(sourceNode->element);
temp = temp->next;
sourceNode = sourceNode->next;
}
temp->next = NULL;
}
template <typename T>
chain<T>::~chain()
{
while(firstNode != NULL)
{
chainNode<T>* nextNode = firstNode->next;
delete firstNode;
firstNode = nextNode;
}
}
add() 和 insert() 操作
思路都是把上一个节点的指针指向插入的元素,插入元素的指针指向节点的下一个节点
代码实现
template <typename T>
void chain<T>::add(int theElement)
{
if(firstNode == NULL)
{
firstNode = new chainNode<T>(theElement, firstNode);
}
else
{
chainNode<T>* p = firstNode;
while(p->next != NULL)
{
p = p->next;
}
p->next = new chainNode<T>;
p->next->element = theElement;
p->next->next = NULL;
}
listSize++;
}
template <typename T>
void chain<T>::insert(int theIndex, const T& theElement)
{
if(theIndex < 0 || theIndex > listSize)
return;
if(theIndex == 0)
{
firstNode = new chainNode<T>(theElement, firstNode);
}
else
{
chainNode<T>* p = firstNode;
for(int i = 0; i < theIndex - 1; i++)
{
p = p->next;
}
p = new chainNode<T>(theElement, p->next);
}
listSize++;
}
erase() 操作
例如,有N个节点的链表,要删除第三个节点,即是遍历到第二个节点,将第二个节点的链域从原来指向第三个节点的指针,改为指向第四个节点,然后释放第三个节点即可
template <typename T>
void chain<T>::erase(int theIndex)
{
if(!checkIndex(theIndex))
return;
chainNode<T>* deleteNode = firstNode;
if(deleteNode == NULL)
{
deleteNode = firstNode;
firstNode = firstNode->next;
}
else
{
chainNode<T>* p = firstNode;
for(int i = 0; i < theIndex - 1; i++)
{
p = p->next;
}
deleteNode = p->next;
p->next = p->next->next;
}
listSize--;
delete deleteNode;
}
get() 和 indexof() 操作
在线性表中要查找一个元素的位置,必须从第一个节点出发,依次遍历每一个节点,直到你需要的节点的位置停止。因此每次遍历的时候我们都需要一个临时指针(指向firstNode节点)来替代头结点,进行遍历,以防止头结点的指向错误,导致下一次遍历失败(或者说,下一次遍历的时候不是从头结点出发,而是从你上次遍历的时候停止的地方)
template <typename T>
T& chain<T>::get(int theIndex) const
{
chainNode<T>* currentNode = firstNode;
for(int i = 0; i < theIndex; i++)
{
currentNode = currentNode->next;
}
return currentNode->element;
}
template <typename T>
int chain<T>::indexof(const T& theElement) const
{
chainNode<T>* currentNode = firstNode;
int index;
for(int i = 0; i < listSize; i++)
{
if(currentNode->element == theElement)
break;
currentNode = currentNode->next;
index++;
}
if(currentNode == NULL)
return -1;
else
return index;
}
其他
相对于单链表,双向链表和循环链表有许多不同,例如:我们如何计算它的链表长度?
这些留给下一次写吧,谢谢你的观看,如果有什么意见请提出来。
友情链接:数据结构之线性表 – 链式描述