排序基础算法

冒泡排序算法实例详解
算法时间复杂度
最坏情况:O(n^2)
最好情况:O(n)
平均情况:O(n^2)

空间复杂度:S(n)=O(1)
稳定性:稳定排序

1.从第一个元素开始逐个比较相邻的元素。如果第一个比第二个大(a[1]>a[2]),就交换他们两个。
2.对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。此时在这一点,最后的元素应该会是最大的数,我们也称呼一遍这样的操作为:一趟冒泡排序。
3.针对所有的元素重复以上的步骤,每一趟冒泡排序的最大值已放在最后,下一次操作则不需要将此最大值纳入计算计算。
4.持续对每次对越来越少的元素,重复上面的步骤,直到没有任何一对数字需要比较,即完成冒泡排序。

代码实现

#include<iostream>
using namespace std;
void bubble_sort(int a[],int n) {
    for(int i=0; i<n; i++) {
        for(int j=0; j<n-i; j++) {
            if(a[j]>a[j+1]) {
                swap(a[j],a[j+1]);  //交换数据
            }
        }
    }
}

int main() {
    int a[8]= {70,50,30,20,10,70,40,60};
    int n=7;
    bubble_sort(a,n);
    for(int i=0; i<=n; i++) {
        cout<<a[i]<<' ';
    }
    return 0;
}

冒泡排序是八大排序中最简单及基础的排序,这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

简单选择排序算法实例详解
算法时间复杂度
最坏情况:O(n^2)
最好情况:O(1) //即不需要排序,本身已是正序
平均情况:O(n^2)

空间复杂度:S(n)=O(1)

稳定性:不稳定排序

1.我们设置两个记录i和j,i自数组第一个元素开始,j自i+1个元素开始。
2.接着j遍历整个数组,选出整个数组最小的值,并让这个最小的值和i的位置交换(如果i选择的元素是最小的则不需要交换),我们称这个过程为一趟选择排序。
3.i选中下一个元素(i++),重复进行每一趟选择排序。
4.持续上述步骤,使得i到达n-1处,即完成排序 。
每一趟从待排序的数据元素中选出最小的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完,我们只需要进行n-1趟排序即可,因为最后剩下的一个数据一定是整体数据中最大的数据。
代码实现如下

#include<iostream>
using namespace std;
void select_sort(int a[],int n){
    int temp;
    for(int i=0;i<n-1;i++){
        temp=i;      //利用一个中间变量temp来记录需要交换元素的位置
        for(int j=i+1;j<n;j++){
            if(a[temp]>a[j]){   //选出待排数据中的最小值
                temp=j;
            }
        }
        swap(a[i],a[temp]); //交换函数
    }
}
int main(){
    int a[8]={2,10,9,4,8,1,6,5};
    int n=8;
    select_sort(a,n);
    for(int i=0;i<n;i++){
        cout<<a[i]<<' ';
    }
    return 0;
}

相比冒泡排序的不断交换,简单选择排序(simple selection sort)是等到合适的关键字出现后再进行交换,并且移动(交换)一次就可以达到一次冒泡的效果。

直接插入排序算法实例详解

最坏情况:O(N^2)
最好情况:O(N^2)
平均情况:O(N^2)

稳定性:稳定排序

直接插入排序是把新的数据插入以及排序好的数列中,排序的基本方法是:每一步将一个待排序的元素,按其排序码的大小,插入到前面已经排好序的一组元素的适当位置上去,直到元素全部插入为止。
可以选择不同的方法在已经排好序数据表中寻找插入位置。根据查找方法不同,有多种插入排序方法,下面要介绍的是直接插入排序。

  1.     每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。
    
  2.     第一趟比较前两个数,然后把第二个数按大小插入到有序表中;
    
  3.     第二趟把第三个数据与前两个数从后向前扫描,把第三个数按大小插入到有序表中;
    
  4.     依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程。
    

