《数据结构邓版》学习总结---第二章向量Vector
《数据结构邓版》学习总结—第二章向量
转载请注明出处,大家共同学习共同进步!
sequence序列中向量vector和列表list;
由数组至向量;向量是线性数组的一种抽象和泛化。
向量的秩rank与元素一一对应。vector[rank]。与数组下标类似
向量ADT接口,引用邓PPT;
vector模板类程序示例
typedef int Rank;
#define DEFAULT_CAPACITY 3;
tempalte<typename T>
class Vector
{
private:
Rank _size;int _capacity;T* _elem;//元素个数,向量容量,存放地址
protected:
void copyForm(T* const A,int lo,int hi);//复制数组区间A[lo,hi]
void expand();//二倍扩容
void shrink();//缩容
bool bubble(Rank lo,Rank hi);//扫描交换算法,冒泡排序内层循环
void bubbleSort(Rank lo,Rank hi);//冒泡排序算法
void merge(Rank lo,Rank mi,Rank hi);//归并算法
void mergeSort(Rank lo,Rank hi);//并归排序算法
void partition(Rank lo,Rank hi);//轴点构造算法
void quickSort(Rank lo,Rank hi);//快排算法
void headSort(Rank lo,Rank hi);//堆排序
public:
//构造函数
Vector(int c=DEFAULT_CAPACITY){_elem=new T[_capacity=c];_size=0}//默认
Vector(T* A,Rank lo,Rank hi){copyForm(T* A,lo,hi);}//数组
Vector(T* A,Rank n){copyForm(T* A,0,n);}
Vector(Vector<T> const& V,Rank lo,Rank hi){copyForm(V._elem,lo,hi);}//拷贝构造
Vector(Vector<T> const& V){copyForm(V._elem,0,V._size);}
//析构
~Vector(){delete [] _elem;}
//只读访问接口
Rank size()const{return _size;}//规模
bool empty()const{return _size<=0;}//判断向量是否为空
int disordered()const;//判断向量是否已排序
Rank find(T const& e)const{return find(e,0,(Rank)_size);}//无序向量 全局查找
Rank find(T const& e,Rank lo,Rank hi)const;//无序向量 区间查找
Rank search(T cosnt& e)const//有序向量 全局查找
{return (0>=_size)?-1:search(e,(Rank)0,(Rank)_size);}
Rank search(T const& e,Rank lo.Rank hi)cosnt;//有序向量 区间查找
//可写访问接口
T& operator[](int r)cosnt;//重载[]运算符
Vector<T>& operator=(Vector<T> cosnt&);//重载=
T remove(Rank r);//删除秩为r的元素
int remove(Rank lo,Rank hi);//删除区间内所有元素
Rank insert(Rank r,T const& e);//在r处插入T类型元素e
Rank insert(T const& e){return insert(_size,e);}//默认插入在末尾处
void sort(Rank lo,Rank hi);//[lo,hi]区间内排序
void sort(){sort(0,_size)}//全局排序 默认非降
void unsort(Rank lo,Rank hi);//局部置乱
void unsort(){unsort(0,_size)}//默认全局置乱
int deduplicate();//无序向量剔除重复元素
int uniquify();//有序向量剔除重复元素
//遍历
void traverse(void (*)(T&));//使用函数指针遍历,只读或局部修改
template <typename VST> void traverse(VST&); //使用函数对象遍历,可全局性修改
};//Vector
基于复制的构造方法实现示例:
template<class T>//class与typename相同
void Vector<T>::copyForm(T* const A,Rank lo.Rank hi)//复制函数
{
_elem=new T[_capacity=2*(hi-lo)];_size=0;//分配2倍空间扩容,向量元素个数清零
while(lo<hi)//
_elem[_size++]=A[lo++];//复制A数组元素至_elem地址的向量
}
由于编译器默认的运算符“=”不足以支持向量之间的相互赋值。
重载运算符“=”实现示例:
template<class T>//class与typename相同
Vector<T>& Vector<T>::operator=(Vector<T> const& V)//重载运算符“=”
{
int n=V.size();//确认V向量的规模大小
if(_capacity<n)//若当前向量的容量小于n,则需要扩展
{delete [] _elem;_elem=new T[_capacity=2*n];}//删除后重新动态分配空间
copyForm(V._elem,0,n);//调用 复制函数进行复制。
return *this;//返回当前对象的指针
}
当向量规模不足,则需要通过内部数组动态扩容算法expand(),通常都采用2倍扩容
template <typename T>
void Vector<T>::expand(){//二倍扩容函数
if(_size<_capacity) return;//若规模未超过容量,则不扩容
if(_capacity<DEFAULT_CAPACITY) _capacity=DEFAULT_CAPACITY;//若容量小于默认设置的容量,取最大
T* oldElem=_elem;_elem=new T[_capacity<<=1];//将原数据地址名更换oldElem,动态内存分配new分配新空间,给_elem,且_capacity右移一位,实现加倍扩容。
for(int i=0;i<_size;i++)
_elem[i]=oldElem[i];//复制原向量内容
delete [] oldElem;//释放oldElem空间(即原_elem空间),new与delete搭配使用哦!!!
}//expand---o(n),分摊运行时间o(1)
分摊复杂度:在使用同一算法对同一数据结构连续实施足够多次操作过程中,总体所需的计算时间分摊至其间所有操作的平均值。
当装填因子很低时,数组发生下溢underflow,此时有必要适当的缩减内部数组的容量,shrink()算法
template <typename T>//元素类型T
void Vector<T>::shrink(){//缩容
if(_capacity<DEFAULT_CAPACITY<<1) return;//若容量减倍后 会小于默认容量,则不执行缩容。
if(_capacity<_size<<2) return;//若容量小于4倍的规模,不执行,25%界限。 size>25%capa
T* oldElem=_elem;elem=new T[_capacity>>=1];
for(int i=0;i<_size;i++)
_elem[i]=oldElem[i];//复制原向量内容
delete [] oldElem;//释放oldElem空间(即原_elem空间),new与delete搭配使用哦!!!
}//shrink() 效率略低,并不适用于对每次操作的执行速度极其敏感的应用场合
为实现与数组同样的循秩访问,重载操作符"[]"
template <typename T>
T& Vector<T>::operator[](int r) const//重载下标操作符,以便沿用数组访问方式
{return _elem[r];}//assert:0<=r<size assert是用于检验条件真假与否!!!
置乱器,置乱算法,从置乱区间末元素开始逆序向前扫描,使用rand()函数,将当前元素与rand()的任一元素交换。
template <typename T>
void permute(Vector<T>& V){//封装于ADT
for(int i=V.size(),i>0;i--)//自后向前思路很好
swap(V[i-1],V[rand()%i]);//将V[i-1]与rand()的任一元素V[0,i-1]交换
}//permute
区间置乱接口—对外
template <typename T>
void Vector<T>::unsort(Rank lo,Rank hi){//置乱区间_elem[lo,hi)
T*V=_elem+lo;//将_elem[lo,hi),视为V[0,hi-lo)
for(Rank i=hi-lo,i>0;i--)//自后向前扫描
swap(V[i-1],V[rand()%i]);//将V[i-1]与rand()的任一元素V[0,i-1]交换
}//unsort,调用方式Vector A;A.unsort(lo,hi)
判等器与比较器
元素的内部类型可能是指向其他对象的指针,而从外部更多关注的往往是指对象的大小。若实参为指针类型,则调用函数内的lt,取地址指向数据进行比较,若数据本身就是T类型,用引用调用就ok!!!
template <typename T> static bool lt(T* a,T* b){return lt(*a,*b);}//less than
template <typename T> static bool lt(T& a,T& b){return a<b;}//less than
template <typename T> static bool eq(T* a,T* b){return eq(*a,*b);}//equal
template <typename T> static bool eq(T& a,T& b){return a==b;}//equal
无序向量的查找算法:
顺序查找sequential search,遍历:依次逐个比对
//无序向量的顺序查找算法:返回最后一个等于e的元素的位置,失败则返回lo-1;
template <typename T>
Rank Vector<T>::find(T const& e,Rank lo,Rank hi){//assert:0<=lo<hi<=_size
while((lo<hi--)&&(_elem[hi]!=e))//自后向前依次查找
return hi;//当lo>hi,失败。
}//find 复杂度,遍历, 线性o(n)
插入算法
每插入一个元素,其后继元素都要后移一位
//将数据e插入至 秩为r的位置
template <typename T>
Rank Vector::insert(T const& e,Rank r){//
expand();//若有必要,扩容
for(int i=_size;i>r;i--)//自后向前
_elem[i]=_elem[i-1];//后继元素顺次后移一个单元
_elem[r]=e;//置入新元素
_size++;//更新规模
return r;//返回秩
}//insert O(n)
删除算法
每删除一个元素,其后继元素都要前移一位。
区间删除算法实现:
//删除区间[lo,hi)
template <typename T>
int Vector::remove(Rank lo,Rank hi){//删除区间[lo,hi)
if(lo==hi) return 0;//退化现象,无元素可删除
while(hi<_size)//以hi之后所有元素均移动完成为终止条件
_elem[lo++]=_elem[hi++];//后继元素顺次后移一个单元
_size=lo;//更新规模
return hi-lo;//返回删除区间的长度
}//remove O(n)
可根据区间删除算法,完成单个元素删除功能
//删除秩为r的元素
template <typename T>
int Vector::remove(Rank r){//
if(_elem[r]==NULL) return 0;//若不存在,不删除
T e=_elem[r];//备份
remove(r,r+1);//调用区间删除大法 删除r处元素
return e;//返回删除秩为r的元素
}//remove O(n)
在某些场合可能要对向量进行处理要求 向量元素互异,即唯一性。
唯一性算法:自前向后逐个比对,删除重复
template <typename T>
int Vector::deduplicate(){
int oldSize=_size;
Rank i=1;
while(i<_size){
Rank j=find(_elem[i],0,i)//在[0,i)区间内找与_elem[i]相同元素
(j<0)?++i:remove(j);//判断j,若<0,则无重复,否则,有重复并删除
}//while
return oldSize-_size;//返回删除个数
}//deduplicate O(n^2)
遍历接口traverse()
两种实现方式:自学补充知识体会不同!!!
//遍历向量,对各节点实施visit操作
//函数指针机制
template <typename T>
void Vector::traverse(void (*visit)(T&))//以函数指针为形参调用,只读或局部性修改
{for(int i=0;i<_size;i++) visit(_elem[i];)}//
//函数对象机制
template <typename T>
template <typename VST>
void Vector::traverse(VST& visit)//以函数对象为形参调用,全局性修改
{for(int i=0;i<_size;i++) visit(_elem[i];)}//
有序向量的接口函数实现
首先判断有序性,提出相邻逆序对概念,即在秩i<=i+1,若a[i]>=a[i+1],则构成一对逆序对。
template <typename T>//类型
int Vector<T>::disordered() const{//有序性判断,返回相邻逆序对总对数
int n=0;//计数器
for(int i=1;i<_size;i++)//从第二个数开始遍历
if(_elem[i-1]>_elem[i]) n++;//若构成相邻逆序对。加1
return n;
}//disordered
唯一性,与无序向量意义相同。不能出现重复元素。
依次比较删除,过于低效。
采用:当识别后一个比当前元素大的元素位置时,删除之间区间。
//有序向量重复元素剔除算法
template <typename T>
int Vector<T>::uniquify(){//唯一性:剔除重复元素,一次识别,多次删除
Rank i=0,j=0;
while(++j<_size)//从1开始遍历至结束
if(_elem[i]!=_elem[j])//若发现i之后的第一个不等元素
elem[++i]=_elem[j];将i++秩的元素换为j的元素
_size=++i;//i为秩,_size为规模,规模=最后1个秩的值+1
return j-i;//返回被删除元素的个数。向量规模变化量
}//uniquify
//思路个人实现版本!!!哈哈哈
template <typename T>
int Vector<T>::uniquify(){//唯一性:剔除重复元素,一次识别,多次删除
Rank i=0,j=1;
while(j<_size)//从1开始遍历至结束
if(_elem[i]!=_elem[j])//若发现i之后的第一个不等元素
elem[++i]=_elem[j++];将i+1秩的元素换为j的元素,并将j+1
_size=++i;//i为秩,_size为规模,规模=最后1个秩的值+1
return j-i;//返回被删除元素的个数。向量规模变化量
}//uniquify
查找接口:
template <typename T>
Rank Vector<T>::search(T const& e,Rank lo,Rank hi){//
if(rand()%2)//随机选择查找方式
return binSearch(_elem,e,lo,hi);
else
return fibSearch(_elem,e,lo,hi);
}
二分法查找思路:寻找秩中点mi,比较查找值在两个区间中哪一个后,再对此区间二分,依次重复直至查找成功或失败。
成功返回当前元素的秩,失败返回不大于查找值的最大秩。
实现示例:
//二分查找A:每次迭代一次判断。但成功查找也不能提前终止。 在多个命中元素时,不能保证返回最靠后值的秩。且查找失败返回-1。
template <typename T>//元素类型
static Rank binSearch(T* A,T const& e,Rank lo,Rank hi){//在A[lo,hi)中查找e
while(lo<hi-1){//循环hi-1-lo次
Rank mi=(lo+hi)>>1;//右移一位 除2
e<A[mi]?hi==mi:lo==mi;//判断在左/右区间
}//while
return e==A[lo]?lo:-1;//成功,返回lo。失败返回-1
}//binSearch
若考虑 在多个命中元素时,返回最靠后值的秩。且查找失败返回不大于查找值的最大秩。
//二分查找B:若考虑 在多个命中元素时,返回最靠后值的秩。且查找失败返回不大于查找值的最大秩。
template <typename T>//元素类型
static Rank binSearch(T* A,T const& e,Rank lo,Rank hi){//在A[lo,hi)中查找e
while(lo<hi){//循环hi-lo次,hi=lo跳出
Rank mi=(lo+hi)>>1;//右移一位 除2
e<A[mi]?hi==mi:lo==mi+1;//判断在左[lo,mi)/右[mi+1,hi)区间;
}//while
return --lo;//成功,返回--lo。失败返回--lo;//循环结束时,lo为大于e的元素的最小秩,则lo-1为不大于e的元素的最大秩。因此 不论成功与否都返回lo-1,此返回值便于插入删除操作!!!
}//binSearch
斐波那契查找:与二分思路相同,只是分界点不是中点了,而是斐波那契对应的数。
//三个分支,可提前终止,在多个命中元素时,不能保证返回最靠后值的秩。且查找失败返回-1
template <typename T>//元素类型
static Rank fibSearch(T* A,T const& e,Rank lo,Rank hi){//在A[lo,hi)中查找e
Fib fib(hi-lo);//
while(lo<hi){//循环hi-lo次,hi=lo跳出
while(hi-lo<fib.get()) fib.pred();//当长度小于斐波那契值,不足以划分两块时,选用前一个更小的斐波那契数值。
Rank mi=lo+fib.get()-1;//(fib(k)-1)-(fib(k-1)-1)-1=(fib(k-2)-1);长度为fib(k)-1;
if(e<A[mi]) hi==mi;//判断在左[lo,mi)/右[mi+1,hi)区间;//深入前半段查找
else if(e>A[mi])lo==mi+1;//深入后半段查找
else return mi;//准确命中
}//while
return -1;//失败
}//fibSearch
向量的排序器:
向量的排序函数接口
//随机选择排序方式
template <typename T>//
void Vector::sort(Rank lo,Rank hi){//_elem[lo,hi)排序
switch(rand()%4){//随机选择
case 1: bubbleSort(lo,hi);break;//起泡排序
case 2: mergeSort(lo,hi);break;//归并排序
case 3: heapSort(lo,hi);break;//堆排序,在堆 章节讲述
default:quickSort(lo,hi);break;//快速排序,在 排序章节讲述
}//switch
}//sort
起泡排序思想:1.逐个比对相邻元素是否为逆序对,若是,交换,否则,不动。每次执行都将一个最大元素冒出。2.之后的比对不再考虑已冒出元素。逐渐缩小比对元素的区间,直至全序!
稳定排序算法,O(n2);
//思路如上,swap交换函数此处不再示例;
template <typename T>
void Vector<T>::bubbleSort(Rank lo,Rank hi){ //起泡排序
{ while(!bubble(lo,hi--)); }//bubbleSort//每次循环后都会排序出一个当前最大元素
//起泡程序
template <typename T>
void Vector<T>::bubbleSort(Rank lo,Rank hi){//起泡
bool sorted=true;//全序标志符-默认全序
while(++lo<hi){//从1开始
if(_elem[lo-1]>_elem[lo]){
sorted=false;
swap(_elem[lo-1],_elem[lo]);
}//if
}//while
return sorted;
}//bubble
//个人思路:for循环实现,可能复杂度考虑不如上一种!
template <typename T>
void Vector<T>::bubbleSort(Rank lo,Rank hi){
int n=hi-lo;//确定排序区间长度
while(--n){//确定最大循环次数n-1次
for(int i=lo;i<--hi;i++){//每次循环比较排序,每次长度-1
if(_elem[i]>_elem[i+1]){
swap(_elem[i],_elem[i+1]);//比较与交换
}//if
}//for
}//while
}//bubbleSort
向量的归并排序:两个有序向量的合并。归并排序O(nlogn); 稳定
merge的思想:1.将向量长度均分2段,对每一段单独排序。(可递归至长度为1的向量段。) 2.对每一段的最前元素比较大小,将最小的弹出并置入有序序列。3.依次比较弹出,直至排序完成。
归并排序的主体结构属于典型的分而治之策略
//归并排序的主体结构属于典型的分而治之策略
template <typename T>//元素类型
void Vector<T>::mergeSort(Rank lo,Rank hi){//
if(hi-lo<2) return;//退化现象,单元素必然有序啊啊啊!!!
int mi=(hi+lo)>>1;//左移一位代表除2
//递归,继续实行分治策略
mergeSort(lo,mi);//左区间归并排序
mergeSort(mi,hi);//右区间归并排序
//区间归并
merge(lo,mi,hi);//归并
}//mergeSort
********************************************
//之后分析merge归并实现算法
//当前向量以mi为界,为两个有序向量[lo,mi),[mi,hi)。归并完成一个完整的有序向量[lo,hi)
template <typename T>//元素类型
void Vector<T>::merge(Rank lo,Rank mi,Rank hi){//
T* A=_elem+lo;//A[0,hi-lo)=_elem[lo,hi);将秩为lo的元素的地址赋予A。
int lb=mi-lo; T* B=new T[lb];
for(int i=0;i<lb;i++) B[i]=B[lo++];//复制A的左区间至B
int lc=hi-mi; T* C=_elem+mi; //A的右区间更名为C//不需要为右区间再分配内存存放;即C与A的右区间用同一段内存单元
//比较B,C并进行合并
Rank i,j,k;i=j=k=0;
while(j<lb&&k<lc){//反复比较
while(j<lb&&B[j]<=C[k]) A[i++]=B[j++];//将小者续在A后;//此处曾考虑过if,也可使用
while(k<lc&&C[k]<B[j]) A[i++]=C[k++];//此处对原处的=去除,个人考虑,因B在前,若有相同也是B中为先,因此出现相同就将B的元素先置于A
}//while
while(j<lb) A[i++]=B[j++];//若只剩B元素,则将其剩余元素依次续在A后。因为C与A右其实同内存单元,即使此时k<lc;也只是C的部分有序元素不会移动位置,也就是A的右区间的部分元素不移动罢了!!!不需要提出while(k<lc) A[i++]=C[k++];
delete [] B;//排序后释放分配至B的空间!new与delete形影不离~
}//merge
学习总结—第二章向量Vector!
所有程序示例截图均来自邓老师数据结构课程及教材!!!
望诸君共勉~!