学不完的数据结构(六)排序

502 阅读9分钟

7.排序

7.1 基本概念

稳定性排序前后相对位置不变。(稳定性不能衡量算法的优劣)(不同排序方法可能得到不同结果)

内部排序:放在内存中的排序

外部排序:不断的在内外存之间移动的排序

​ 注意拓扑排序不是排序。

7.2 插入类排序

7.2.1直接插入排序

直接插入排序:每趟将一个待排序的关键字按照其值大小插入到已经有序的部分序列的合适位置上,直到所有关键字都被插入为止。

代码实现:

void insertSort(int R[],int n)
{
    int i,j;
    int temp;
    for(i=1;i<n;i++)
    {
        temp = R[i];
        j = i-1;
        while(j>=0&&temp<R[j])
        {
            R[j+1] = R[j];
            --j;
        }
        R[j+1] = temp;
    }
}
直接插入排序 最好 最坏 平均
空间 O(1)
时间 有序:O(n) 逆序:O(n^2) O(n^2)
比较次数 有序:n-1 逆序:\frac{n(n-1)}{2} \frac{n^2}{4}
移动次数 有序:0 逆序:\frac {(n-1)(n+2)}{2} \frac{n^2}{4}

​ 稳定性: 稳定 适用性:顺序结构,链式结构 一趟排序不能确定任何一个关键字到达其最终位置

适用场景:

初始序列基本有序时使用

7.2.2折半插入排序

折半插入排序:插入思想和直接插入排序相似,区别是折半插入是采用折半查找法来确定插入位置的

代码实现:

//自己写的 仅供参考
void midInsert(int R[],int n)
{
    int low,high;
    low = 0;high = 0;
    int i,j,mid,temp;
    for(i=1;i<n;i++)
    {
        temp = R[i];
        while(low<=high)
        {
            mid = (low+high)/2;
            if(R[mid]>=R[i])
                high = mid-1;
            else 
                low = mid+1;
        }
        //插入到相应位置
        j = i-1;
        while(j>=low)
            R[j+1] = R[j];
        R[low] = temp;
        high++;
    }
}
折半插入排序 最好 最坏 平均
空间 O(1)
时间 O(nlogn) O(n^2) O(n^2)
比较次数 nlog_2n
移动次数 有序:0 逆序:\frac {(n-1)(n+2)}{2} \frac{n^2}{4}

​ 稳定性: 稳定 适用性:顺序结构,链式结构 一趟排序不能确定任何一个关键字到达其最终位置

7.2.3希尔排序

希尔排序:其本质上还是插入排序,只不过将待排序列按某种规则分为几个序列,然后分别对这几个子序列进行直接插入排序,这中规则的体现就是增量的选取。

增量的选取规则有下面两个:

  1. 把N每次都除以2并向下取整 所对应的时间复杂度为O(n^2)

  2. 2^k+1 ..... 5, 3, 1 其中2^k+1小于待排序列的长度 所对应的时间复杂度为O(n^1.5)

空间复杂度:O(1) 时间复杂度:不定

​ 稳定性: 稳定 适用性:顺序结构 一趟排序不能确定任何一个关键字到达其最终位置

7.3 交换类排序

7.3.1冒泡排序

冒泡排序:就是通过一系列的交换动作完成的,冒泡排序结束的条件是在一趟排序中没有发生关键字的交换

代码实现如下

void BubbleSort(int R[],int n)
{
    int i,j,flag; int temp;
    for(i=n-1;i>=1;--i)
    {
        flag=0;
        for(j=1;j<=i;j++)
            if(R[j-1]>R[j])
            {
                temp = R[j];
                R[j] = R[j-1];
                R[j-1] = temp;
                flag = 1;
            }
        if(flag==0)
            return 0;
    }
}
冒泡排序 最好 最坏 平均
空间 O(1)
时间 有序:O(n) 逆序:O(n^2) O(n^2)
比较次数 有序:n-1 逆序:\frac{n(n-1)}{2}
移动次数 有序:0 逆序:\frac {3n(n-1)}{2}

