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