直接插入排序是由两层嵌套循环组成的。外层循环标识并决定待比较的数值。内层循环为待比较数值确定其最终位置。直接插入排序将待比较的数值与它的前一个数值进行比较,所以外层循环是从第二个数值开始的。当前一数值比待比较数值大的情况下继续循环比较,直到找到比待比较数值小的并将待比较数值置入其后一位置,结束该次循环。
代码实现(第一层循环改为<=n,不然最后一位不排序)

#include<iostream>
using namespace std;
void insert_sort(int a[],int n) {
    int i,j;
    for(i=1; i<n; i++) { //循环从第2个元素开始
        if(a[i]<a[i-1]) {
            int temp=a[i];
            for(j=i-1; j>=0 && a[j]>temp; j--) {
                a[j+1]=a[j];
            }
            a[j+1]=temp;//此处就是a[j+1]=temp;
        }
    }
}

int main() {
    int a[8]= {70,50,30,20,10,70,40,60};
    int n=7;
    insert_sort(a,n);
    for(int i=0; i<=n; i++) {
        cout<<a[i]<<' ';
    }
    return 0;
}

希尔排序算法实例详解
算法时间复杂度
最坏情况:O(n^2)
最好情况:O(n)
平均情况:O(n^2)

稳定性:不稳定排序

算法时间复杂度
最坏情况:O(n^2)
最好情况:O(n)
平均情况:O(n^2)

稳定性:不稳定排序

希尔排序,又名递减增量排序算法,是一种非稳定的更高效的插入排序,在对几乎已经排好序的数据操作时,效率极高,即可以达到线性排序的效率,直接插入排序整体来说是低效的,因为插入排序每次只能将数据移动一位;
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
过程如下:

  1. 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
  2. 按增量序列个数 k,对序列进行 k 趟排序;
  3. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

实现代码

#include<iostream>
using namespace std;
void shellSort(int arr[], int n) {
    int i, j, gap;
    for (gap = n / 2; gap > 0; gap /= 2) {
        for (i = 0; i < gap; i++) {
            for (j = i + gap; j < n; j += gap) {
                for (int k = j; k > i && arr[k] < arr[k-gap]; k -= gap) {
                    swap(arr[k-gap], arr[k]);
                }
            }
        }
    }
}

int main() {
    int a[8]= {70,50,30,20,10,70,40,60};
    int n=8;
    shellSort(a,n);
    for(int i=0; i<n; i++) {
        cout<<a[i]<<' ';
    }
    return 0;
}

快速排序算法实例详解
算法时间复杂度
最坏情况:O(n^2)
最好情况:O(nlogn)
平均情况:O(nlogn)

稳定性:不稳定排序

快速排序是考察次数最多的排序,无论是在大学专业课的期末考试,还是在公司的面试测试题目中,快速排序都极大的被使用,在实际中快速排序也极大的被使用,如STL中的sort底层就是一个快速排序。
首先在数组中选择一个基准点,然后分别从数组的两端扫描数组,设两个指示标志(low指向起始位置,high指向末尾),首先从后半部分开始,如果发现有元素比该基准点的值小,就交换low和high位置的值,然后从前半部分开始扫描,发现有元素大于基准点的值,就交换low和high位置的值,如此往复循环,直到low>=high,然后把基准点的值放到high这个位置。一次排序就完成了。
以后采用递归的方式分别对前半部分和后半部分排序,当前半部分和后半部分均有序时该数组就自然有序了。

代码实现

#include<iostream>
using namespace std;

void qucik_sort(int a[],int low,int high) {
    int i,j,temp;
    i=low;
    j=high;
    if(low<high) {
        temp=a[low];    //设置枢轴
        while(i!=j) {
            while(j>i&&a[j]>=temp) {
                --j;
            }
            if(i<j) {
                a[i]=a[j];
                ++i;
            }

            while(i<j&&a[i]<temp) {
                ++i;
            }
            if(i<j) {
                a[j]=a[i];
                --j;
            }
        }
        a[i]=temp;
        qucik_sort(a,low,i-1);
        qucik_sort(a,i+1,high);
    }
}