​ 稳定性: 稳定 适用性:顺序结构,链式结构 一趟排序能确定一个关键字到达其最终位置

适用场景:

初始序列基本有序时使用

7.3.2 快速排序

快速排序:通过多次划分实现的交换类的排序

代码实现

void quickSort(int R[],int low,int high)
{
    int temp;
    int i=low,j=high;
    if(low<high)
    {
        temp = R[low];
        while(i<j)
        {
            while(i<j&&R[j]>=temp) j--;
            if(i<j)
            {
                R[i] = R[j];
                i++;
            }
            while(i<j&&R[i]<temp) i++;
            if(i<j)
            {
                R[j] = R[i];
                j--;
            }
        }
        R[i] = temp;
        quickSort(R,low,i-1);
        quickSort(R,i+1,high);
    }
}
快速排序 最好 最坏 平均
空间 枢轴将表等分O(logn) 有序或逆序:O(n) O(logn)
时间 中间向两边有序O(nlogn) 有序或逆序:O(n^2) O(nlogn)
比较次数
移动次数

​ 稳定性: 不稳定 适用性:顺序结构,链式结构 一趟排序能确定一个关键字到达其最终位置

适用场景:

平均性能最好

​ 当序列初始状态不好不使用快排。

7.4选择类的排序

7.4.1简单选择排序

简单选择排序:从头到尾扫描序列 选出最小(大)的元素 和第一个(最后一个)元素交换 依次进行 直到结束。。

代码实现:

void selectSort(int R[],int n)
{
    int i,j,k;
    int temp;
    for(i=0;i<n;i++)
    {
        k=i;
        for(j=i+1;j<n;i++)
        {
            if(R[j]>R[k])
                k = j;
        }
        temp = R[i];
        R[i] = R[k];
        R[k] = temp;
    }
}
选择排序 最好 最坏 平均
空间 O(1)
时间 O(n^2)
比较次数 \frac {n(n-1)}{2}
移动次数 有序:0 逆序:3(n-1)

​ 稳定性: 不稳定 适用性:顺序结构,链式结构 一趟排序能确定一个关键字到达其最终位置

7.4.2堆排序

堆排序:可以把堆看成一个完全二叉树 通过构造大(小)顶堆 将根节点与序列的最后一个(第一个)元素交换 之后依次进行来实现排序

(对于初始堆的建立 堆节点的插入 删除 堆的调整一定要熟悉)

代码的实现:

//堆调整函数
void shift(int R[],int low,int high)
{
    int i = low,j = 2*i;
    int temp = R[i];
    while(j<=high)
    {
        if(j<high&&R[j]<R[j+1])
            j++;
        if(temp<R[j])
        {
            R[i] = R[j];
            i = j;
            j = 2*i;
        }
        else
            break;
    }
    R[i] = temp;
}

void heapSort(int R[],int n)
{
    int i;
    int temp;
    for(i=n/2;i>=1;i--)
    	shift(R,i,n);
    for(i=n;i>=2;i--)
    {
        temp = R[1];
        R[1] = R[i];
        R[i] = temp;
        shift(R,1,i-1);
    }
}

空间复杂度:O(1) 时间复杂度:O(nlogn)

​ 稳定性: 不稳定 适用性:顺序结构 一趟排序能确定一个关键字到达其最终位置

适用场景:

​ 适用于找到前k个最大(小)的元素

​ 不牺牲空间的情况下在最坏的序列排列中依旧需要nlgn时间复杂度的排序。

7.5 归并类的排序

7.5.1 二路归并排序

2路归并排序:先将整个序列分为两半,对每一半分别进行归并排序,将得到两个有序序列 ,然后将这两个序列归并成一个序列。

void mergeSort(int A[],int low,int high)
{
    if(low<high)
    {
        int mid=(low+high)/2;
        mergeSort(A,low,mid);
        mergeSort(A,mid+1,high);
        merge(A,low,mid,high);
    }
}

空间复杂度:O(n) 时间复杂度:O(nlogn)

