向量模板类

时间:2025-03-10 20:32:04

数据结构之向量

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() 遍历向量并统一处理所有元素 向量

模板类

#include ""
#include<>
#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, ());
    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()操作。