一、顺序表
1.1顺序表概述
顺序表(线性表之一)
元素整体化管理,需要将元素归置成元素组。
线性表:序列元素的组织形式,是某类元素的一个集合,还记录着元素之间的一种顺序关系。
线性表是最基本的数据结构之一。
根据线性表的实际存储方式,分为两种实现模型:
顺序表:将元素顺序地存放在一块连续的存储区里,元素间的顺序关系由它们的存储顺序自然表示。
链表,将元素存放在通过链接构造起来的一系列存储块中。
1.2顺序表的存储形式:
1.2.1元素大小相同–连续存储,如下图
数据元素-连续存储
元素的下标是其逻辑地址。
存储的物理地址(实际内存地址)=存储区的起始地址Loc(e0)加上逻辑地址(第i个元素)与存储单元大小(c)的乘积计算而得,即:
Loc(ei) = Loc(e0) + c*i
故,访问指定元素时无需从头遍历,通过计算便可获得对应地址,其时间复杂度为O(1)。
1.2.2.元素大小不同–对实际数据的索引的顺序表,这是最简单的索引结构。
元素的大小不统一:采用元素外置存储—将实际数据元素另行存储,而顺序表中各单元位置保存对应元素的地址信息(即链接)。
每个链接所需的存储量相同,通过上述公式,可以计算出元素链接的存储位置,而后顺着链接找到实际存储的数据元素。
注意,**图b中的c不再是数据元素的大小,而是存储一个链接地址所需的存储量,**这个量通常很小。
1.3顺序表的结构和实现
1.3.1 顺序表的结构
顺序表信息包括:
1.表中元素合集(数据区)
2.表操作所需信息(信息区):元素存储区容量+表中元素个数
1.3.2 顺序表的两种基本实现方式
1.3.2.1 一体式结构
存储表信息的单元与元素存储区以连续的方式安排在一块存储区里
数据元素存储区域是表对象的一部分,顺序表创建后,元素存储区就固定了。
1.3.2.2 分离式结构
表对象里只保存与整个表有关的信息(即容量和元素个数)
实际数据元素存放在另一个独立的元素存储区里,通过链接与基本表对象关联。
1.4 元素存储区替换
1.4.1 一体式(数据区+信息区连续存储)–整体搬迁
1.4.2 分离式—更换数据区,顺序表对象不变,只需将信息区链接地址更新,将信息区连接到新的数据区
1.5.元素存储区扩充-----具体如何执行?
分离式结构的顺序表可用,不改变表对象的前提下对数据存储区进行扩充。
动态顺序表,其容量可动态变化
如何扩充:
方法一线性增长:每次增加固定数目的存储位置,如每次扩充增加10个元素位置。
特点:节省空间,但是扩充操作频繁,操作次数多。
方法二:每次扩充容量加倍,如每次扩充增加一倍存储空间。(推荐)
特点:减少了扩充操作的执行次数,但可能会浪费空间资源。
顺序表的元素插入:此前已知,列表中在首部增加元素和尾部增加元素,效率不同。
1.6 顺序表的代码实现
1.6.1 一维数组静态分配内存的方式实现
typedef struct
{
int m_data[10];//静态数组来存储数据表中的实际元素(静态无法扩容数据)
int m_length;//顺序表当前存储的数据个数
}SeqList;
上述代码中,数组m_data的大小在编译时,已经确定,后续无法改变其大小,这意味着该顺序表只能保存10个元素的数据。
1.6.2 一维数组动态分配内存的方式实现
typefdef struct
{
int *m_data;//顺序表元素中的元素保存在m_data所指向的动态数组内存中
int m_length;//顺序表当前实际长度
int m_maxsize;//顺序表数组的最大容量(因为动态数组可以扩容。因此要记录该值)
}SeqList;
上述代码中,数组m_data的大小事先是不确定的,在执行过程中,用new的方式为m_data指针(一维数组)分配一定数量的内存,当顺序表中装满时,当前顺序表无法容纳,可以用new新开辟一块更大的内存,并将当前内存中的数据拷贝到新内存中去,同时把旧的内存释放掉。
1.6.3 实现的顺序表源码(类似于stl库中的vector容器)
#include <iostream>
using namespace std;
#define InitSize 10//动态数组的初始化尺寸
#define IncSize 5//动态数组每次扩容的长度
template<typename T>
class MyVector
{
public:
MyVector(int length = InitSize);//默认构造初始化长度为宏定义的10
~MyVector();
public:
bool ListInsert(int i, const T& e);//在第i个位置插入指定的元素e
bool ListDelete(int i);//删除第i个位置的元素
bool GetElem(int i,T& e);//获取第i个位置的元素
int LocateElem(const T& e);//寻找在容器中第一次出现元素e的位置
void DispList();//输出顺序表中的所有元素
int ListLength();//获取顺序表的长度
void ReverseList();//翻转顺序表
private:
void IncreaseSize();//当顺序表存满数据后可以调用该函数进行扩容
private:
T* m_data;//存放顺序表中的元素
int m_length;//顺序表当前长度(顺序表中当前存储的元素数量)
int m_maxSize;//顺序表最大的容量
};
template<typename T>
MyVector<T>::MyVector(int length)
{
m_data = new T[length];
m_length = 0;
m_maxSize = length;
}
template<typename T>
MyVector<T>::~MyVector()
{
delete[] m_data;
m_length = 0;//非必须,习惯
}
template<typename T>
bool MyVector<T>::ListInsert(int i,const T& e)
{
if (m_length >= m_maxSize)
{
/*cout << "顺序表已存满了,不能再继续插入数据!" << endl;
return false;*/
IncreaseSize();//存满了则调用扩容函数
}
if ((i < 1) || (i > (m_length+1)))
{
cout << "元素" << e << "插入位置" << i << "不合法,合法位置应该位于1到" << m_length + 1 << "之间" << endl;
return false;
}
//从最后一个元素开始向前遍历到要插入元素的第i个位置,分别将i以后的元素向后移动一个位置
for (int j = m_length; j >= i ;j--)
{
m_data[j] = m_data[j - 1];
}
m_data[i-1] = e;
m_length++;
return true;
}
template<typename T>
bool MyVector<T>::ListDelete(int i)
{
if (m_length < 1)
{
cout << "当前顺序表元素为空,删除元素失败" << endl;
return false;
}
if ((i < 1) || (i > m_length))
{
cout << "删除元素位置" << i << "的元素不合理,合理位置应该位于1到" << m_length << "之间" << endl;
return false;
}
cout << "成功删除位置为" << i << "的元素,该元素的值为" << m_data[i - 1] << endl;
//从第i+1个位置开始向后遍历所有的元素,分别将这些位置中原有的元素向前移动一个位置
for (int j = i; j < m_length;j++)
{
m_data[j - 1] = m_data[j];
}
m_length--;
return true;
}
template<typename T>
bool MyVector<T>::GetElem(int i,T& e)
{
if (m_length < 1)
{
cout << "当前顺序表为空,不能获取任何数据" << endl;
return false;
}
if ((i < 1) || (i > m_length))
{
cout << "获取元素的位置为" << i << "不合法,合法的位置是1到" << m_length << "之间" << endl;
return false;
}
cout << "成功找到第" << i << "个位置的元素,该元素的值为" << m_data[i - 1] << endl;
return true;
}
template<typename T>
int MyVector<T>::LocateElem(const T& e)
{
for (int i = 0; i < m_length; i++)
{
if (m_data[i] == e)
{
cout << "值为" << e << "的元素在顺序表中第一次出现的位置为" << i+1 << endl;
return i+1;
}
}
cout << "值为" << e << "的元素在该顺序表中没有找到" << endl;
return -1;
}
template<typename T>
void MyVector<T>::DispList()
{
for (int i = 0; i < m_length; i++)
{
cout << m_data[i] << " ";
}
cout << endl;
}
template<typename T>
int MyVector<T>::ListLength()
{
return m_length;
}
template<typename T>
void MyVector<T>::ReverseList()
{
if (m_length <= 1)
{
cout << "当前顺序表元素个数少于1个,无需进行翻转操作" << endl;
return;
}
T temp;//定义一个暂时存储数据的元素
for (int i = 0; i < (m_length / 2); i++)
{
temp = m_data[i];
m_data[i] = m_data[m_length - i -1];
m_data[m_length - i - 1] = temp;
}
}
template<typename T>
void MyVector<T>::IncreaseSize()
{
T* p = m_data;
m_data = new T[m_maxSize + IncSize];//重新为顺序表分配一块更大的新内存
for (int i = 0 ; i < m_length; i++)
{
m_data[i] = p[i];
}
m_maxSize += IncSize;
cout << "成功为顺序表扩容,新的顺序表容量为" << m_maxSize << endl;
delete[] p;//释放掉原有的顺序表内存,否则内存泄露
}
int main()
{
MyVector<int> myvector(10);
myvector.ListInsert(1,10);
myvector.ListInsert(2, 20);
myvector.ListInsert(3, 30);
myvector.ListInsert(4, 40);
myvector.ListInsert(5, 50);
myvector.DispList();
myvector.LocateElem(20);
int num = 0;
myvector.GetElem(3,num);
cout << "num的值为" << num << endl;
myvector.ListDelete(1);
myvector.DispList();
myvector.ReverseList();
myvector.DispList();
for (int i = 3; i < 30; i ++)
{
myvector.ListInsert(i,i*30);
}
myvector.DispList();
myvector.ReverseList();
myvector.DispList();
}
程序执行结果:
二、单链表
1.单链表概述
链表就是逻辑连续,物理不一定连续的线性表。链表分为指针域和数据域,数据域存当前节点的有效数据,指针域存放指向下一个节点的指针。如下图,逻辑上是利用指针将其串联起来的,物理上却是杂乱的,最后以NULL空指针结束。在内出在内存中,数组为内存块,而链表为内存点,在内存中分散分布,内存之间不连续,上一个节点的指针保存的是下一个节点的地址,由此进行寻址串联。如下图:
2.单链表的实现代码,如下:
//定义单链表每个节点的结构体
template<typename T>
struct Node
{
T data;//数据域,节点保存的数据
Node<T>* next;//指针域,指向下一个节点(保存的是下一个节点数据域的地址)
};
template<typename T>
class LinkList
{
public:
LinkList();
~LinkList();
public:
bool ListInsert(int i,const T& e);//在第i个位置指定插入元素e
bool ListDelete(int i);//删除第i个位置的元素
bool GetElem(int i,T& e);//获得第i个位置的元素值
int LocateElem(const T& e);//按元素值查找其在单链表中第一次出现的位置
void DispList();//输出单链表中所有的元素
int ListLength();//获取单链表的长度
bool EmpTy();//判断单链表是否为空
void ReverseList();//翻转单链表
private:
Node<T>* m_head;//头指针
int m_length;//单链表当前长度(前面有几个元素)
};
template<typename T>
LinkList<T>::LinkList()
{
m_head = new Node<T>;
m_head->next() = nullptr;
m_length = 0;
}
template<typename T>
LinkList<T>::~LinkList()
{
Node<T>* p_node = m_head->next;
Node<T>* p_tmp;
while (p_node != nullptr)
{
p_tmp = p_node;
p_node = p_node->next;
delete p_tmp;
}
delete m_head;
m_head = nullptr;
m_length = 0;
}
template<typename T>
bool LinkList<T>::ListInsert(int i, const T& e)
{
//判断插入位置i是否合理
if ((i < 1) || (i > (m_length + 1)))
{
cout << "元素" << e << "插入的位置" << i << "不合理,合理位置应为1到" << m_length+1 << "之间" << endl;
return false;
}
Node<T>* p_curr = m_head;
//for循环找到第i-1个节点,j从0开始是因为p_curr刚开始指向head标识为0,p_curr一直滑到要插入节点的上一个节点的尾指针
for (int j = 0;j < (i-1);j++)
{
p_curr = p_curr->next();//此时p_curr指向需要插入节点的上一个节点的尾指针
}
Node<T>* node = new Node<T>;
node->data = e;
node->next = p_curr->next;//让新插入的节点指向上一个节点指向的节点
p_curr->next = node;//再将上一个节点指向的内容,替换成新插入的节点
cout << "成功在位置为" << i << "处插入元素" << e << endl;
m_length++;
return true;
}
template<typename T>
bool LinkList<T>::ListDelete(int i)
{
if (m_length < 1)
{
cout << "当前单链表为空,不能删除任何数据" << endl;
return false;
}
if (i < 1 || i > m_length)
{
cout << "删除的位置" << i << "不合理,合理的位置为1到" << m_length << "的位置" << endl;
return false;
}
Node<T>* p_curr = m_head;
for (int j = 0 ; j < (i-1) ;j++)
{
p_curr = p_curr->next;//让curr指向删除节点的上一个节点
}
Node<T>* p_willdel = p_curr->next;
p_curr->next = p_willdel->next;
cout << "成功删除位置为" << i << "的元素,该元素的值为" << p_willdel->data << endl;
m_length--;
delete p_willdel;
return true;
}
template<typename T>
bool LinkList<T>::GetElem(int i, T& e)
{
if (m_length < 1)
{
cout << "当前单链表为空,不能获取任何数据" << endl;
return false;
}
if (i < 1 || i > m_length)
{
cout << "获取位置的元素位置为" << i << "不合理,合理的位置是1到" << m_length << "之间" << endl;
return false;
}
Node<T>* p_curr = m_head;
for (int j = 0; j < i;j++ )
{
p_curr = p_curr->next;
}
e = p_curr->data;
cout << "成功获取位置为" << i << "的元素,该元素的值为" << e << endl;
return true;
}
template<typename T>
int LinkList<T>::LocateElem(const T& e)
{
Node<T>* p_curr = m_head;
for (int i = 0 ;i < m_length ;i++)
{
if (p_curr->next->data == e)
{
cout << "值为" << e << "的元素第一次在在该单链表中出现的位置为" << i << endl;
return i;
}
p_curr = p_curr->next;
}
cout << "元素值为" << e << "的元素在该单链表中没有找到" << endl;
return -1;
}
template<typename T>
void LinkList<T>::DispList()
{
Node<T>* p_curr = m_head->next;
while (p_curr != nullptr)
{
cout << p_curr->data << " " << endl;
p_curr = p_curr->next;
}
cout << endl;
}
template<typename T>
int LinkList<T>::ListLength()
{
return m_length;
}
template<typename T>
bool LinkList<T>::EmpTy()
{
if (m_head->next == nullptr)
{
return true;
}
return false;
}
template<typename T>
void LinkList<T>::ReverseList() //对整个节点的翻转,并不是翻转节点中的数据
{
if (m_length <= 1)
{
cout << "顺序表中没有元素或者只有一个元素,不需要进行翻转" << endl;
return;
}
Node<T>* p_othersjd = m_head->next->next;//永远指向第二个分组的头指针,p_othersjd指向第二个节点
m_head->next->next = nullptr;//第一个节点的尾指针指向空
Node<T>* p_tmp;
while (p_othersjd != nullptr)//p_othersjd为空表示第二个分组为空
{
p_tmp = p_othersjd;
p_othersjd = p_othersjd->next;
p_tmp = m_head->next;//从分组二中将第一个节点移出,尾节点指向head的下一个节点(插入到头节点和第一个分组的第一个节点之间)
m_head->next = p_tmp;
}
}
单链表的翻转实现如图所示:
三、双链表
1.双链表的特性
与单链表相似,单链表中有一个指针域指向后继指针。双链表存在两个指针域,一个前指针域指向上一个节点,一个后指针域指向下一个节点,头指针的前指针域指向空,最后一个节点的后指针域指向空。
单链表和双链表的优缺点对比:
1.实现复杂度:双链表的实现更加繁琐,元素获取、求元素个数判断为空、链表释放等方法基本相同
2.时间复杂度:因为双链表有两个指针,可以向前索引也可以向后,查找的算法回比单链表快。单链表的平均时间复杂度为O(n),单链表有时查找时会从O(n)变为O(1)。
2.双链表的代码实现,如下:
*****************************************************
如下函数可以只用后继指针,同单链表
bool GerElem(int i,T& e);//获取第i个节点的元素值
bool LocateElem(const T& e);//按元素值查找该元素在双链表中第一次出现的位置
void DispList();//输出双链表中的所有元素
int ListLength();//获取双链表的长度
bool Empty();
*******************************************************
template<typename T>
struct DblNode
{
T data;//保存节点数据域数据
DblNode<T>* prior;//指针域前驱指针
DblNode<T>* next;//指针域后继指针
};
template<typename T>
class DblLinkList
{
public:
DblLinkList();
~DblLinkList();
public:
bool ListInsert(int i, const T& e);//在第i个节点插入指定的元素e
bool ListDelete(int i);//删除第i个节点的元素
bool GerElem(int i,T& e);//获取第i个节点的元素值
bool LocateElem(const T& e);//按元素值查找该元素在双链表中第一次出现的位置
void DispList();//输出双链表中的所有元素
int ListLength();//获取双链表的长度
bool Empty();
private:
DblNode<T>* m_head;//头指针
int m_length;
};
template<typename T>
DblLinkList<T>::DblLinkList()
{
m_head = new DblNode<T>;//创建头节点
m_head->next = nullptr;//初始头节点后继指针指向空
m_head->prior = nullptr;//初始前驱节点指针指向空
m_length = 0;//元素个数初始化为空
}
template<typename T>
DblLinkList<T>::~DblLinkList()
{
DblNode<T>* p_node = m_head->next;
DblNode<T>* p_tmp;
while (p_node != nullptr)
{
p_tmp = p_node;
p_node = p_node->next;
delete p_tmp;
}
delete m_head;
m_head = nullptr;
m_length = 0;
}
template<typename T>
bool DblLinkList<T>::ListInsert(int i,const T& e)
{
//判断插入位置i是否合理
if ((i < 1) || (i > (m_length + 1)))
{
cout << "元素" << e << "插入的位置" << i << "不合理,合理位置应为1到" << m_length + 1 << "之间" << endl;
return false;
}
DblNode<T>* p_curr = m_head;
for (int j = 0; j < (i - 1); j++)
{
p_curr = p_curr->next();//此时p_curr指向需要插入节点的上一个节点的尾指针
}
DblNode<T>* node = new DblNode<T>;
node->data = e;
node->next = p_curr->next;
node->prior = p_curr;
if (p_curr->next != nullptr) //判断插入元素后是否是最后一个元素
{
p_curr->next->prior = node;
}
p_curr->next = node;
}
template<typename T>
bool DblLinkList<T>::ListDelete(int i)
{
if (m_length < 1)
{
cout << "当前单链表为空,不能删除任何数据" << endl;
return false;
}
if (i < 1 || i > m_length)
{
cout << "删除的位置" << i << "不合理,合理的位置为1到" << m_length << "的位置" << endl;
return false;
}
DblNode<T>* p_curr = m_head;
for (int j = 0; j < (i - 1); j++)
{
p_curr = p_curr->next;//让curr指向删除节点的上一个节点
}
DblNode<T>* p_willdel = p_curr->next;
DblNode<T>* p_willdelNext = p_willdel->next;
p_curr->next = p_willdelNext->next;
if (p_willdelNext != nullptr)
{
p_willdelNext->prior = p_curr;
}
}
四、循环链表
循环链表分为单(单向)循环链表和双(双向)循环链表,只需要在原有的单链表和双链表基础上稍做改动即可,循环列表顾名思义即首尾相连的链表,单链表的尾指针不再指向空,而是指向头指针的节点,双链表的头节点前驱指针指向双链表最后一个节点,最后一个节点的尾指针指向头节点。
1.单循环列表的构造函数代码,如下:
m_head = new Node<T>;
m_head->next = m_head;
m_length = 0;
2.判断一个单循环链表是否为空,只需要判断头节点的next指针是否指向本身
单循环链表判断为空的函数代码,如下:
if(m_head->next == m_head)
{
return true;
}
else
{
return false;
}
3.输出循环单链表中的全部元素,判断条件变为while循环中,指针是否又指回了head节点,表示当前已经循环一圈。
单循环链表输出链表中全部数据的代码,如下:
Node<T>* p = m_head->next;
while(p != m_head)
{
cout << p->data << " " ;
p = p->next;
}
cout << endl;
4.单循环链表析构函数时:
循环判断条件为:while(p_node != m_head),在单链表和双链表中为while(p_bode!= nullptr)