向量模板类

数据结构之向量

1.简述

数据结构是数据项的结构化集合,其结构性表现为数据项之间的相互联系及作用,也可以理解为定义数据项之间的某种逻辑次序。而根据这种逻辑次序的复杂程度,大致可以将各种逻辑结构大致分为三大类。

  • 线性结构
  • 半线性结构
  • 非线性结构

最为基本的线性结构统称为序列,分为向量和列表。
- 在向量中,所有数据项的物理存放位置与逻辑次序完全吻合,此时的逻辑次序也称作秩。
- 在列表中,逻辑上相邻的数据项在物理上未必相邻,而采用间接定址的方式通过封装后的位置相互引用。

2.接口

操作接口功能适用对象
size()报告规模向量
get(r)获取秩为r的元素的数值向量
insert(r)e作为秩为r的元素向量
remove(r)删除秩为r的元素向量
disorder()判断所有元素是否已按非降序排列向量
sort()调整各元素位置,使其按非降序排列向量
find(e)查找目标元素e,返回不大于e且秩最大的元素向量
search(e)查找目标元素e,返回不大于e且秩最大的元素有序向量
deduplicate()删除重复元素向量
uniquify()删除重复元素有序向量
traverse()遍历向量并统一处理所有元素向量

3.vector模板类

#include "stdafx.h"
#include<stdio.h>
#include<iostream>
using namespace std;
typedef int Rank;
#define DEFAULT_CAPACITY 3
#define IsValid(x,lo,hi) ((x)<=hi||(x)>=lo) 
template<typename T>class vector
{
protected:
    T* _elem; int _size; int _capacity;
public:
    //构造函数
    vector(int c = DEFAULT_CAPACITY, int s = 0, T v = 0) { _elem = new T[_capacity = c]; for (_size = 0; _size < s; _elem[_size++] = v); }//容量为c,规模s,内容为v的向量
    vector(T const* A, Rank n) { copyFrom(A, 0, n); }//基于数组的整体复制
    vector(T const* A, Rank lo, Rank hi) { copyFrom(A, lo, hi); }//区间复制
    vector(vector<T>& v) { copyFrom(v._elem, 0, v._size); }//基于向量的整体复制
    vector(vector<T>& v, Rank lo, Rank hi) { copyFrom(v._elem, lo, hi); }//基于向量的区间复制
    //析构函数
    ~
        vector() {
        delete[] _elem;
    }
    //只读接口
    int size() const { return _size; }//规模
    bool empty() const { return !_size; }//判空
    int dissorted() const;//是否无序
    Rank find(T const& e, Rank lo, Rank hi) const;//区间查找
    Rank find(T const& e) const { return find(e, 0, _size); }//整体查找
    Rank Search(T const& e, Rank lo, Rank hi) const;//有序向量区间查找
    Rank Search(T const& e) const { return (0>=_size)?-1:Search(e, 0, _size); }//有序向量整体查找
    //可写访问接口
    Rank insert(T const& e, Rank r) ;//插入指定位置
    Rank insert(T const& e)  { return insert(e, _size); }//尾插入
    int remove(Rank lo, Rank hi);
    T remove(Rank r);
    T& operator[](Rank r);
    vector<T> & operator= (vector<T> const&);
    void sort(Rank lo, Rank hi);//区间排序
    void sort() { sort(0, _size); }//整体排序
    int deduplicate();//无序向量唯一化
    int uniquify();//有序向量唯一化
    template<typename VST>void traverse(VST&);//遍历
private:
    void copyFrom(T const* A, Rank lo, Rank hi);
    void expand();
    void shrink();
    Rank max(Rank lo, Rank hi);
    void selectionSort(Rank lo, Rank hi);
    bool bubble(Rank lo, Rank hi);
    void bubbleSort(Rank lo, Rank hi);
    //void insertionSort(Rank lo, Rank hi);
    void merge(Rank lo, Rank mi, Rank hi);
    void mergeSort(Rank lo, Rank hi);
    Rank partition(Rank lo, Rank hi);
    void quickSort(Rank lo, Rank hi);
};

3.2 构造与析构

向量结构在内部维护一个元素类型为T的私有数组_elem[];其容量由变量_capacity指定;有效元素的数量,则由_size变量进一步指定。因此,向量对象析构销毁数据区_elem[]即可。

3.3基于复制的构造方法

copyFrom()首先开辟待复制区间的双倍规模,然后通过一步迭代,完成元素的顺次复制。

