排序算法总结

摘要:

本文是对常见的排序算法进行总结和分析。

排序算法稳定性的定义:

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,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& 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]);
            }
        }
    }
}

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& 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)。

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& 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];
}

4.堆排序

4.1堆排序原理

堆是具有如下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆。

堆排序就是利用堆(假设利用大顶堆)进行排序的方法。他的基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走,然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n元素中的次大值。如此反复执行,便可以得到一个有序序列了。

4.2复杂度分析

堆排序的时间消耗主要是在初始构建堆和在重建堆时的反复筛选中。

构建堆的过程中,因为我们是完全二叉树,从下层最右端的非终端结点开始构造,将它和孩子进行比较和必要的互换,对每个非终端节点,最多进行两次比较和互换的操作,因此构建堆的时间复杂度为O(n)。

在正式排序时,第i次取堆顶记录重建堆需要用logn的时间,并且需要取n-1次堆顶记录,因此重建堆的时间复杂度为O(nlogn)。

由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好,最坏,还是平均时间复杂度都是O(nlogn)。

由于记录的比较与交换时跳跃式进行,因此堆排序也是一种不稳定的排序算法。

4.3代码

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);
    }
 }  

 

你可能感兴趣的:(排序算法,算法)