多种基础排序方法

一、插入排序

 

1.直接插入排序

依次将待排序序列中每一个记录插入到有序序列中,直到全部记录都排好序。

#include <bits/stdc++.h>

using namespace std;

void insertsort(int r[], int n)
{
    for(int i = 2; i <= n; i++)
    {
        int j;
        r[0] = r[i];//r[0]是作为哨兵 
        //哨兵作用:1.进入查找(插入位置)循环之前,它保存了r[i]的副本,使不致于因记录后移而丢失r[i]的内容 2.在查找循环中"监视"下标变量j是否越界。一旦越界(即j=0),因为r[0].可以和自己比较,循环判定条件不成立使得查找循环结束,从而避免了在该循环内的每一次均要检测j是否越界(即省略了循环判定条件"j>=1")
        for(j = i-1; r[0] < r[j]; j--)
        {
            r[j+1] = r[j];
        }
        r[j+1] = r[0];
    }
}

int main()
{
    int n;
    int r[105];

    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &r[i]);
    }
    insertsort(r, n);
    for(int i = 1; i <= n; i++)
    {
        printf("%d%c", r[i], " \n"[i==n]);
    }
    return 0;
}
/*
7
12 15 9 20 6 31 24
*/

复杂度分析:

最好情况下,待排序为正序,每次只和有序序列最末位比较一次,并只移动两次。复杂度O(n)
最坏情况下,待排序为逆序,第i次插入要和i-1个数字进行比较,每比较一次都要移动一次。复杂度O(n²)
平均:O(n²)
辅助空间:O(1)
直接插入排序是稳定的排序方法。
当序列中的记录基本有序或者待排序列记录较少时,它是最佳的排序方法。
 

2.希尔排序

先将整个待排序列分为若干子序列,在子序列内进行直接插入排序,待整个序列基本有序时,再将全体记录进行一次插入排序。

void shellsort(int r[], int n)
{
    for(int d = n/2; d >= 1; d /= 2)//此处d就是以d为间隔来达成逐段分割
    {
        for(int i = d+1; i <= n; i++)
        {
            int j;
            r[0] = r[i];
            for(j = i-d; j > 0 && r[0] < r[j]; j -= d)
            {
                r[j+d] = r[j];
            }
            r[j+d] = r[0];
        }
    }
}
复杂度分析:

时间性能在O(n²)和O(nlog2n{log_2{n}})之间。n在特定范围内,时间性能在O(n^1.3)。
最好:O(n^1.3)
最坏:O(n²)
平均:O(nlog2n{log_2{n}})~O(n²)
辅助空间:O(1)
希尔排序是一种不稳定的排序方法。
 

二、交换排序

 

1.起泡排序

两两比较相邻的数字,反序则交换,依次比较直到没有反序为止。

void bubblesort(int r[], int n)
{
    int flag = n;//flag是用来判断在遍历无序区是否交换过,若交换过则必然不为0,则继续while
    while(flag != 0)
    {
        int last = flag;//last是标记作为无序区的最后一位的位置
        flag = 0;
        for(int i = 1; i < last; i++)
        {
            if(r[i] > r[i+1])
            {
                int tmp = r[i];
                r[i] = r[i+1];
                r[i+1] = tmp;
                flag = i;
            }
        }
    }
}
复杂度分析:

最好情况下:待排序为正序,则执行一次,无交换,复杂度为O(n)
最坏情况下:待排序为逆序,则每次只有无序序列中最大值交换到了最终位置,执行了n-1次,第i次执行了n-i次的比较与交换。复杂度为O(n²)
平均:O(n²)
辅助空间:O(1)
起泡排序是一种稳定的排序方法。
 

2.快速排序

快排是选择一个轴值,将待排根据轴值分为两部分,左边均小于轴值,右边均大于轴值,然后对左右两边都进行这样的操作,直到整个序列有序。

#include <bits/stdc++.h>

using namespace std;

