逆序对是指相邻元素之间前者大于后者,即逆序对。
template<typename T>
int Vector<T>::disorder() const
{
int n=0;
for(int i=1;i<_size;i++)
n+=(_elem[i-1]>_elem[i]);
return n; 当且仅当n=0的时候,为有序向量
}
(1)唯一化,uniquify( )
1.低效率的算法
template<typename T>
int Vector<T>:uniquify()
{
int oldSize=_size;
int i=0;
while(i<_size-1)
(_elem[i]==_elem[i+1])?remove(i+1):i++;
return oldSize-_size;
}
运行时间取决于while循环,但是每次调用remove()都会出现复杂度,O(n-1)~O(1),是算数级数,最后结果是O(n2)和无序的deduplicate一样,显然是不满意的。
2.高效率算法:希望能成批的删除,而不是每次remove一个。
template<typename T>
int Vector<T>::uniquify()
{
Rank i=0,j=0;
while(++j<_size) //先加1,也就是从1开始
if(_elem[i]!=_elem[j])
_elem[++i]=_elem[j];//else相同直接返回,否则将值移动到++i
_size=++i;shrink();
return j-i; //返回删除的元素个数
}
没有显示的删除操作,只是在赋值过程,共计n-1次迭代,累计O(n)时间
(2)有序向量:二分查找binSearch( )
约定:找到不大于e的最后一个元素,如元素非常小,返回lo-1,假想左侧有哨兵lo-1位-。(有多个元素命中时,返回其中最靠后的;失败时,小于e的最大值,含哨兵lo-1)
版本A原理,减而治之,以任一元素x=S[mi]为界,将待查找区分为三部分S[lo,mi)<=S[mi]<=S(mi,hi);若e<x,则e必属于左侧子区间;若e>x,则e必属于右侧自区间,e=x,则命中。
template<typename T>
static Rank binSearch(T*A,T const& e,Rank lo,Rank hi)
{
while(lo<hi)
{
Rank mi=(lo+hi)>>1;
if(e<A[mi]) hi=mi; //如果e小于A[mi],前[lo,mi)继续查找递归
else if(A[mi]<e) lo=mi+1;// e小于A[mi],后(mi,hi)继续查找递归
else return mi;//在mi处命中
}
return -1;
}
T(n)=T(n/2)+O(1)=O(logn),底数是无所谓的,所以不用纠结啦…
查找长度search length:比较得到对于左侧的代价比右侧的代价会少些。而下面介绍的斐波那契算法则是利用这个进行优化的,黄金分割点…
(3)有序向量:斐波那契Fibonacci( ),它的平均查找长度是优于二分查找的。因为查找左侧的步数会比较低,所以偏向左侧。
斐波那契Fibonacci( ):0 1 1 2 3 5 8 13 21…
F(0) = 0,F(1)=1,F(n)=F(n-1)+F(n-2) (n≥2),显然这是一个线性递推数列,随着数列项数的增加,前一项与后一项之比越来越逼近黄金分割点0.618
若n=fib(6)-1=7,下一步分为f(5)-1、f(4)-1也就是4、2;4由可以分为2、1。
template<typenameT>
static Rankf ibSearch(T*A,Tconst&e,Ranklo,Rankhi)
{
Fibfib(hi-lo); //一个斐波那契数列
while(lo<hi)
{
while(hi-lo<fib.get())fib.prev; //找到属于的序列
Rank mi=lo+fib.get()-1; //按黄金比例分割
if(e<A[mi])hi=mi;
else if(A[mi]<e)lo=mi+1;
else
return mi;
}
}
(4)两者的分析
二分查找对应的λ为0.5,而斐波那契对应的λ为黄金分割点0.618.
(5)二分查找左右转向代价不平衡的问题,
这与二分查找的不同是mi的x涵盖到后半部分:e<x时,e必属于左侧子区间[lo,mi),若e>x时,e必属于右侧子区间[mi,hi),当hi-lo=1时才命中。因为我们的区间定义都是[lo,hi)
版本B:
template<typenameT>
static Rank(T*A,Tconst&e,Ranklo,Rankhi)
{
while(1<hi-lo) //查找区间宽度缩短为1时才能终止算法
{
Rank mi=(lo+hi)>>1; //移位操作
(if(e<A[mi]))?hi=mi:lo=mi; //判断e和A[mi]的大小
}
return(e=A[lo])?lo:-1; //返回命中元素的秩或者返回-1
}
总体而言比之前的版本A更加稳定。
语义约定:(1)当有多个命中元素时,必须返回最大秩者;(2失败时返回小于e的最大者,即是哨兵lo-1)
版本C:
template<typenameT>
static Rank(T*A,Tconst&e,Ranklo,Rankhi)
{
while(lo-hi)
{
Rank mi=(lo+hi)>>1;
(if(e<A[mi]))?hi=mi:lo=mi+1; // A[lo=hi]为不大于e的最小元素
}
return--lo; //lo-1即是不大于e的元素的最大秩
}
(6)起泡排序bubbleSort,逐趟做扫描交换,直至全序,
由于在某些条件下,前缀是有序的,就不需要逐个进行比较。
template<typenameT> void Vector<T>::bubbleSort(Ranklo,Rankhi)
{
while(!bubble(lo,hi--));//逐趟做扫描交换,直至全序
}
template<typenameT>bool Vector<T>::bubble(Ranklo,Rankhi)
{
bool sorted=true; //设初始的标志为true,即整体有序标志
while(++lo<hi)
if(_elem[lo-1]>_elem[lo])
{ //如果逆序,则交换,并且false
sorted=false;
swap(_elem[lo-1],_elem[lo]);
}
return sorted; //返回有序标志
}
继续改进:在已经进行了一段时间的比较之后,后半段已经是有序的,但是前半段有可能是无序的,如果进行之前的算法需要进行扫描O(n),这显然是浪费的,所以我们要想办法进行改进,我们后半段既然已经排序好了,我们就不进行
template<typenameT> void Vector<T>::bubbleSort(Ranklo,Rankhi)
{
while(lo<(hi=bubble(lo,hi))); //只交换某一部分
}
template<typenameT> Rank Vector<T>::bubble(Ranklo,Rankhi)
{
Ranklast=lo;
while(++lo<hi)
if(_elem[lo-1]>_elem[lo])
{
last=lo; //更新最右侧逆序的位置
swap(_elem[lo-1],_elem[lo]);
}
}
return last;
}
(7)归并排序MergeSort,之前的比较算法的上下界分别是Ω(nlogn),上界是O(n2),原理是,序列一分为二O(1),子序列递归排序,合并有序子序列。
求T(n)=2T(n/2)+O(n),其时间复杂度是O(nlogn)。
template<typenameT>
void Vector<T>::mergeSort(Ranklo,Rankhi)
{
if(hi-lo<2) return;
int mi=(lo+hi)>>1; //找到中点mi
mergeSort(lo,mi); //对前半部分排序,找到递归基
mergeSort(mi,hi); //对后部分进行排序找到递归基
merge(lo,mi,hi);
}
合并后的数据放在A中,
template<typenameT>void Vector<T>::merge(Ranklo,Rank mi,Rankhi)
{
T*A=_elem+lo; //_elem[lo,hi)=A[0,hi-lo),A为_elem中的一段
int lb=mi-lo;
T*B=newT[lb]; //B长度是lb
for(Ranki=0;i<lb;B[i]=A[i++]) //将A中的前部分元素复制给B
int lc=hi-mi;
T*C=_elem+mi; //C为A中元素的后半部分
for(Ranki=0,j=0,k=0;(j<lb)||(k<lc))
{
if((j<lb)&&((lc<=k)||(B[j]<=C[k])))A[i++]=B[j++];//比较,C完结或B 不大
if((k<lc)&&((lb<=j)||(C[k]<B[j])))A[i++]=C[k++]; //B完结或者C较小
}
}

890

被折叠的 条评论
为什么被折叠?



