本文是对常见的排序算法进行总结和分析。
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
对相邻的元素进行两两比较,顺序相反则进行交换。这样每一趟将最小或最大的元素浮到顶端。最终达到完全有序。
排序时间复杂度是?
冒泡排序最坏的时间复杂度是O(n^2)。为什么?考虑最坏的情况,完全倒序,那么需要比较的次数就是n+(n-1)+(n-2)+....1,等差数列求和O(n^2)
如何优化?
优化的思想其实就是添加一个标志,若在某一趟比较中没有发生交换,则停止后面的比较。这样在(最佳)顺序排列的时候时间复杂度O(n),也就是检查一轮就结束排序。
冒泡排序是否稳定?
稳定,但是冒泡排序也可以转化为不稳定算法。即将比较的条件 if (a[i]>a[i+1]) ====> if (a[i]>=a[i+1]) 转化为不稳定排序算法。
void BubbleSort(vector& data)
{
bool flag=true;
for(int i=0;i=i;--j)
{
if(data[j]>data[j+1])
{
flag=true;
swap(data[j],data[j+1]);
}
}
}
}
1.选择一个基数。
2.分区,把小于基数的排左边,大于基数的排右边。
3.对得到两个分区重复以上步骤,直至分区只有1个元素
快排最坏的情况是O(n^2),顺序或者逆序都是。最佳的情况就是O(nlogn)。(注意:所谓最坏其实就是分治的时候分成两个极不平衡的数组,比如n个元素分成一个是n-1个元素和一个1个元素的.)
空间复杂度主要是递归造成的递归栈所引起,快排的空间复杂度最坏的情况下是O(n),通常情况下为O(logn)
方法一:不要总把数组第一个数选择基数,采取随机选择。
方法二:三数取中。比方说有序列: 8 1 4 9 6 3 5 2 7 0取最左边、最最右边以及中间的。分别是8 0 6。取三个数中间的数即 0 6 8 的6。把取到的数和序列第一个数交换,也就是得到序列: 6 1 4 9 8 3 5 2 7 0,继续进行快排。
其实方法一、方法二都是针对基数的选择来进行优化。
快速排序还有其他优化方法:
比如优化不必要的交换:即对中枢点数字进行保存,在之前是swap时,只做替换工作,最终当low与high会合,再将low处数据赋值为之前保存的数字。
优化小数组时的排序方案:当数组非常小,快排不如直接插入排序来的更好,原因是快速排序用到了递归操作,在大量数据排序时,这点性能相对于他整体的算法优势而言是可以忽略的,但如果数组只有几个数字需要排序时,这就成了一个“大炮打蚊子的问题”。
优化递归操作:部分使用迭代而不是递归,缩减堆栈深度。
int Partiton(vector& data,int low,int high)
{
int pivot=data[low];
while(low=pivot)
++high;
swap(data[low],data[high]);
while(low data,int low,int high)
{
int index;
if(low data,int low,int high)
{
stack s;
int index=Partition(data,low,high);
if(index>low+1)
{
s.push(low);
s.push(index-1);
}
if(index+1low+1)
{
s.push(low);
s.push(index-1);
}
if(index+1
非递归的思想其实就是利用一个栈来存放4个索引(left,mid-1)和(mid+1,right)。
具体可以看看博客:https://www.cnblogs.com/chengxiao/p/6194356.html原理大概是两个阶段:阶段1是分。分的时候就是把整个数组分成只有一个元素的数组。阶段2是合。就是两两合并。合并的时候利用两个哨兵i,j。分别指向两个集合。然后比较-移动,看看核心代码就明白。
归并排序的时间复杂度为O(nlogn),最坏也是O(nlogn).是一种稳定的算法
归并排序的空间复杂为O(n),这里主要是由在merge时候产生一个O(n)的辅助数组决定的.
void MergeSort(vector& data)
{
vector copy(data.begin(),data.end());
Merge(data,copy,0,data.size()-1);
}
void Merge(vector& data,vector& copy,int start,int end)
{
if(start==end)
{
copy[start]=data[start];return;
}
int length=(end-start)/2;
Merge(data,copy,start,start+length);
Merge(data,copy,start+length+1,end);
int i=start+length,j=end;
int copyIndex=end;
while(i>=start&&j>=start+length+1)
{
if(data[i]>data[j])
copy[copyIndex--]=data[i--];
else
copy[copyIndex--]=data[j--];
}
for(;i>=start;--i)
copy[copyIndex--]=data[i];
for(;j>=start+length+1;--j)
copy[copyIndex--]data[j];
}
堆是具有如下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆。
堆排序就是利用堆(假设利用大顶堆)进行排序的方法。他的基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走,然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n元素中的次大值。如此反复执行,便可以得到一个有序序列了。
堆排序的时间消耗主要是在初始构建堆和在重建堆时的反复筛选中。
构建堆的过程中,因为我们是完全二叉树,从下层最右端的非终端结点开始构造,将它和孩子进行比较和必要的互换,对每个非终端节点,最多进行两次比较和互换的操作,因此构建堆的时间复杂度为O(n)。
在正式排序时,第i次取堆顶记录重建堆需要用logn的时间,并且需要取n-1次堆顶记录,因此重建堆的时间复杂度为O(nlogn)。
由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好,最坏,还是平均时间复杂度都是O(nlogn)。
由于记录的比较与交换时跳跃式进行,因此堆排序也是一种不稳定的排序算法。
void shiftdown(vector& data,int index,int end)
{
int left=2*index+1;
int largest;
while(left<=end)
{
largest=(left& data)
{
for(int i=(data.size()-1)/2;i>=0;--i)
shiftdown(data,i,data.size()-1);
for(int i=data.size()-1;i>0;--i)
{
swap(data[0],data[i]);
shiftdown(data,0,i-1);
}
}