int main() {
    int a[8]= {70,50,30,20,10,70,40,60};
    int n=8;
    qucik_sort(a,0,n-1);
    for(int i=0; i<n; i++) {
        cout<<a[i]<<' ';
    }
    return 0;
}

归并排序算法实例详解
算法时间复杂度
最坏情况O(NlogN)
最好情况O(NlogN)
平均情况O(NlogN)

空间复杂度O(N) 注:归并排序需要创建一个与原数组相同长度的数组来辅助排序

稳定性:稳定排序

2.过程介绍
归并排序的核心思想是将两个有序的数列合并成一个大的有序的序列。通过递归,层层合并,即为归并,归并排序的算法效率仅次于快速排序,是一种稳定的算法,需要建立两倍的数组空间,一般用于对总体而言无序,但是各子项又相对有序【并不是完全乱序】的情况比较适用。
a)申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
b)设定两个指针,最初位置分别为两个已经排序序列的起始位置
c)比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤c直到某一指针超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾

  1. 相关的代码
#include <iostream>
#include <math.h>
using namespace std;

void merge(int arr[],int l,int mid,int r) {
    int aux[r-l+1];//开辟一个新的数组,将原数组映射进去
    for(int m=l; m<=r; m++) {
        aux[m-l]=arr[m];
    }

    int i=l,j=mid+1;//i和j分别指向两个子数组开头部分

    for(int k=l; k<=r; k++) {
        if(i>mid) {
            arr[k]=aux[j-l];
            j++;
        } else if(j>r) {
            arr[k]=aux[i-l];
            i++;
        } else if(aux[i-l]<aux[j-l]) {
            arr[k]=aux[i-l];
            i++;
        } else {
            arr[k]=aux[j-l];
            j++;
        }
    }
}

void merge_sort(int arr[],int n) {
    for(int sz=1; sz<=n; sz+=sz) {
        for(int i=0; i+sz<n; i+=sz+sz) { //i+sz防止越界
            //对局部:arr[i...sz-1]和arr[i+sz.....i+2*sz-1]进行排序
            merge(arr,i,i+sz-1,min(i+sz+sz-1,n-1));    //min函数防止越界
        }
    }

}

int main() {
    int a[8]= {70,50,30,20,10,70,40,60};
    int n=8;
    merge_sort(a,n);
    for(int i=0; i<n; i++) {
        cout<<a[i]<<" ";
    }
    return 0;
}

各个排序面对数据的适用情况与小技巧
选取几个比较有特点有代表性质的排序算法
快速排序算法的效率体现在序列越乱的时候,效率越高,当数据趋于一个有序状态时(无论是顺序还是逆序),将会退化为冒泡排序,当数据完全处于逆序状态时,快速排序将会消耗极大的时间,因此有些OJ的快速排序模板测试题并不能直接写快排通过【毒瘤数据,超大完全逆序数据】,可以尝试适用rand()产生随机数的方法将整体数据打乱再使用快速排序,可以极大的减少运行时间。
直接插入排序有着稳定和速度快的优点,缺点是比较次数越少,插入点后的数据移动就越多,特别是数据庞大的时候就需要大量的移动数据。
堆排序作为一个相对比较复杂的排序,我们可以更加深入的去借鉴思想,比如有问题要求在n个数据中选出或者排序出前k个数据,利用堆排序的思维可以不将全部的n进行排序而只操作出前k个数据的情况,这个思维是很重要的。
希尔排序最主要的操作是比较而不是交换,因此在小数组的情况下是比快速排序和堆排序要快的,但是涉及大量数据时依旧不如快排。
对于绝大多数排序在数据达到顺序的情况下并没有办法直接结束,在前置处理数据不是很大的情况下,可以设置一个flag标记,当数据完全处于顺序的情况下直接强制退出排序,以达到减少运行时间的效果,当然前置处理数据在总执行步骤中占比不易过大,如总执行100次,在执行95次的时候已经达到顺序,有5次循环浪费,这样的情况浪费时间不如你而外增添的标记判断时间,就不需要这个flag标记。

原文链接:https://www.dotcpp.com/course/ds-sort/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值