最近主要学习了排序的相关知识,这里我做一个大概的整理,以便于以后的参阅。
第一部分:STL中的sort排序
在STL中,排序是通过使用函数模板sort来完成的。这里给出一些使用的例子
sort(v.begin(),v.end());//整个容器V按非降序排列
sort(v.begin(),v.end(),greater<int>());//将整个容器降序排列
sort(v.begin(),v.begin()+(v.end()-v.begin())/2);//这里将容器的前半部分按升序排列
最简单的排序算法之一是插入算法(insertion sort)。插入排序又N-1趟排序组成。对于p=1到N-1趟,插入排序保证从位置0到位置p为已排序状态。插入排序利用了这样的事实:位置0到位置p-1上的元素是已经拍过序的。例如
初始状态 | 34 | 8 | 64 | 51 | 32 | 21 | 移到的位置 |
after p=1 | 8 | 34 | 64 | 51 | 32 | 21 | 1 |
after p=2 | 8 | 34 | 64 | 51 | 32 | 21 | 0 |
after p=3 | 8 | 34 | 51 | 64 | 32 | 21 | 1 |
after p=4 | 8 | 32 | 34 | 51 | 64 | 21 | 3 |
after p=5 | 8 | 21 | 32 | 34 | 51 | 64 | 4 |
这里给出代码:
<span style="font-size:18px;">template<typename comparable>
void insertionSort(vector<comparable>&T)
{
int j;
for(int p=1;p<a.size();p++)//注意这里没有从0开始
{
comparable tmp=a[p];
for(j=p;j>0&&tmp<a[j-1];j--)
a[j]=a[j-1];
a[j]=tmp;
}
}
注:在这里给出一些简单排序算法的下界
定理1:N个互异元素的数组的平均逆序数是N(N-1)/4
定理2:通过交换相邻元素进行的排序的任何算法平均需要O(N^2)时间
第三部分:谢尔排序
谢尔排序(shellsort)的名称来源于它的发明者Donald Shell,该算法是冲破二次时间屏障的第一批算法之一。谢尔排序使用一个序列h1,h2,h3,..,hn,叫做增量序列(increment sequence)。只要h1=1,任何增量序列都是可行的(实际上是插入排序),不过,有些增量序列比一些增量序列更好。在使用增量h后,所有相隔h的元素均被排序(类比于插入排序,其间隔为1,而这里间隔为好),这里给出例子
初始状态 | 81 | 94 | 11 | 96 | 12 | 35 | 17 | 95 | 28 | 58 | 41 | 75 | 15 |
5排序之后 | 35 | 17 | 11 | 28 | 12 | 41 | 75 | 15 | 96 | 58 | 81 | 94 | 95 |
3排序之后 | 28 | 12 | 11 | 35 | 15 | 41 | 58 | 17 | 94 | 75 | 81 | 96 | 95 |
1排序之后 | 11 | 12 | 15 | 17 | 28 | 35 | 41 | 58 | 75 | 81 | 94 | 95 | 96 |
这里给出增量序列为1,2,4,...,size()/2的代码实现
template<typename comparable>
void shellSort(vector<comparable>&a)//这里可以类比下插入排序,除了递增序列外几乎差不多的
{
for(int gap=a.size()/2;gap>0;gap/=2)
for(int i=gap;i,a.size();i++)
{
comparable tmp=a[i];
int j=i;
for(;j>=gap&&tmp<a[j-gap];j-=gap)
a[j]=a[j-gap];
a[j]=tmp;
}
}
第四部分:堆排序
回忆之前建立N个二叉堆的基本方法,这个阶段花费O(N)的时间。然后执行N次deleteMin操作。按照这个顺序,最小的元素先离开堆。通过这些元素记录到第二个数组然后再将第二个数组拷贝回来,我们将得到N个数组的排序。对于每个deleteMin才做花费O(N)的时间,因此总的运行时间是O(NlogN)。当然我们可以必然使用第二个数组。在我们实现方法中将使用一个max堆,且每一件事都是在数组之中完成的。第一步是以线性时间建立堆。然后通过将堆中的最后元素与第一个元素交换,缩小堆得大小并进行下滤,来进行N-1次的deleteMax操作。当算法终止时,数组则以所排的顺序包含这些元素。
执行堆排序的代码将在下面给出。稍微有些复杂的是,不想二叉堆,二叉堆的数据是从下标1开始,而此处堆排序的数组包含位置0的数据。这里的程序与二叉堆少许不同,不过变化很小。
第五部分:归并排序
template<typename comparable>
void heapsort(vector<comparable>&a)
{
for(int i=a.size()/2;i>=0;i--)//注意这里i开始的下滤位置
percolateDown(a,i,a.size());//建立一个二叉堆
for(int j=a,size()-1;j>0;j--)
{
swap(a[0],a[j]);//交换第j个与第0个的位置,并从位置0开始下滤
percolateDown(a,0,j);
}
}
inline int leftChild(int i)
{
return 2*i+1;//注意这里的左儿子为2*i+1,而不是2*i
}
template<typename comparable>
void percolateDown(vector<comparable>&a)//保证进行下滤后仍然是一个二叉堆
{
int child;
comparable tmp;
for(tmp=a[i];leftChild(i)<n;i=child)
{
child=leftChild(i);
if(child!=n-1&&a[child]<a[child+1])
child++;//让child的位置适中指向较大的儿子
if(tmp<a[child])//如果tmp比最大的儿子还要小的话,进行赋值
a[i]=a[child];
else
break;
}
a[i]=tmp;
}
归并排序(merge sort)以O(NlogN)最坏情形运行时间运行,而使用的比较次序几乎是最优的。它是递归算法很好的一个实例。这个算法中基本操作是合并两个已排序的表。因为这两个表是已排序的,所以若将输出放到第三个表中则该算法可以通过对输出数据一趟排序完成。基本的合并算法是取两个输入数组A和B、一个输出数组C以及三个计数器(Atr,Btr,Ctr),它们的初始位置对应数组的开端。A[Atr]和B[Btr]中的较小者被复制到C中的下一个位置,相关的计数器向前推进一位。当两个输入列表有一个用完的时候,则将另一个表中的剩余部分拷贝到C中。下面给出代码实现
//单参数mergeSort是四参数递归函数mergeSort的一个驱动程序
template<typename comparable>
void mergeSort(vector<comparable>&a)
{
vector<comparable>tmpArray(a.size());//创建一个临时数组
mergeSort(a,tmpArray,0,a.size()-1);
}
template<typename comparable>
void mergeSort(vector<comparable>&a,vector<comparable>&tmpArray,int left,int right)
{
if(left<right)
{
int center=(left+right)/2;
mergeSort(a,tmpArray,left,center);//分而治之的思想
mergeSort(a,tmpArray,center+1,right);
merge(a,tmpArrat,left,center+2,right);
}
}
template<typename comparable>
void merge(vector<comparable>&a,vector<comparable>&tmpArray,int leftPos,int rightPos,int rightEnd)
{
int leftEnd=rightPos-1;
int tmpPos=leftpos;//tmpArray的记位器
int numElements=rightEnd-leftPos+1;//记录当前元素的个数
while(leftPos<=leftEnd&&rightPos<=rightEnd)//将左右两部分按大小顺序一次放入第三个数组中
if(a[leftPos]<=a[rightPos])
tmpArray[tmpArray[tmpPos++]=a[leftPos++];
else
tmpArray[tmpArray[tmpPos++]=a[rightPos++];
while(leftPos<=leftEnd)//若此时左边还有剩余
tmpArray[tmpPos++]=a[leftPos++];
while(rightPos<=rightEnd)//若此时右边还有剩余
tmpArray[tmpPos++]=a[rightPOs++];
for(int i=0;i<numElements;i++,rightend--)//将排过序的数组重新放在原数组中
a[rightEnd]=tmpArray[rightEnd];
}
最后一部分:快速排序
顾名思义,快速排序(quicksort)实在实践中最快的一直排序算法,它的平均运行时间为O(NlogN)。该算法之所以特别快,主要是由于非常精炼和高度优化的内部循环。
首先要选取枢纽元,通常没有经过充分考虑的选择是将第一个元素用作枢纽元,如果输入时随机的,那么这是可以接受的,否则将会形成一个恶劣的分割。一种安全的算法是随机的选取枢纽元,但显然其花费是昂贵的,另一种比较好的做法是三数中值分割法,下面给出这种算法的具体代码:
template<typename comparable>
const comparable& median3(vector<comparable>&a,int left,int right)//寻找枢纽元
{
int center=(left+right)/2;
if(a[center]<a[left])//将位于left,right,center位置的元素排序
swap(a[left],a[center]);
if(a[left]>a[right])
swap(a[left],a[right]);
if(a[center]<a[right])
awap(a[left],a[right]);
swap(a[center],a[right-1]);//将枢纽元也就是a[center]放在right-1的位置上
return a[right-1];
}
其次是分割阶段,在这里我们要做的就是把所有小的元素移到数组的左边而把所有大的元素移向数组的右端。这里的大小是相对枢纽元的。具体的做法是:当i在j的左边时,我们将i右移,移过那些小于枢纽元的元素,并将j左移,移过那些大于枢纽元的元素。当i和j停止时,i指向一个大元素而j指向一个小元素。如果i在j的左边,那么将这两个元素互换,其效果是把一个大的元素推向右边而把一个小的元素推向左边。这里给出代码,当然也是篇的最后一段代码
//快速排序的驱动程序
template<typename comparable>
void quickSort(vector<comparable>&a)
{
quickSort(a,0,a.size()-1);
}
template<typename comparable>
void quickSort(vector<comparable>&a,int left,int right)
{
if(left+10<=right)
{
comparable pivot=median3(a,left,right);//找出枢纽元
int i=left,j=right-1;
for(; ;)
{
while(a[++i]<pivot){};//一定会跳出,因为a[right]最大
while(pivot<a[--j]){};//同理也一定会跳出
if(i<j)
swap(a[i],a[j]);
else break;
}
swap(a[i],a[right-1]);//将pivot放回到中间位置,其原在right-1位置
quickSort(a,left,i-1);
quickSort(a,i+1,right);
}
else
insertionSort(a,left,right);//当元素个数少于十个是,插入排序速度更快
}
哎,忙活了半天,终于搞定了。终于写完了我的第一篇比较完整的博文,总的来说还是感觉太累了,不得不感叹一下那些写博客的大神。马上就要期末考试了,加油,要挺过去啊,不要忘了你的梦想!!!