int onesort(int r[], int s, int e)//对当前区间进行划分,s为起始位置,e为结束位置
{
    int i = s;
    int j = e;
    while(i < j)//判断左右两边的下标,
    {
        while(i < j && r[i] <= r[j])//先对右边进行扫描,扫描到左边大于右边则停止循环并进行交换,下次从左边扫描
        {
            j--;
        }
        if(i < j)
        {
            int tmp = r[i];
            r[i] = r[j];
            r[j] = tmp;
            i++;
        }
        while(i < j && r[i] <= r[j])//对左边进行扫描,扫描到左边大于右边则停止扫描并进行交换,下次从右边扫描
        {
            i++;
        }
        if(i < j)
        {
            int tmp = r[i];
            r[i] = r[j];
            r[j] = tmp;
            j--;
        }
    }
    return i;//返回当前区间轴值的位置
}

void quicksort(int r[], int s, int e)
{
    if(s < e)
    {
        int mid = onesort(r, s, e);//对当前区间进行划分并取得当前区间的轴值
        quicksort(r, s, mid-1);//对轴值左边区间进行划分
        quicksort(r, mid+1, e);//对轴值右边区间进行划分
    }
}

int main()
{
    int n;
    int r[105];

    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &r[i]);
    }
    quicksort(r, 1, n);
    for(int i = 1; i <= n; i++)
    {
        printf("%d%c", r[i], " \n"[i==n]);
    }
    return 0;
}
/*
7
12 15 9 20 6 31 24
8
50 13 55 97 27 38 49 65
7
23 13 49 6 31 19 28
*/

快速排序的次数取决于递归的深度。

复杂度分析:

最好情况下:每次划分的序列左边区间与右边区间长度相等,对每次划分要扫描整个序列,所需O(n),共需划分log2n{log_2{n}}次,故复杂度为O(nlog2n{log_2{n}})
最坏情况下:待排序列为正序或者逆序,则划分左边或者右边总会有一边长度为空,则需调用n-1次递归才能完全排好序。故复杂度为O(n²)
平均O(nlog2n{log_2{n}})
辅助空间:O(log2n{log_2{n}})~O(n)
快速排序是一种不稳定的排序方法。
快速排序平均性能是迄今为止排序算法中最好的一种。
 

三、选择排序

 

1.简单选择排序

在r[i]-r[n]中最小的与r[i]进行交换作为有序序列中第i个。

void selectsort(int r[], int n)
{
    for(int i = 1; i <= n; i++)
    {
        int flag = i;
        for(int j = i+1; j <= n; j++)
        {
            if(r[flag] > r[j])
            {
                flag = j;
            }
        }
        if(flag != i)
        {
            int tmp = r[flag];
            r[flag] = r[i];
            r[i] = tmp;
        }
    }
}
复杂度分析:

无论情况怎样,都会进行n-1次比较,且需要进行n-1次排序。故最好情况最坏情况下复杂度均为O(n²)
辅助空间:O(1)。
简单选择排序是一种不稳定的排序方法。
 

2.堆排序

利用二叉树的逻辑思想,将最大值放到根节点(大根堆),然后将最大值放到有序序列中第一个位置,依次下去便可排好序。

void sift(int r[], int s, int e)//每次调整堆,当前节点为s,堆中最后一个节点为e (大根堆)
{
    int i = s;
    int j = i*2;//左节点
    while(j <= e)//还在这个堆里
    {
        if(j < e && r[j] < r[j+1])//将j指向左节点与右节点中的大者
        {
            j++;
        }
        if(r[i] > r[j])//若当前节点大于左右节点,则不用调整
        {
            break;
        }
        else//调整这个堆并且把i与j往下,达到完全调整好以s为节点的大根堆
        {
            int tmp = r[i];
            r[i] = r[j];
            r[j] = tmp;
            i = j;
            j = i*2;
        }
    }
}