​ 稳定性: 稳定 适用性:顺序结构 一趟排序不能确定一个关键字到达其最终位置

适用场景:

​ 适用于关键字特别特别多的情况,将其存储在外存中排序。

7.6 基数排序

7.6.1 最高位优先法

最高位优先法:先按照最高位排成若干子序列,再对每个子序列按次高位排序。

7.6.2 最低位优先法

最低位优先法:这种方式不必分成子序列,每次排序全体关键字都参与,不通过比较,而是通过“分配”和“收集”。

​ 先按最低位排序,再依次根据更高层的位排序。

空间复杂度:O(rd) 时间复杂度:O(d(n+r))

​ 稳定性:稳定 适用性:顺序结构,链式结构 一趟排序不能确定一个关键字到达其最终位置

n是序列中的关键字的个数 d是关键字的位数 rd是关键字个数

适合场景:

​ 序列中关键字很多,但组成关键字的关键字的取值范围较小可以采用最低位优先法

​ 如果关键字的取值范围很大 而且大多数的关键字的最高位不相同 这时可以采用 最高位优先法

​ 一般是整数,有局限性。

7.7 排序知识总结

由给出的k趟排序序列推测所使用的排序方式

​ 1):写出最终排序的结果,对比题目中给出的序列

​ 2):查看是否有最终的结果确定下来,若有则标记为 |

​ 3):从有最终确定结果的排序方式中确认算法

冒泡...||| (|的个数>=k) (...中逆序个数由才开始的个数减少了k个)

快排..|..|.. (|的个数>=k)(其中每个|前面的数小于它,后面的大于它)

选择|||... (|的个数>=k)

堆排|||... (|的个数>=k) (...中的序列组成)

​ 4):若没有最终确定的结果

插入:前k个局部有序

归并:每2^k个序列有序

基数:按位有序

复杂度总结:

排序算法种类 直接插入 折半插入 希尔 冒泡 快排 简单选择 堆排序 二路归并 基数
平均情况时间复杂度 O(n^2) O(n^2) O(nlogn) O(n^2) O(nlogn) O(n^2) O(nlogn) O(nlogn) O(d(n+rd))
最好情况时间复杂度 O(n) O(nlogn) O(nlogn) O(n) O(nlogn) O(n^2) O(nlogn) O(nlogn) O(d(n+rd))
最坏情况时间复杂度 O(n^2) O(n^2) O(nlogn) O(n^2) O(n^2) O(n^2) O(nlogn) O(nlogn) O(d(n+rd))
空间复杂度 O(l) O(l) O(l) O(l) O(logn) O(l) O(l) O(n) O(rd)

选择排序就是屎,啥情况都倒数第一。

堆排就是他妈的好,可惜就是不稳定。

平均情况

快速(最好) 归并(需要空间,但是稳定堆排序 都是O(nlogn)

​ 口诀 :“以nlogn的速度归队

快 ---- 快速排序 归-----归并排序 队-----堆排序 这些都是平均情况下是nlogn

空间复杂度

快排为log n,归并恒定 n,别的都是1

基本有序情况

插入 冒泡 都是O(n)

最坏情况

堆排(不稳定,但不占空间) 归并(需要空间,但是稳定) 都是O(nlgn)

最坏情况 只有快速排序和平均情况不同 为O(n^2) 其他的都和平均情况相同

排序稳定性

​ 口诀:“考研复习累的一匹,情绪不稳定快些选好友聊天吧”

快 ---- 快速排序 些------希尔排序 选-----简单选择排序 堆------堆排序不稳定

能确定最终位置

​ 口诀:“快选在群里冒泡的确定研究生位置”

快排选择堆排冒泡

和初始状态无关

​ 选择(O(n^2)),堆排归并(要空间

比较次数初始序列无关的是:

​ 选择,折半插入 ,归并,基数 剩下的全都有关

移动次数初始序列有关的是:

​ 插入,冒泡,快排

排序趟数初始序列有关的是:

​ 交换类的排序(冒泡排序,快速排序) 剩下的全都无关