经典排序算法(更新中。。。)

Posted 十年换你一句好久不见

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了经典排序算法(更新中。。。)相关的知识,希望对你有一定的参考价值。

 

一、插入排序-直接插入排序(Straight Insertion Sort)

直接插入排序(Straight Insertion Sort)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表。开始时有序表中只包含1个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,将它插入到有序表中的适当位置,使之成为新的有序表,重复n-1次可完成排序过程。

 

#include <bits/stdc++.h>
using namespace std;
void InsertSort(int *a,int n)
{
    for(int i=1;i<n;i++)
    {
        if(a[i]<a[i-1])
        {
            int j=i-1;
            int x=a[i];
            a[i]=a[i-1];
            while(x<a[j-1] && j>0) a[j]=a[j-1],j--;
            a[j]=x;
        }
    }
}
int main()
{
    int a[8]={-3,-1,-5,-7,-2,-4,-9,-6};
    InsertSort(a,8);
    for(int i=0;i<8;i++)
        printf("%d%c",a[i],i==7?\'\\n\':\' \');
    return 0;
}

 

二、插入排序-希尔排序(Shell\'s Sort)

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

如-3,-1,-5,-7,-2,-4,-9,-6

第一次分组k=8/2=4,分为四组(-3,-2)(-1,-4)(-5,-9)(-7,-6)排序后为-3,-4,-9,-7,-2,-1,-5,-6;第二次k=4/2=2
分为两组(隔二取一)(-3,-9,-2,-5)(-4,-7,-1,-6)

-9,-7,-5,-6,-3,-4,-2,-1

第二次 k=1,整体为一组,直接插序。

#include <bits/stdc++.h>
using namespace std;
void ShellSort(int *a,int n)
{
    for(int k=n/2;k;k/=2)
    {
        for(int i=k;i<n;i++)
        {
            for(int j=i-k;j>=0;j-=k)
                if(a[j]>a[j+k]) swap(a[j],a[j+k]);
        }
    }
}
int main()
{
    int a[8]={-3,-1,-5,-7,-2,-4,-9,-6};
    ShellSort(a,8);
    for(int i=0;i<8;i++)
        printf("%d%c",a[i],i==7?\'\\n\':\' \');
    return 0;
}

三、选择排序-简单选择排序(Simple Selection Sort)

 选则排序,和插入排序不同的是,每次从数组中选择一个最小的数,放到前面,因此就是n次循环O(n^2)

void SelectionSort(int *a,int n)
{
    for(int i=0;i<n;i++)
    {
        int k=i;
        for(int j=i+1;j<n;j++)
            if(a[j]<a[k]) k=j;
        if(k!=i) swap(a[i],a[k]);
    }
}
int main()
{
    int a[8]={-3,-1,-5,-7,-2,-4,-9,-6};
    SelectionSort(a,8);
    for(int i=0;i<8;i++)
        printf("%d%c",a[i],i==7?\'\\n\':\' \');
    return 0;
}

上面的选择排序,每次确定一个最小值,循环n次,也可以二元选择,每次确定一个最大值,一个最小值,因此可以减少循环。

//二元选择排序
#include <bits/stdc++.h>
using namespace std;
void SelectionSort_TwoPoint(int *a,int n)
{
    for(int i=0;i<n/2;i++)
    {
        int mink=i,maxk=n-i-1;
        for(int j=i;j<n-i;j++)
        {
            if(a[j]<a[mink]) mink=j;
            if(a[j]>a[maxk]) maxk=j;
        }
        if(mink==n-i-1 &&  maxk==i) swap(a[i],a[n-i-1]);//避免二次交换
        else if(maxk==i) swap(a[n-i-1],a[i]),swap(a[i],a[mink]);
        else swap(a[i],a[mink]),swap(a[maxk],a[n-i-1]);
    }
}
int main()
{
    int a[8]={0,-1,-5,-7,-2,-4,-9,-3};
    SelectionSort_TwoPoint(a,8);
    for(int i=0;i<8;i++)
        printf("%d%c",a[i],i==7?\'\\n\':\' \');
    return 0;
}

 

四、选择排序-堆排序(Heap Sort)

什么是堆(Heap):

  (1)堆中某个节点的值总是不大于或不小于其父节点的值;

  (2)堆总是一颗完全二叉树(Complete Binary Tree)

  完全二叉树是由满二叉树(Full Binary Tree)而引出来的。除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树称为满二叉树。

如果除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右边的若干结点,这样的二叉树被称为完全二叉树。

一棵完全二叉树,如果某个节点的值总是不小于其父节点的值,则根节点的关键字是所有节点关键字中最小的,称为小根堆(小顶堆);如果某个节点的值总是不大于其父节点的值,则根节点的关键字是所有节点关键字中最大的,称为大根堆(大顶堆)。

  若一个数列满足时可称之为堆(即任何一个子树的根节点所对应的元素一定是这颗子树所代表的所有元素中最小或最大的元素)

  堆排序的核心是建树,任何一个树的父节点的个数为n/2,设父节点的标号为u,则左儿子的标号为2*u,右儿子的标号为2*u+1;

  建树的核心为从父节点所在的最底层开始建树,每次比较父节点,左儿子和右儿子的元素大小,把最大最小的元素放到父节点的位置上(左儿子和右儿子的相对大小不做要求),若父节点的元素已经为最大(或最小),则继续向上建树,否则,交换元素后向下回溯。(建树从下往上,回溯从上往下)

以0,-1,-5,-7,-2,-4,-9,-3为例:

  元素的个数为8,所以有4个父节点。

建树完成后,1节点为最大值,交换到数组最后,之后维护前面的数,调整二叉树,直到排序完成。

代码实现:

#include <bits/stdc++.h>
using namespace std;
void HeapAdjust(int *a,int u,int n)
{
    int lson=2*u,rson=2*u+1,maxelement=u;
    if(u<=n/2)
    {
        if(lson<=n && a[maxelement]<a[lson]) maxelement=lson;
        if(rson<=n && a[maxelement]<a[rson]) maxelement=rson;
        if(maxelement!=u) swap(a[u],a[maxelement]),HeapAdjust(a,maxelement,n);//向下回溯
    }
}
void BuildHeap(int *a,int n)
{
    for(int i=n/2;i;i--)
    {
        HeapAdjust(a,i,n);
    }
}
void HeapSort(int *a,int n)
{
    BuildHeap(a,n);//建立大顶堆
    for(int i=n;i;i--)
    {
        swap(a[i],a[1]);
        HeapAdjust(a,1,i-1);//将剩余的元素重新建立为大顶堆
        //因为,每个子树的根节点为最大的,因此调整时只需从根节点维护。
    }
}
int main()
{
    int a[9]={0,0,-1,-5,-7,-2,-4,-9,-3};
    HeapSort(a,8);
    for(int i=1;i<=8;i++)
        printf("%d%c",a[i],i==8?\'\\n\':\' \');
    return 0;
}

 

五、交换排序-冒泡排序(Bubble Sort)

冒泡排序是最先接触的排序算法,十分的基础,大的向下沉,小的往上冒

 

#include <bits/stdc++.h>
using namespace std;
void BubbleSort(int *a,int n)
{
    for(int i=0;i<n-1;i++)
    {
        for(int j=0;j<n-1-i;j++)
            if(a[j]>a[j+1]) swap(a[j],a[j+1]);
    }
}
int main()
{
    int a[9]={0,-1,-5,-7,-2,-4,-9,-3};
    BubbleSort(a,8);
    for(int i=0;i<8;i++)
        printf("%d%c",a[i],i==7?\'\\n\':\' \');
    return 0;
}

 

简单标记,若数组已经有序的话,直接跳出循环,减少循环次数。

#include <bits/stdc++.h>
using namespace std;
void BubbleSort(int *a,int n)
{
    for(int i=0;i<n-1;i++)
    {
        bool flag=false;
        for(int j=0;j<n-1-i;j++)
            if(a[j]>a[j+1]) swap(a[j],a[j+1]),flag=true;
        if(!flag) break;
    }
}
int main()
{
    int a[9]={0,-1,-5,-7,-2,-4,-9,-3};
    BubbleSort(a,8);
    for(int i=0;i<8;i++)
        printf("%d%c",a[i],i==7?\'\\n\':\' \');
    return 0;
}

每次冒泡正向来,只能得到一个最大或最小值,但可以正反两次得到一个最大,一个最小。

#include <bits/stdc++.h>
using namespace std;
void BubbleSort_Adjust(int *a,int n)
{
    int l=0,r=n-1;
    while(l<r)
    {
        bool flag=false;
        for(int i=l;i<r;i++)
            if(a[i]>a[i+1]) swap(a[i],a[i+1]),flag=true;
        if(!flag) break;
        r--;
        for(int i=r;i>l;i--)
            if(a[i]<a[i-1]) swap(a[i],a[i-1]);
        l++;
    }
}
int main()
{
    int a[9]={0,-1,-5,-7,-2,-4,-9,-3};
    BubbleSort_Adjust(a,8);
    for(int i=0;i<8;i++)
        printf("%d%c",a[i],i==7?\'\\n\':\' \');
    return 0;
}

六、交换排序-快速排序(Quick Sort)

快速排序,运用了分冶思想递归实现。每次先选定一个基准元素(pivot),然后分割数组,使得基准元素之前的元素都小于基准元素,基准元素之后的元素的元素都大于

基准元素。

之后递归基准元素左边,以及右边。

#include <bits/stdc++.h>
using namespace std;
int findpivot(int *a,int left,int right)
{
    int key=a[left];
    while(left<right)
    {
        while(left<right && a[right]>key) right--;
        swap(a[left],a[right]);
        while(left<right && a[left]<key) left++;
        swap(a[left],a[right]);
    }
    return left;//a[left]==key;
}
void QuickSort(int *a,int left,int right)
{
    if(left>=right) return ;
    int mid=findpivot(a,left,right);
    QuickSort(a,left,mid-1);
    QuickSort(a,mid+1,right);
}
int main()
{
    int a[9]={0,-1,-5,-7,-2,-4,-9,-3};
    QuickSort(a,0,7);
    for(int i=0;i<8;i++)
        printf("%d%c",a[i],i==7?\'\\n\':\' \');
    return 0;
}

 快速排序平均的运行时间为O(nlogn),但最慢时也会达到O(n^2),因此是一个不稳定的算法,因为遇上数列基本有序时,就相当于冒泡排序,效率降低,因此可以

快速排序与插入排序相结合,进行简单优化。

#include <bits/stdc++.h>
using namespace std;
int findpivot(int *a,int left,int right)
{
    int key=a[left];
    while(left<right)
    {
        while(left<right && a[right]>key) right--;
        swap(a[left],a[right]);
        while(left<right && a[left]<key) left++;
        swap(a[left],a[right]);
    }
    return left;//a[left]==key;
}
void QuickSort_Advance(int *a,int left,int right,int k)
{
    if(right-left<=k) return ;
    int mid=findpivot(a,left,right);
    QuickSort_Advance(a,left,mid-1,k);
    QuickSort_Advance(a,mid+1,right,k);
}
void QuickSort(int *a,int left,int right,int k)
{
    QuickSort_Advance(a,left,right,k);//k任意指定,但<=right;
    for(int i=1;i<=right;i++)
    {
        if(a[i]<a[i-1])
        {
            int j=i-1,x=a[i];
            a[i]=a[i-1];
            while(x<a[j-1] && j>0) a[j]=a[j-1],j--;
            a[j]=x;
        }
    }
}
int main()
{
    int a[9]={0,-1,-5,-7,-2,-4,-9,-3};
    QuickSort(a,0,7,3);
    for(int i=0;i<8;i++)
        printf("%d%c",a[i],i==7?\'\\n\':\' \');
    return 0;
}

 

七、归并排序(Merge Sort)

归并算法是将两个有序表合成一个有序表,因此一个序列可以不断二分成若干子序列,当子序列里只有一个或者零个元素时,这个子序列一定是有序的,将两个子序列按有序重写完成合并。因此归并排序是用分冶的方法递归实现。

 

//归并排序递归实现
#include <bits/stdc++.h>
using namespace std;
void MergeArrays(int *a,int left,int mid,int right,int *b)
{
    int l=left,r=mid+1,t=0;
    while(l<=mid && r<=right) b[t++]=a[a[l]<=a[r]?l++:r++];
    //每次a[i],a[j]比较取较小。
    while(l<=mid) b[t++]=a[l++];
    while(r<=right) b[t++]=a[r++];
    //i,j必有一个没有达到端点处
    t=0;
    while(left<=right) a[left++]=b[t++];
}
void MergeSort(int *a,int left,int right,int *b)
{
    if(left>=right) return ;
    int mid=left+right>>1;
    MergeSort(a,left,mid,b);
    MergeSort(a,mid+1,right,b);//分割
    MergeArrays(a,left,mid,right,b);//合并
}
int main()
{
    int a[15]={-789,0,1,7,9,2,6,55,-1,90,-9,1100},b[15];
    MergeSort(a,0,11,b);
    for(int i=0;i<12;i++)
        printf("%d%c",a[i],i==11?\'\\n\':\' \');
    return 0;
}

 

八、基数排序/桶排序(Radix Sort)

         基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

  注意:通过键值来排序,例如一学院学生身高排序,同一个年级里面身高从高到底排序,不同年级的话,低年级在前,高年级在后,则应该把相同年级放在一起从高到低排序,然后按年级连接起来。

  以12,14,67,89,238,344,1,9,10,78为例子:

以个位数为键值:

以十位数为键值:

 

以百位数为键值:

代码实现数字排序(包含负数)

#include <bits/stdc++.h>
using namespace std;
vector<int>v[10],fv[10],s,fs;
int a[1000];
int n,x,maxnum,k=0;
void RadixSort(vector<int>& s,vector<int>& fs,int k,int *a)
{
    int pos=1,t=0;
    for(int z=0;z<k;z++)
    {
        for(int i=0;i<=9;i++) v[i].clear(),fv[i].clear();//容器清空
        for(int i=0;i<s.size();i++)
            v[s[i]/pos%10].push_back(s[i]);
        s.clear();
        for(int j=0;j<=9;j++)
            for(int i=0;i<v[j].size();i++)
                s.push_back(v[j][i]);
        for(int i=0;i<fs.size();i++)
            fv[fs[i]/pos%10].push_back(fs[i]);
        fs.clear();
        for(int j=9;j>=0;j--)//负数,大的在前
            for(int i=0;i<fv[j].size();i++)
                fs.push_back(fv[j][i]);
        pos*=10;
    }
    for(int i=0;i<fs.size();i++) a[t++]=-1*fs[i];
    for(int i=0;i<s.size();i++) a[t++]=s[i];
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&x);
        maxnum=max(maxnum,abs(x));
        if(x<0) fs.push_back(abs(x));
        else s.push_back(x); //正数,负数分开
    }
    while(maxnum) k++,maxnum/=10;
    RadixSort(s,fs,k,a);
    for(int i=0;i<n;i++)
        printf("%d%c",a[i],i==n-1?\'\\n\':\' \');
    return 0;
}
/*
15
-7896654 -12 -1 -6 8 9654 1 2 0 -9999 -6 -3 56 456321 23
10
98754 12 3568 45 0 1 23 69 74 12365
 */

 

 

以上是关于经典排序算法(更新中。。。)的主要内容,如果未能解决你的问题,请参考以下文章

初阶数据结构——经典排序算法(待更新)

10分钟看懂10大经典算法(Swift代码实现)

十大经典排序算法动画与解析,看我就够了!(配代码完全版)

十大经典排序算法最强总结(含Java代码实现)

计理02组06号十大经典排序算法上篇

《数据结构与算法》十大经典排序算法动图演示(2019最新Python代码)