void heapsort(int r[], int n)
{
    for(int i = n/2; i >= 1; i--)//从最后一个分支节点开始往根节点 都要进行调整 初始堆
    {
        sift(r, i, n);//调整以i为节点的大根堆
    }
    for(int i = 1; i < n; i++)//对所有节点都进行 把大根堆的顶点r[1]放到r[n-i+1]相对应的位置,并调整1~n-i的堆,因为n-i以后都是有序的
    {
        int tmp = r[1];
        r[1] = r[n-i+1];
        r[n-i+1] = tmp;
        sift(r, 1, n-i);
    }
}
复杂度分析:

运行时间主要在初始堆与调整堆上,初始堆需要的时间为O(n),第i次调整需要的时间为O(log2i{log_2{i}}),且需要n-1次调整堆,故最好情况与最坏情况都是时间复杂度为O(nlog2n{log_2{n}})
堆排序是一种不稳定的排序方法。
由于堆排交换与比较次数比较多,平均而言比快速排序慢。
 

四、归并排序

 

1.二路归并排序

把整个序列递归分下去分成若干小块,将两两相邻的小块进行排序,之后在一层层向上整合。

void Merge(int r[], int s, int m, int e)//将s~m与m+1~e的两小块整合排序到s~e中
{
    int i = s;
    int j = m+1;
    int l = s;
    int tmp[105];
    while(i <= m && j <= e)//将左右小块中的小值放到辅助数组中
    {
        if(r[i] <= r[j])
        {
            tmp[l++] = r[i++];
        }
        else
        {
            tmp[l++] = r[j++];
        }
    }
    while(i <= m)//若右边小块完了,就把左边小块剩余的放到辅助数组中
    {
        tmp[l++] = r[i++];
    }
    while(j <= e)
    {
        tmp[l++] = r[j++];
    }
    for(int i = s; i <= e; i++)//把辅助数组给传参进来的数组
    {
        r[i] = tmp[i];
    }
}

void Mergesort(int r[], int s, int e)
{
    if(s == e)//起始位置和结束位置相等表示这小块只有一个数字,不用排序了
    {
        return;
    }
    else
    {
        int m = (s+e)>>1;//找中值并分别对左右小块进行递归
        Mergesort(r, s, m);
        Mergesort(r, m+1, e);
        Merge(r, s, m, e);//整合排序左右小块
    }
}

复杂度分析:

一次归并排序需要将待排小块扫描一遍,故时间复杂度为O(n),且归并需要进行log2n{log_2{n}}次,故最好情况最坏情况时间复杂度为O(nlog2n{log_2{n}})
辅助空间需要一个等大的辅助数组来支持,故空间复杂度为O(n)
二路归并排序时一种稳定的排序方法。
 

五、分配排序

 

1.桶排序

桶排序思路就是将序列中的数字放到哈希表中,再遍历这个哈希表依次将元素取出便成为有序的序列。
可以用链表与队列进行实现。

复杂度分析:

由于是放入哈希表中,再遍历,故初始化放入就需要O(n)的时间,再者序列中可能会有相同元素,设相同元素有m个,故遍历这些相同元素就需要O(m)的时间,故总时间为O(n+m)
空间复杂度为O(m),表示储存m个静态队列表示的桶。
桶排序是稳定的排序方法。
 

2.基数排序

基数排序是将序列中所有数字补位补到等长,数位短的就在前面补0以此达到目的。之后从最低位开始往高位进行排序。就会变成有序序列。
设待排序列为n个,d为等长的长度,取值范围为m,每一次分配的时间为O(n),每一次收集需要的时间为O(n+m),需要执行d次,故时间复杂度O(d(n+m))
空间复杂度,因为需要m个队列,故空间复杂度为O(m)
 

六、大概总结

 

1.各排序的复杂度

在这里插入图片描述
对于基本有序的序列,直接插入排序最合适!
快速排序目前被认为是最快的排序方法!
对于序列里记录多的情况下,归并排序较堆排序更快!
最好情况下:直接插入排序和起泡排序最快
最坏情况下:堆排序和归并排序最快
平均情况下:快速排序最快

 

2.各排序的稳定性

稳定的:直接插入排序、起泡排序、归并排序、桶排序和基数排序。
不稳定的:希尔排序、快速排序、简单选择排序、堆排序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章