线性表的顺序存储结构
线性表的顺序存储结构是通过一组连续的存储单元一次存储线性表的数据元素。其特点是,逻辑上相邻的数据元素,其物理次序也是相邻的。
数据的存储位置:
1.假设线性表的每个数据元素需要占用X个存储单元,并以所占的第一个单元的存储地址作为数据元素的存储起始位置。
2.则线性表中第x+1个数据元素的存储位置LOC(x+1)和第x个元素的存储位置LOC(x)之间满足该条件:LOC(x+1) = LOC(x) +1。
3.一般来说,线性表的第x个数据元素的存储位置:LOC(x) = LOC(x)+X(x-1)。
(emmmm,这个地方,我还不怎么会用markDown写这些公式。。。)
顺序结构的优缺点
优点:拥有随机访问的能力,依靠下标访问数据的时间复杂度为常数阶。其存储位置也可以用一个简单,直观的公式来表示。
缺点:在做插入或删除操作时,需要移动大量数据。其时间复杂度较高。且有着其长度固定的静态特点。可能会导致内存空间浪费或内存空间不够。
顺序存储结构类实现
/**状态枚举定义:在记录七已经定义过了*/
enum Status{
SUCCESS,FAIL,MEMORY_ALLOCATION_FAILED,ARRAY_OUT_OF_BOUNDS,OTHER_ERROR
/**枚举定义说明
* SUCCESS:操作成功
* FAIL:操作失败
* MEMORY_ALLOCATION_FAILED:内存分配失败
* ARRAY_OUT_OF_BOUNDS:数组下标越界
* OTHER_ERROR:其它错误
*/
};
/**
*顺序存储结构类声明
*/
template
class OrderList : public ListSelf{//OrderList : 顺序表类 继承 线性表抽象类(ListSelf)
private:
T * data;//数据元素首地址
size_t length;//存储长度
protected:
/**
* 构造一个线性表,申请内存
*/
Status init(){
data = new T[SIZE+1];//多申请一个空间,空余出第一个数据空间
length = 0;//长度为0
if (!data){
exit(ERROR);//如果内存申请失败,退出
}
return SUCCESS;
}
/**
* 销毁线性表,回收内存。
*/
Status destroy(){
if (!data){
return FAIL;
}
delete [] data;
return SUCCESS;
}
public:
/**
* 默认的构造函数
*/
OrderList(){
init();//调用init()函数,构建线性表
}
/**
* 析构函数
*/
~OrderList(){
destroy();//回收内存
}
/**
* 在相应的位置上插入新的数据
*/
Status insert(T elem,size_t index){
if (length == SIZE){ //判断满
return FULL;//返回满
}
if(index < 1 || index > length+1 ){//判断下标是否合法
return ARRAY_OUT_OF_BOUNDS;//返回下标越界错误
}
size_t i = 0;
for (i = length; i >= index; --i){//将数据往后面移动
data[i+1] = data[i];//将数据往后移动
}
data[i+1] = elem;//插入数据
++length;//长度增加
return SUCCESS;
}
/**
* 在线性表的末尾插入新的数据
*/
Status insert(T elem){
if (length == SIZE){//判断是否空间是否为满
return FULL;
}
data[++length] = elem;//放入数据
return SUCCESS;
}
/**
* 取出线性表的数据元素
*/
T at(size_t index){
if(index < 1 || index > length){
throw std::out_of_range("Array out of bounds");//返回下标越界错误
}
return data[index];
}
/**
* 返回数据的索引下标
*/
int local(T elem) {
//安排哨兵
data[0] = elem;
size_t i= 0;
for(i = length;data[i]!=elem;--i);//查询位置。数据相等就退出。
if (i == 0){
return -1;//查找失败,返回-1
}
return i;//返回位置
}
/**
* 修改指定位置的数据
*/
Status updateIndex(T newElem,size_t index){
if(index < 1 || index > length ){
return ARRAY_OUT_OF_BOUNDS;//返回数组下标错误
}
data[index] = newElem;//修改数据
return SUCCESS;//返回成功
}
/**
* 修改匹配的数据
*/
Status update(T oldElem,T newElem){
int index = local(oldElem);//先查询老数据的位置
if (index == -1){
return FAIL;//如果没有查询到,就修改错误
}
data[index] = newElem;//更新数据
return SUCCESS;//返回成功
}
/**
* 在相应的位置上删除数据
*/
Status removeIndex(size_t index){
if(index < 1 || index > length ){
return ARRAY_OUT_OF_BOUNDS;//返回数组下标错误
}
for (size_t i = index; i <= length; ++i){//数据往前面移动,覆盖掉原来的数据
data[i] = data[i+1];
}
--length;//长度减一
return SUCCESS;
}
/**
* 移除线性表中相同的元素
*/
Status remove(T elem){
int index = local(elem);//先查询数据的位置
if(index == -1){
return FAIL;//没有数据就返回假
}
for (size_t i = index; i < length; ++i){//同removeIndex()
data[i] = data[i+1];
}
--length;
return SUCCESS;
}
/**
* 返回查找元素的直接前驱
*/
T * prior(T elem){
if (data[0] == elem){
return NULL;//第一个元素没有直接前驱
}
int index = local(elem);
if(index == -1){
return NULL;//没有前驱返回空
}
return &data[index-1];
}
/**
* 返回查找元素的直接后驱
*/
T * next(T elem){
if (elem == data[length]){
return NULL;//最后一个数据没有
}
int index = local(elem);
if (index == -1){
return NULL;
}
return &data[index+1];
}
/**
* 返回线性表中元素的个数
*/
size_t size(){
return length;
}
/**
* 将线性表重置为空表,清空所有数据
*/
void clear(){
length = 0;//直接长度清空即可
}
/**
* 判断线性表是否为空表,如果是空表,返回true,否则返回false
*/
bool isEmpty(){
return length==0;
}
/**
* 遍历线性表
*/
Status show(){
for (size_t i = 1; i <= length; i++){
std::cout<<"data["<
STL(Standard Template Library)标准模板库中的顺序表
Array
Array是C++11新添加的一种容器。是一种固定大小的容器。容器中包含着以严格的线性序列的排列的数据元素。 并且不通过分配器管理其内存的分配。其编译是常数阶,没有格外的内存和时间开销。
(Array的使用推荐博客:c++ array模板类使用,这里是分析源码。)
Array源码分析(gcc version 8.1.0 (x86_64-win32-sjlj-rev0, Built by MinGW-W64 project)
(我是直接从里面拷出来看的=-=)
__array_traits结构体,是作为存储用的。Array的底层依旧是一个数组。
当Array的大小为O时,底层是一个空的结构体。
Array是一个结构体。里面重命名了大量的数据类型。
fill函数实现
void
fill(const value_type& __u)
{ std::fill_n(begin(), size(), __u); }//调用了fill_n的函数
//fill_n函数:https://www.cnblogs.com/Emilylice/p/7754700.html
swap函数实现
void
swap(array& __other)
noexcept(_AT_Type::_Is_nothrow_swappable::value)
{ std::swap_ranges(begin(), end(), __other.begin()); }//调用了swap_ranges函数。
swap_ranges
template
_ForwardIterator2
swap_ranges(_ForwardIterator1 __first1, _ForwardIterator1 __last1,
_ForwardIterator2 __first2)
{
// concept requirements
__glibcxx_function_requires(_Mutable_ForwardIteratorConcept<
_ForwardIterator1>)
__glibcxx_function_requires(_Mutable_ForwardIteratorConcept<
_ForwardIterator2>)
__glibcxx_requires_valid_range(__first1, __last1);
for (; __first1 != __last1; ++__first1, (void)++__first2)
std::iter_swap(__first1, __first2);//实践上是遍历原迭代器,然后交换值。
return __first2;//返回交换后的数据
}
data函数的实现
_GLIBCXX17_CONSTEXPR pointer
data() noexcept
{ return _AT_Type::_S_ptr(_M_elems); }//直接调用__array_traits结构体中的指针函数。
//取出当前指针。_M_elems是底层的数组或者空结构体。
_GLIBCXX17_CONSTEXPR const_pointer
data() const noexcept//返回常量的data()
{ return _AT_Type::_S_ptr(_M_elems); }
begin,end等迭代器实现
// Iterators.
_GLIBCXX17_CONSTEXPR iterator
begin() noexcept
{ return iterator(data()); } //调用data(),将其转换为数据类型的指针。
//=-=假设我们是array。那么T就是int类型的。data()返回的就是int *的。然后再转为int *类型,
//emmm,我测试了一下,好像也是int *;好像是强制转换了T *类型的了。
_GLIBCXX17_CONSTEXPR const_iterator
begin() const noexcept
{ return const_iterator(data()); }
_GLIBCXX17_CONSTEXPR iterator
end() noexcept
{ return iterator(data() + _Nm); }//end()函数则是调用data()取得底层数组的首地址。
//然后再加上长度。返回就是尾部地址。然后再转换一下。就得到T *了。
(需要注意的是!!data()+_Nm后,其地址不包含了数据了。也就是data()+_Nm-1才是最后的尾数据。)
_GLIBCXX17_CONSTEXPR const_iterator
end() const noexcept
{ return const_iterator(data() + _Nm); }
at,front,back等取值函数的实现
/*****************************************at()******************************/
(首先是传入了下标,然后利用三元运算法,将下标和数组长度进行比较。
如果传入的下标比数组长度要长度要小,就调用__array_traits结构体中引用函数,返回其数据的引用。
否则,抛出异常。)
constexpr const_reference
at(size_type __n) const
{
// Result of conditional expression must be an lvalue so use
// boolean ? lvalue : (throw-expr, lvalue)
return __n < _Nm ? _AT_Type::_S_ref(_M_elems, __n)
: (std::__throw_out_of_range_fmt(__N("array::at: __n (which is %zu) "
">= _Nm (which is %zu)"),
__n, _Nm),
_AT_Type::_S_ref(_M_elems, 0));
}
/*****************************************front()******************************/
_GLIBCXX17_CONSTEXPR reference
front() noexcept
{ return *begin(); }//直接调用begin().*begin()就是直接返回值。
//而reference是数据的引用。所有front()其实就是返回数据的引用。
constexpr const_reference
front() const noexcept
{ return _AT_Type::_S_ref(_M_elems, 0); }//如果返回的是一个常量的话,
//是直接调用__array_traits结构体中引用函数返回的。
/*****************************************back()******************************/
_GLIBCXX17_CONSTEXPR reference
back() noexcept
{ return _Nm ? *(end() - 1) : *end(); }//这是一个三元运算符。如果你给array的长度为0的话,
//就直接返回end()的值。我测试了一下, array arr;cout<
size,max_size,empty等函数的实现
/*****************************************size()******************************/
// Capacity.
constexpr size_type
size() const noexcept { return _Nm; }//直接返回定义的大小。
//所以,size()不是返回当前使用的数据元素长度。
/*****************************************max_size()******************************/
constexpr size_type
max_size() const noexcept { return _Nm; }//和size一样的。
/*****************************************empty()******************************/
constexpr bool
empty() const noexcept { return size() == 0; }//这个,只有你定义的长度不是0的话。
//这个就是恒定为假了。哪怕你没有使用。
总结
顺序表其实和数组并没有太多的差异。我们其实完全可以使用数组来完成顺序表的功能。但是,依旧推荐Array容器来代替数组。因为其用有迭代器以及at()等方法。比数组更加安全,也更加方便。需要注意的是,Array是C++11新添的。所以需要C++11版本及其以下版本。
个人后话
第一次看源码,=-=还有点看不懂呀。但是我会坚持下去。本来今天打算把动态顺序表也一起放进去的。可是,已经晚上9点了。。。。还是算了(明天再放吧)。本来昨天还说晚上不写代码的。结果还是写了。完蛋了。
修改
2020年2月4日 忘记了size_t是不可能有负数的去了,在申请内存那个地方不需要判断是否为小于零,而且小于零也写错了。=-=太不应该了。并且考虑到因为是泛型编程GP。不应该在抽象类中使用sort的。因为如果T是一个类的对象,那么又该怎么排序呢?所以在代码中去掉了sort()函数。且记录七 线性表中的顺序结构抽象类会去掉sort()函数。在此声明。
修改前的代码:
/**
* 构造一个线性表,申请内存
*/
Status init(){
if (SIZE < 0){
data = new T[SIZE+1];//多申请一个空间,空余出第一个数据空间
}
data = new T[SIZE+1];//多申请一个空间,空余出第一个数据空间
length = 0;//长度为0
if (!data){
exit(ERROR);//如果内存申请失败,退出
}
return SUCCESS;
}
修改后的代码:
/**
* 构造一个线性表,申请内存
*/
Status init(){
data = new T[SIZE+1];//多申请一个空间,空余出第一个数据空间
length = 0;//长度为0
if (!data){
exit(ERROR);//如果内存申请失败,退出
}
return SUCCESS;
}