基础算法 - 排序

一、排序

- 排序分为三种:

1. 朴素排序算法

2. 分治排序算法

3. 统计排序算法

此外还有专门用于排序的 sort 函数

- 排序的稳定性

排序的稳定性,指排序前后,元素的相对位置是否发生改变。例如原先有两个 2,我们称前面的为 1-2,后面的为 2-2。若排序后,前面的 2 还是 1-2,后面的 2 还是 2-2,那么这个排序就是稳定的,反之则不稳定。

1)朴素排序算法

1. 冒泡排序

2. 插入排序

3. 选择排序

- 冒泡排序

冒泡排序,其操作是不断地从头至尾一次对比相邻两个数,若不满足排序要求,则调换这两个数。直到全部满足排序要求。因其效果形似汽水中的气泡从底部浮上表面的过程,而得名“冒泡排序”。

冒泡排序前后数的顺序不会发生改变,是稳定的。

//冒泡排序示例
2 5 1 3 6 4 
2 5 1 3 6 4  //1,2
2 1 5 3 6 4  //2,3
2 1 3 5 6 4  //3,4
2 1 3 5 6 4  //4,5
2 1 3 5 4 6  //5,6
1 2 3 5 4 6  //1,2
1 2 3 5 4 6  //2,3
1 2 3 5 4 6  //3,4
1 2 3 4 5 6  //4,5
1 2 3 4 5 6  //5,6 ✓

因此冒泡排序的实际操作数是待排序数列中的逆序对数,时间复杂度为 O(n2)。

//代码
void bubblesort(int a[N],int n){    //一个长度为 n 的数列 a
    for(int i=1;i<=n;i++){    //不断地重复遍历操作
        for(int j=1;j<=n-i;j++){    //从头到尾进行遍历
            if(a[j]>=a[j+1]){    //比较相邻两个数,这里默认从小到大排序,>= 不满足这个条件,因此若 a[j]>=a[j+1] 就需要调换两数
                swap(a[j],a[j+1]);
            }
        }
    }
}

- 插入排序

从头到尾遍历一遍待排序数列,每得到一个新的数,就将其插入到已排序数列中的合适位置。该排序过程类似玩扑克牌时按点数从小到大排列的排序方法。

插入排序前后数的顺序不会发生改变,是稳定的。

//插入排序示例
//待排序         已排序  
2 5 1 3 6 4    
5 1 3 6 4       2  //2
1 3 6 4         2 5  //5
3 6 4           1 2 5  //1
6 4             1 2 3 5  //3
4               1 2 3 5 6  //6
                1 2 3 4 5 6  //4

因此插入排序的实际操作数对于每个数据查找其位置所需要操作数的总和,即 1+2+3+4+……+n,时间复杂度为 O(n2)。

//代码
void insertsort(int a[],int n){    //一个长度为 n 的待排序数列 a
    int b[n];    //用于存储已排序的数列 b
    memset(b,0,sizeof b);
    for(int i=1;i<=n;i++){    //从头到尾遍历数列 a
        int x=a[i];    //获取新的数
        bool fl=true;    //判断是否插在中间
        for(int j=1;j<i;j++){    //循环遍历插入 x 的合适位置
            if(b[j]>=x){    //如果找到合适位置,这里默认从小到大排序,因此当下一个数大于等于 x 时,就可以插入 x 了
                for(int k=i;k>j;k--){    //将后面部分的数列向后移一位,给 x 腾出地方
                    b[k]=b[k-1];
                }
            b[j]=x;    //插入 x
                fl=false;    //标记已经在中间插入
            break;    //跳出循环,不需要在后面寻找了。同时这样即可将 j 循环和 k 循环合并成一个,以达到优化的效果
        }
        }
        if(fl) b[i]=x;    //如果没有在中间插入,意味着 x 比任何已排序数都大,则放在最后
    }
    for(int i=1;i<=n;i++) a[i]=b[i];    //将待排序数列全部赋值成已排序数列,排序完毕
}

- 选择排序

不断重复从头到尾遍历数列,每次挑选最小/大的一个放到数列第一项。

选择排序中,序列中最小/大元素,与当前序列中的首元素交换,从而有可能破坏原序列相同元素的顺序,属于不稳定排序。
//选择排序示例
2 5 1 3 6 4    
1 2 5 3 6 4  //1
1 2 5 3 6 4  //2
1 2 3 5 6 4  //3
1 2 3 4 6 5  //4
1 2 3 4 5 6  //5

因此选择排序的实际操作数对于每个数据查找其位置所需要操作数的总和,即 n*n,时间复杂度为 O(n2)。

//代码
void selectionsort(int a[N],int n){    //一个长度为 n 的待排序数列 a
    for(int i=1;i<=n;i++){    //从头到尾遍历数列,每次找到最小项
        int mini=i;    //记录最小项的下标
        for(int j=i+1;j<=n;j++){    //之前排过的 i-1 项不用排了,第 i 项是当前项,所以从 i+1 开始遍历
            if(a[j]<a[mini]) mini = j;    //打擂台法,遇到比 a[i] 小/大的,就更新 mini。
        }
        swap(a[mini],a[i]);    //得到从第 i 项到第 n 项中的最小项后,将其与当前待排序数列的首项 a[i] 交换
    }
}

2)分治排序算法

1. 归并排序

2. 快速排序

- 归并排序

先不断地二分待排序数列,接着依次把左边的区间排序,把右边的区间排序,最后把左右区间合并成一个有序的区间。因其建立在归并操作的基础上,故称之为“归并排序”。

归并前后不会改变左右区间的相对顺序,所以是稳定的。

//归并排序示例
{2 5 1 3 6 4}   
{{2 5 1}    {3 6 4}}
{{{2 5}    {1}}  {{3 6}  {4}}}
{{{{2}    {5}}  {1}}  {{{3}    {6}}  {4}}}
{{{2 5}    {1}}  {{3 6}    {4}}}
{{1 2 5}    {3 4 6}}
{1 2 3 4 5 6}

该算法的平均时间复杂度为 O(nlogn)。

 

- 快速排序

 

posted on 2023-10-15 12:23  一只菜鸡的量子幽灵  阅读(29)  评论(0)    收藏  举报