template<typename T>
void vector<T>::copyFrom(T const* A, Rank lo, Rank hi)
{
    _elem = new T[_capacity = 2 * (hi - lo)]; _size = 0;
    while (lo < hi) { _elem[_size++] = A[lo++]; }
}

由于向量内部含有动态分配的空间,默认的运算符“=”不足以支持向量之间的赋值。比如图结构的邻接表实现,就需要二维形式的向量,其主向量中的每一元素本身都是一维向量。

template<typename T>
vector<T>& vector<T>::operator=(vector<T> const &v)
{
    if (_elem) delete[] _elem;
    copyFrom(v._elem, 0, v.size());
    return *this;
}

3.4动态空间管理

内部数组所占物理空间的容量,在向量生命周期内不允许调整,一经申请,容量已有_capacity确定,因此称为静态空间管策略。一方面,既然容量固定,总有可能在此后的某一时刻,无法加入更多的新元素——导致发生上溢。另一方面,无法提前预知需要多少预留空间来解决上溢问题。取_size/_capacity作为装填因子。
上述问题即可描述为:
- 如何才能保证向量的装填因子既不致于超过1,也不致于太接近于0

3.5扩容

template<typename T>
void vector<T>::expand()
{
    if (_size < _capacity) return;//成员未满时,不必扩容
    if (_capacity < DEFAULT_CAPACITY) _capacity = DEFAULT_CAPACITY;//不低于最小容量
    T* old = _elem; _elem = new T[_capacity <<= 1];//容量加倍
    for (int i = 0; i < _size; i++)//一趟迭代
        _elem[i] = old[i];
    delete [] old;
}

这里的关键在于,新数组的容量总是取作原数组的两倍——这正是上述问题的解。

3.5.1扩容策略

如果不选取加倍容量策略,凭借一般的思路,具体分析。
取两种策略:
- 1.每次追加固定空间
- 2.每次追加双倍空间
采取第一种扩容策略,假定每次追加C个单元,于是,只要每隔固定的常数K次操作就发生一次扩容,则初始容量为n0的向量经过连续的N(>>K)次操作后,容量将增加至:
n=n0+C(N/K)
总时间为:
T(N)=(n0+C)+(n0+2C)+….+(n0+C(N/K))=n0*(N/K)+C(N/K)(N/K-1)
均摊至单次操作:
T(N)/N=n0/K+(C/K)(N/K-1)=n/K-C/K=O(n)

考察初始容量为0的向量,假设每次追加一个单元,则扩容需要的次数恰好为几何级数,采用均摊分析,共需O(n)时间,换句话说,这种情况的确有可能发生。

而采用第二张扩容策略,每次追加双倍空间,假定数组的初始容量为某一常数n0——即将溢出。经过连续的
N(>>K)次操作后,容量将增加至:
n=2^N*n0
T(N)=n0+2n0+4n0+…+2^N*n0=n0*(2^N-1)
而所需扩容的次数,也成以2的公比的等比数列增长。故
T(n)=2N+4N+8N+…+capacity(n)<2*capacity(n)=O(n)
T(n)/n=O(1)
也就是说采用第二种扩容方式需要O(1)的时间。

3.5.2装填因子

从装填因子的角度考虑,每次加倍扩容,装填因子不会到达100%也不至低于50%。

3.6判等器与比较器

template<typename T>static bool lt(T* a,T* b){return lt(*a,*b);}
template<typename T>static bool lt(T& a,T& b){return a<b;}
template<typename T>static bool mt(T& a,T& b){return a>b;}
template<typename T>static bool equals(T* a,T* b){return equals(*a,*b);}
template<typename T>static bool equals(T& a,T& b){return a==b;}

3.7无序查找

template<typename T>
Rank vector<T>::find(T const & e, Rank lo, Rank hi) const
{
    if(IsValid(lo,0,_size)&&IsValid(hi,0,_size)){
        while ((lo < hi--) && (e != _elem[hi]));
        return hi;
    }
}

由后向前顺序查找(秩必须合法),直到发现与之相等者(即e),则查找成功。对于多个元素命中时,返回秩最大者。查找失败时,将越过边界lo,统一返回lo-1即-1。

3.8循秩访问

template<typename T>
T & vector<T>::operator[](Rank r)
{
    return _elem[r];
}

重载后的[]返回的是数组元素的引用,可以取代get()和set()操作。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值