zoukankan      html  css  js  c++  java
  • 排序(N+1种)

    from large to small

    选择排序:

    • 算法描述:
    1. 输入a[n]
    2. a[1]~a[n]
    3. a[2]~a[n]          a[i]~a[n]

    找最小的,与a[1]交换

    找最小的,与a[2]交换

    ......

    找最小的,与a[i]交换

    i=n-1

    such as this

     代码:

    #include<iostream>
    using namespace std;
    const int Maxn=10001;
    int n,k,i,j;
    double temp,a[Maxn];
    int main(){
        scanf("%d",&n);
        for(i=1;i<=n;i++)
        scanf("%d",&a[i]);
        for(i=1;i<=n;i++){
            k=i;//标记
            for(j=i+1;j<=n;j++)
            if(a[i]<a[j])
            k=j;
            if(k!=i)
            swap(a[i],a[k]);//进行交换
        }
        for(i=1;i<=n;i++)
        printf("%d ",a[i]);
        return 0;
    }

    冒泡排序:

    就是使最矮的数,像鱼吐泡泡一样升上来。

    比较相邻两数是否为逆序对,若是则交换,不是则继续搜寻下一位

    例如:

    第一次

    第二次

    第三次

    第四次

    第五次

    例题:洛谷P1116

    传送门

    跳过一般的冒泡排序直接进入加强版

    这里是求转换次数,并非输出结果

    #include<bits/stdc++.h>
    using namespace std;
    long n,i,j,t,s,a[10000];
    int main()
    {
        cin>>n;
        for(i=1;i<=n;i++)
        cin>>a[i];
        for(i=1;i<=n;i++)
        for(j=1;j<=n-1;j++)
        if(a[j]>a[j+1])
        {
            swap(a[j],a[j+1]);
            s++;
        };
        cout<<s;
        return 0;
    }

     输出结果的方法:

    //在cout<<s;前加上
    for(i=1;i<=n;i++)
        cout<<a[i]<<“ ”;
    //这样既可

    在这一题中是由小到大

    插入排序:
    回忆打扑克的时候的样子,就是选择大小花色来插入适当的位置,

    这就叫做插入排序

    当读入一个元素时,在已经排序好的序列中,搜寻它正确的位置,再放入读入的元素。

    当然,其中有一个不容忽略的重要问题

    在插入这个元素前,应当先将将它后面的所有元素后移一位,以保证插入位置的原元素不被覆盖。

    示例:

    代码在这:

    #include<iostream>
    using namespace std;
    const int MAXN=10001;
    int main()
    {
          int n,i,j,k;  
          float temp,a[MAXN];
          cin>>n;
          for (i=0;i<n;i++)
             cin>>a[i];                      //输入n个数 
        for(i=0; i<n; i++)
        { 
           for(j=i-1; j>=0; j--)          //在前面有序区间中为a[i]找合适的插入位置 
             if (a[j]<a[i]) break;        //找到比a[i]小的位置就退出,插入其后 
           if (j!=i-1)
           {
              temp=a[i];                   //将比a[i]大的数据向后移 
              for(k=i-1;k>j;k--)
                 a[k+1]=a[k];              //将a[i]放在正确位置上 
              a[k+1]=temp;
           }
        }
        for (i=0;i<n;i++)
            cout<<a[i]<<" ";            //输出排序的结果 
       return 0; 
    }

    洛谷P1059

    明明的随机数

    次元穿梭

    #include<bits/stdc++.h>
    using namespace std;
    set<int>s;/*根据适应性原则,这题除了排序还要计算不同数的数量
    再接着排序,所以说在STL中适用于这种情况的就是集合
    运用集合中不允许存在相同数的原则,即可轻松解决不同数的数量问题
    然后在此集合中进行排序,便可轻松进行秒杀*/
    int a[101];
    int main()
    {
        int n;
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            s.insert(a[i]);//输入
        }
        cout<<s.size()<<endl;//计算大小
        while(!s.empty())
        {
            cout<<*s.begin()<<" ";
            s.erase(s.begin()); 
        }
    }

    不完全是排序,有一部分STL表在其中。

    快速排序:

    快速排序是对冒泡排序的一种改进。

    它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小

    则可分别对这两部分记录继续进行排序,以达到整个序列有序。 假设待排序的序列为{a[L],a[L+1],a[L+2],……,a[R]}

    首先任意选取一个记录(通常可选中间一个记作为枢轴或支点),然后重新排列其余记录,将所有关键字小于它的记录都放在左子序列中

    所有关键字大于它的记录都放在右子序列中。由此可以将该“支点”记录所在的位置mid作分界线

    将序列分割成两个子序列和。这个过程称作一趟快速排序(或一次划分)。 一趟快速排序的具体做法是:附设两个指针i和j

    它们的初值分别为L和R,设枢轴记录取mid,则首先从j所指位置起向前搜索找到第一个关键字小于的mid的记录,然后从i所指位置起向后搜索

    找到第一个关键字大于mid的记录,将它们互相交换,重复这两步直至i>j为止。

    (直接copyc++一本通的,因为讲的好清楚,自己写的肯定没这么清晰。)

    所以说,快排是运用范围最广的。(乱讲分明是sort)

    例题:洛谷P1177

    传送门

    直接上代码吧:

    #include<iostream>
    using namespace std;
    int q[10000000];
    void qsort(int a,int b)
    {
        int i,j,mid,p;
        i=a;
        j=b;
        mid=q[(a+b)/2];
        do
        {
            while(q[i]<mid)
            i++;
            while(q[j]>mid)
            j--;
            if(i<=j)
            {
                p=q[i];q[i]=q[j];q[j]=p;
                i++;j--;
            }
        }while(i<=j);
        if(a<j)
        qsort(a,j);
        if(i<b)
        qsort(i,b);
    }
    int main()
    {
        int n,i;cin>>n;
        for(i=1;i<=n;i++)
        cin>>q[i];
        qsort(1,n);
        for(i=1;i<=n;i++)
        cout<<q[i]<<" ";
        return 0;
    }

    其实快排有一个函数,只是理解它的运算更为重要

    sort(a[i],b[i],cnt);

     桶排序:

    这个,就是拿桶倒来倒去。

    (开玩笑的啦)划掉

    桶排序的思想是若待排序的值在一个明显有限范围内(整型)时

    可设计有限个有序桶,待排序的值装入对应的桶(当然也可以装入若干个值)

    桶号就是待排序的值,顺序输出各桶的值,将得到有序的序列。

    其实很类似于数组排序。(毕竟看着就像啊)

    代码:

    #include<iostream>
    #include <cstring>
    using namespace std;
    int main(){ 
         int b[101],n,i,j,k;
         memset(b,0,sizeof(b));             //初始化
         cin>>n;
         for (i=1;i<=n;i++)
         {
             cin>>k; b[k]++;                     //将等于k的值全部装入第k桶中
         }
         for (i=0;i<=100;i++)                  //输出排序结果
           while (b[i]>0)                          //相同的整数,要重复输出
           {
               cout<<i<<" "; b[i]--;             //输出一个整数后,个数减1
            }
          cout<<endl;    
    }

    有一定的局限性,必须在一定范围内,此话极度重要。

    但是利用的好可以对极大部分算法进行优化。

    归并排序:

    传说中的归并大发

    采用分治算法,分而治之

    把一整个数组,从中间对半分开,一只分到只有一个数,再进行比对

    然后,合并;

    整个归并排序中,只有两大步骤:分解,合并

    将已有序的子序列合并,得到完全有序的序列;

    即先使每个子序列有序,再使子序列段间有序。

    若将两个有序表合并成一个有序表,称为二路归并。

    合并操作:

    比较a[i]和a[j]的大小,若a[i]≤a[j]

    则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;

    否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。

    归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分

    接着把左边子区间排序,再把右边子区间排序

    最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。

    好的呀,归并最实用的于求逆序对:

    函数哦:

    void msort(int s,int t){
        if(s==t) return; 
        int mid=(s+t)/2;
        msort(s,mid); 
        msort(mid+1,t);    
        int i=s, j=mid+1, k=s;
        while(i<=mid && j<=t)
        {
              if(a[i]<=a[j])  
              {
                    r[k]=a[i]; k++; i++;
               }else{
                            r[k]=a[j]; k++; j++;
                        }
          }  
          while(i<=mid)       
          {
               r[k]=a[i]; k++; i++;
           }
           while(j<=t)   
           {
                r[k]=a[j]; k++; j++;
            }
            for(int i=s; i<=t; i++) a[i]=r[i];  
            return 0;
    } //爱玩的哥们直接调用就行;

    例题:洛谷P1908

    次元突破

    #include<bits/stdc++.h>
    using namespace std;
    int p[40005],ans=0;
    void merge(int a[],int l,int r)//函数中便是归并大法
    {
        if(l==r)
        return;//如果只有一个数字则返回,无须排序
        int half=(l+r)/2;
        merge(a,l,half);//分解左序列
        merge(a,half+1,r); //分解右序列
        int i=l,j=half+1,q=l;//接下来合并
        while(i<=half&&j<=r)
        {
            if(a[i]>a[j])
            {
                p[q++]=a[j++];
                ans+=half-i+1;    
            }
        else
        p[q++]=a[i++];
        }
        while(i<=half)//复制左边子序列剩余
        p[q++]=a[i++];
        while(j<=r) //复制右边子序列剩余
        p[q++]=a[j++];
        for(i=l;i<=r;i++)
        a[i]=p[i];
    }
    int main()
    {
        int n,a[40005];
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
        merge(a,1,n);
        printf("%d",ans);
        return 0;
    }

     让我们来比较一下各个排序算法吧!!!

    稳定性比较:

    插入排序、冒泡排序、二叉树排序、二路归并排序及其他线形排序是稳定的。  

    选择排序、希尔排序、快速排序、堆排序是不稳定的。

    好的呀,除了希尔排序和二叉树排序以及二路归并排序都木有,其他的都可以比较

    时间复杂性比较:

     插入排序、冒泡排序、选择排序的时间复杂度为O(n2

    快速排序、堆排序、归并排序的时间复杂度为O(nlog(2n))

    桶排序的时间复杂度为O(n)

    其中

    在最好情况下,直接插入排序和冒泡排序最快;

    在平均情况下,快速排序最快;

    在最坏情况下,堆排序和归并排序最快。

  • 相关阅读:
    SPOJ 694 (后缀数组) Distinct Substrings
    POJ 2774 (后缀数组 最长公共字串) Long Long Message
    POJ 3693 (后缀数组) Maximum repetition substring
    POJ 3261 (后缀数组 二分) Milk Patterns
    UVa 1149 (贪心) Bin Packing
    UVa 12206 (字符串哈希) Stammering Aliens
    UVa 11210 (DFS) Chinese Mahjong
    UVa (BFS) The Monocycle
    UVa 11624 (BFS) Fire!
    HDU 3032 (Nim博弈变形) Nim or not Nim?
  • 原文地址:https://www.cnblogs.com/U58223-luogu/p/9463457.html
Copyright © 2011-2022 走看看