线性表的链式存储结构
顺序表:基于顺序存储结构的线性表
链表:基于链式存储结构的线性表
顺序表与链表的差异:
时间复杂度:
对于内置基础类型,顺序表和单链表的效率不相上下
对于自定义类类型,顺序表在效率上低于单链表
插入和删除 :
顺序表:涉及大量数据对象的复制操作
单链表:只涉及指针操作,效率与数据对象无关
数据访问:
顺序表:随机访问,可直接定位数据对象
单链表:顺序访问,必须从头访问数据对象,无法直接定位
链表大家族:
-单链表:每个结点只包含直接后继的地址信息
-循环链表:单链表中的最后—个结点的直接后继为第—个结点
-双向链表:单链表中的结点包含直接前驱和后继的地址信息
-双向循环链表:双向链表中的第一个节点的直接前驱为最后一个节点,最后一个节点的直接后继为第一个节点
-十字循环链表…
![](https://i-blog.csdnimg.cn/blog_migrate/1bd57f18e4c84fc424a017906dc67607.png)
创建首结点,用于指向第一个结点,辅助数据元素的定位。
首结点仅启标识作用,并不是链表中的一个数据结点。
头结点若采用Node直接创建,存在隐患,解决方案:
#pragma pack(4)
/*
如果T中成员最大元素为8字节(含有Double),Node将以8字节进行内存对齐
下面的m_header是4字节对齐,内存布局会存在差异
retinterpret_cast转换得来的数据将会错误(内存对齐方式相同才可以强制类型转换)
所以强制使用4字节对齐
*/
struct Node : public Object
{
T value;
Node* next;
};
/*
不直接使用Node*声明(使用sizeof),是为了避开泛指类型T的构造,因为T的构造可能会发生异常
都继承自顶层父类,保证内存布局与node相同,就可以通过retinterpret_cast强制转换来使用
mutable的声明是因为const成员函数中会取m_header的地址
*/
mutable struct : public Object
{
char reserved[sizeof(T)];
Node* next;
}m_header;
#pragma pack()
插入内存布局知识:
/* 内存布局小剧场 toStudy
1.结构体的总大小,必须要是其内部最大成员的整数倍,不足的要补齐。
2.结构体或联合的数据成员,第一个数据成员是要放在offset == 0的地方,
如果遇上子成员,要根据子成员的类型存放在对应的整数倍的地址上。
3.如果结构体作为成员,则要找到这个结构体中的最大元素,然后从这个最大
成员的整数倍地址开始存储。
*/
typedef struct one {
char a;//=====> 1 -> 4
int b;//======> 4
double c;//===> 8. //max
char d;//=====> 1 -> 8 //补齐到8的整数倍
} ONE;//sum ======> 24
typedef struct two {
char array[2];//==> 2 -> 4
int b;//==========> 4
double c;//=======> 8 //max
float d;//========> 4 -> 8 //原则1
} TWO;//sum ==========> 24
typedef struct three {
char a;//=====> 1 -> 4
int b;//======> 4
double c;//===> 8
short d;//====> 2 -> 8 //原则3 ,下面是个结构体,其中最大成员为8,则需要从8的整数倍地址存放,所以变量d补齐到8
TWO e;//======> 24 (max 8)
}THREE;//sum =====> 48
LinkList
LinkList的设计要点:
使用类模板技术,通过首结点访问后继结点
定义内部结点类型Node,实现数据域与指针域
封装内存空间的申请与删除,提高可拓展性
提供游标与步长 封装move();end();next();current(),使得链表可以按步长线性访问
使用举例:for(move(0);!end();next()) list.current(); //需要先move,才可以使用
LinkList.h
#ifndef _LinkList_H_
#define _LinkList_H_
#include "include/List.h"
#include "include/Exception.h"
namespace JYlib
{
/* 单链表
创建新的堆空间,将对象的值拷贝进去,所以原对象改变与链表无关
链表中的对象改变,也不影响原对象
*/
template < typename T >
class LinkList:public List<T>
{
protected:
#pragma pack(4)
/*
如果T中成员最大元素为8字节(含有Double),Node将以8字节进行内存对齐
下面的m_header是4字节对齐,内存布局会存在差异
retinterpret_cast转换得来的数据将会错误(内存对齐方式相同才可以强制类型转换)
所以强制使用4字节对齐
*/
struct Node : public Object
{
T value;
Node* next;
};
/*
不直接使用Node*声明(使用sizeof),是为了避开泛指类型T的构造,因为T的构造可能会发生异常
都继承自顶层父类,保证内存布局与node相同,就可以通过retinterpret_cast强制转换来使用
mutable的声明是因为const成员函数中会取m_header的地址
*/
mutable struct : public Object
{
char reserved[sizeof(T)];
Node* next;
}m_header;
#pragma pack()
int m_length;
Node* m_current;//游标当前位置
int m_step;//游标步进的长度
//定位对象的前一个位置
Node* position(int i)const
{
Node* ret = reinterpret_cast<Node*>(&m_header);
for(int j = 0;j < i;j++)
{
ret = ret->next;
}
return ret;
}
//封装申请与删除 增强拓展性
virtual Node* create()
{
return new Node();
}
virtual void destroy(Node* pn)
{
delete pn;
}
public:
LinkList()
{
m_header.next = NULL;
m_current = NULL;
m_step = 1;
m_length = 0;
}
LinkList(const List<T>& t) {}
LinkList& operator =(const List<T>& e) {}
bool insert(const T& e)
{
return insert(m_length,e);
}
bool insert(int i,const T& e)
{
bool ret = (0 <= i)&&(i <= m_length);//小于等于是为了可以在末尾插入
if(ret)
{
Node* new_Node = create();
if(new_Node != NULL)
{
Node* current = position(i);
new_Node->value = e;
new_Node->next = current->next;
current->next = new_Node;
m_length++;
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException,"No memory to create DynamicList object ...");
}
}
return ret;
}
//remove时同时析构掉对象
bool remove(int i)
{
bool ret = ((0 <= i)&&(i < m_length));
if(ret)
{
Node* current = position(i);
Node* remove = current->next;
if(m_current == remove)//如果当前游标在此,则游标指向下一个位置
{
m_current = remove->next;
}
current->next = remove->next;
m_length--;
destroy(remove);//先改数据,后析构,即使析构出问题,其仍然已经不是单链表一员
}
return ret;
}
bool set(int i,const T& e)
{
bool ret = (0 <= i)&&(i < m_length);
if(ret)
{
position(i)->next->value = e;
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException,"Parameter i is invalid ...");//抛出数组越界异常
}
return ret;
}
bool get(int i,T& e)const
{
bool ret = (0 <= i)&&(i < m_length);
if(ret)
{
e = position(i)->next->value;
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException,"Parameter i is invalid ...");//抛出数组越界异常
}
return ret;
}
virtual T get(int i)const
{
T ret;
get(i,ret);
return ret;
}
int find(const T& e)const
{
int ret = -1;
int i = 0;
Node* node = m_header.next;
while(node != NULL)
{
if(node->value == e)
{
ret = i;
break;
}
else
{
i++;
node = node->next;
}
}
return ret;
}
int length(void)const
{
return m_length;
}
//游标移动到目的地址,并重设置步进值
virtual bool move(int i,int step = 1)
{
bool ret = (0 <= i)&&(i < m_length)&&(step > 0);
if(ret)
{
m_current = position(i)->next;
m_step = step;
}
//else
//{
// THROW_EXCEPTION(IndexOutOfBoundsException,"Parameter i is invalid ...");//抛出数组越界异常
//}
/*
此处不需要异常的原因为,当使用封装好的,move,next,end等操作时
如果链表里没有元素,for(move(0);!end();next()),将会直接抛出异常
那么使用这个语句前还要判断是否有元素存在 if(length() != 0)
使得本来简易的for循环使用起来容易出问题
*/
return ret;
}
//游标是否到末尾
virtual bool end()
{
return (m_current == NULL);
}
//当前游标的值
virtual T current()
{
if(!end())
{
return m_current->value;
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException,"Parameter i is invalid ...");//抛出数组越界异常
}
}
//步进至一个游标
virtual bool next()
{
int i = 0;
while((i < m_step)&&(!end()))
{
m_current = m_current->next;
i++;
}
return (i == m_step);
}
void clear()
{
while(m_header.next)
{
Node* remove = m_header.next;
m_header.next = remove->next;
m_length--;
destroy(remove);
}
}
~LinkList()
{
clear();
}
};
}
#endif
StaticLinkList
静态单链表的由来:
当长时间使用单链表对象频繁增加和删除数据元素时
堆空间会产生大量的内存碎片,导致系统运行缓慢
想法:在给定的大小空间内,进行增加或删除结点数据
实现:
在类中定义固定大小的空间(unsigned char[])
增加标记数组,标记对应的空间是否被使用
重写create和destroy函数,改变内存的分配和归还方式
在Node类中重载operator new , 用于在指定内存上创建对象
数据结点重新定义:
//使用LinkList<T>::Node访问LinkList中的Node typename说明Node是类型而不是数据
typedef typename LinkList<T>::Node Node;
//重载new操作符,因为如果是对象,仅分配地址不行,还需要调用对象的构造函数
struct SNode : public Node
{
//new(loc)type; loc为new的目标地址,type会被内置的sizeof转化
//new操作符重载,第一个是sizeof转出来的内存大小,第二个参数是存放地址
void* operator new(unsigned int size,void* loc)
{
(void)size;//不使用会报对象未使用的warning
return loc;
}
};
StaticLinkList.h
#ifndef _StaticLinkList_H_
#define _StaticLinkList_H_
#include "LinkList.h"
namespace JYlib
{
template < typename T,int N >
class StaticLinkList:public LinkList<T>
{
protected:
//使用LinkList<T>::Node访问LinkList中的Node typename说明Node是类型而不是数据
typedef typename LinkList<T>::Node Node;
//重载new操作符,因为如果是对象,仅分配地址不行,还需要调用对象的构造函数
struct SNode : public Node
{
//new(loc)type; loc为new的目标地址,type会被内置的sizeof转化
//new操作符重载,第一个是sizeof转出来的内存大小,第二个参数是存放地址
void* operator new(unsigned int size,void* loc)
{
(void)size;//不使用会报对象未使用的warning
return loc;
}
};
unsigned char m_space[sizeof(SNode) * N];//静态申请N个SNode大小的空间
int m_used[N];//标记空间是否使用
virtual Node* create()
{
SNode* ret = NULL;
for(int i=0;i<N;i++)
{
if(!m_used[i])
{
ret = reinterpret_cast<SNode*>(m_space) + i;
ret = new(ret)SNode();//在ret处调用SNode的构造函数
m_used[i] = 1;
break;
}
}
return ret;
}
virtual void destroy(Node* pn)
{
SNode* ret = reinterpret_cast<SNode*>(m_space);
SNode* psn = dynamic_cast<SNode*>(pn);
for(int i = 0;i<N;i++)
{
if(pn == ret + i)
{
m_used[i] = 0;
psn->~SNode();//调用SNode的析构函数
break;
}
}
}
public:
StaticLinkList()
{
for(int i=0;i<N;i++)
{
m_used[i] = 0;
}
}
int capacity()
{
return N;
}
~StaticLinkList()
{
this->clear();//调用父类的析构函数
}
};
}
#endif
CircleList
CircleList的设计特点:
定义内部函数last_to_first() , 用于将单链表首尾相连
注意事项:首元素的插入操作和删除操作
重新实现:清空操作和遍历操作
CircleList.h
#ifndef _CircleList_H_
#define _CircleList_H_
#include "include/LinkList.h"
namespace JYlib
{
template < typename T >
class CircleList:public LinkList<T>
{
protected:
typedef typename LinkList<T>::Node Node;
int mod(int i)const
{
return (this->m_length == 0 ? 0 : i%(this->m_length));
}
//返回尾结点
Node* last()const
{
return this->position(this->m_length);
}
//尾结点指向头结点
void last_to_first()
{
last()->next = this->m_header.next;
}
public:
bool insert(const T& e)
{
return insert(this->m_length,e);
}
bool insert(int i,const T& e)
{
i = i %(this->m_length + 1);//归一化处理
bool ret = LinkList<T>::insert(i,e);
if(ret && (i == 0))
{
last_to_first();
}
return ret;
}
bool remove(int i)
{
bool ret = false;
i = mod(i);
if(i == 0)//删除的是头结点
{
Node* remove = this->m_header.next;
if(remove == NULL)//没有元素时
{
return false;
}
else
{
this->m_header.next = remove->next;
this->m_length--;
if(this->m_length > 0)//除头结点外有结点
{
last_to_first();
if(this->m_current == remove)
{
this->m_current = this->m_current->next;
}
}
else//除头结点外无结点
{
this->m_header.next=NULL;
this->m_current=NULL;
}
this->destroy(remove);
ret = true;
}
}
else//删除非头结点
{
ret = LinkList<T>::remove(i);
}
return ret;
}
bool set(int i,const T& e)
{
return LinkList<T>::set(mod(i),e);
}
T get(int i)const
{
return LinkList<T>::get(mod(i));
}
T get(int i,const T& e)const
{
return LinkList<T>::get(mod(i),e);
}
int find(const T& e)const
{
int ret = -1;
Node* silder = this->m_header.next;
for(int i=0;i<this->m_length;i++)
{
if(silder->value == e)
{
ret = i;
break;
}
silder = silder->next;
}
return ret;
}
void clear()
{
if(this->m_length > 0)
{
while(this->m_length > 1)
{
remove(1);//remove(0)效率太低
}
if(this->m_length == 1)//首结点需要置空
{
Node* remove = this->m_header.next;
this->m_header.next = NULL;
this->m_current = NULL;
this->m_length = 0;
this->destroy(remove);
}
}
}
bool move(int i,int step = 1)
{
return LinkList<T>::move(mod(i),step);
}
bool end()
{
return (this->m_length == 0)||(this->m_current == NULL);
}
~CircleList()
{
clear();
}
};
}
#endif