排序算法综合整理

排序算法综合整理

一、直接插入排序

  • 从小到大排序:从第二个数开始,若非递增,则插入前面有序序列中,该位置前面一位小于它,后面一位大于它;

  • 二分插入排序:因为前面序列有序,则可用二分找到该位置;

用了二分可以减少比较大小的次数

撸代码:

#include<stdio.h>
int main()
{
    int a[10]={56,32,12,43,78,66,23,44,21,58};
    /**从小到大  直接插入排序*/
    for(int i=1;i<10;i++)
    {
        if(a[i]<a[i-1])/**若存在逆序,则向前找小于a[i]*/
        {
            int temp=a[i],j=i-1;
            while(j>=0&&temp<a[j])/**在找的过程中,大于a[i]的向后推1位*/
            {
                a[j+1]=a[j];
                j--;
            }
            a[j+1]=temp;
        }
    }
    printf("直接插入排序后:\n");
    for(int i=0;i<10;i++)
        printf("%d ",a[i]);
    return 0;
}

二、希尔排序

直接插入排序每次跨度为1,而希尔排序不断改变跨度(增量,在增量下“大致有序”),多次直接插入排序。

撸代码:

#include<stdio.h>
void shellPass(int a[],int d)
{
    for(int i=d;i<10;i++)
    {
        if(a[i]<a[i-d])
        {
            int temp=a[i],j=i-d;
            while(j>=0&&temp<a[j])
            {
                a[j+d]=a[j];
                j-=d;/**跨度不同*/
            }
            a[j+d]=temp;
        }
    }

}
int main()
{
    int a[10]={56,32,12,43,78,66,23,44,21,58};
    int len=10;
    while(len)
    {
        len=len/2;
        shellPass(a,len);/**多次改变增量*/
    }
    printf("希尔排序后:\n");
    for(int i=0;i<10;i++)
    {
        printf("%d ",a[i]);
    }
    return 0;
}

三、归并排序

给一个序列 9 1 0 5 4,进行归并排序:(从小到大)

下标01234
数值91054

单个元素为一组,两两合并为有序序列:

下标0 12 34
数值1 90 54

2个元素为一组,每两组合并:

下标0 1 2 34
数值0 1 5 94

最后两组合并:

下标0 1 2 3 4
数组0 1 4 5 9

在合并的过程中,就相当于两个有序序列合成一个有序序列

那么怎么分成两组呢?

递归派上了用场!从1~n开始分,知道分不了,利用回溯进行排序,完全ok

怎样求逆序数

在两个有序序列合并的时候,一个是left序列,一个是right序列,当right序列某个元素跑到left前面了,计算一下,left的长度减去跑的位置,就是当前这个数的逆序啦

撸代码:

#include<stdio.h>
#define N 500050
long long ans;
int a[N],n,t[N];
void Sort(int l,int mid,int r)
{
    int i=l,j=mid+1;/**将分开的左右两个有序链表合并*/
    int k=l;
    while(i<=mid&&j<=r)
    {
        if(a[i]<=a[j])
        {
            t[k++]=a[i++];
        }
        else
        {
            t[k++]=a[j++];
            ans+=mid-i+1;/**统计 a[j]的 逆序数*/
        }
    }
    while(i<=mid)
    {
        t[k++]=a[i++];
    }
    while(j<=r)
    {
        t[k++]=a[j++];
    }
    for(int i=l;i<=r;i++)
    {
        a[i]=t[i];
    }
    return ;
}
void departSort(int l,int r)
{
    if(l<r)
    {
        int mid=(l+r)>>1;
        departSort(l,mid);
        departSort(mid+1,r);/**分完之后,排序*/

        Sort(l,mid,r);
    }
    return ;
}

int main()
{
    while(~scanf("%d",&n)&&n)
    {
        ans=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        departSort(1,n);/**归并排序*/
        printf("%lld\n",ans);
    }
    return 0;
}

四、快速排序

从小到大

思想:先2分块,确定基准元素,保证块之间有序,也就是说 左块任意数字小于右块任意数字,块内无序,再对每一块分2块,层层递归;

优化:减少基准元素值的交换次数,先找到两个位置 ,左边大于基准元素,右边小于基准元素,两个值交换,直至遍历所有数字后,才基准元素交换;

关于递归优化不是太明白,优化前是quickSort函数两次递归,优化后一次递归,应该是在这里减少了堆栈深度。

撸代码:

#include<stdio.h>
#include<iostream>
using namespace std;
int Partition(int a[],int low,int hight)
{
    int pivotKey=a[low];/**基准元素值(枢轴)*/
    while(low<hight)
    {
        while(low<hight&&a[hight]>=pivotKey)/**从右开始,若小于基准元素,则交换位置*/
            hight--;
        swap(a[low],a[hight]);/**小于基准元素的值跑到左边*/
        while(low<hight&&a[low]<=pivotKey)/**从左开始*/
            low++;
        swap(a[low],a[hight]);
    }
    return low;
}

/*******优化 不必要的交换(还可优化基准元素的选取,防止选到极大极小值)*********/
/*数组中分三次取样,每次取三个数,三个样品中各取出中间数,然后在这三个中枢当中再取一个中间数作为枢轴*/
int Partition1(int a[],int low,int hight)
{
    int startIndex=low;/**基准元素只交换一次*/
    int pivotKey=a[low];
    while(low<hight)
    {
        while(low<hight&&a[hight]>=pivotKey)
            hight--;
        while(low<hight&&a[low]<=pivotKey)
            low++;
        swap(a[low],a[hight]);/**找到左边大的,右边小的 ,交换一次*/
    }
    /**循环执行完,左小右大,low就是基准元素该去的位置*/
    swap(a[low],a[startIndex]);
    return low;
}


void quickSort(int a[],int low,int hight)
{
    int pivot;
    if(low<hight)
    {
        pivot=Partition(a,low,hight);/**基准元素下标*/
        quickSort(a,low,pivot-1);/**分块排序,保证块之间有序*/
        quickSort(a,pivot+1,hight);
    }

    /*********递归优化:减少堆栈深度**********/
    /*
    while(low<hight)
    {
        pivot=Partition(a,low,hight);
        quickSort(a,low,pivot-1);
        low=pivot+1;
    }
    */

}
int main()
{
    int a[10]={5, 1, 9, 3, 7, 4, 8, 6, 2};
    int n=9;
    quickSort(a,0,n-1);/**从小到大排序*/
    for(int i=0;i<n;i++)
        printf("%d ",a[i]);
    return 0;
}

五、堆排序

  1. 大根堆,小根堆:所有非叶子节点大于或者小于其孩子节点。

  2. 用大根堆进行从小到大的排序

  3. 建立大根堆:从下往上,从右往左遍历非叶子节点,判断其是否符合大根堆性质,若不符合,则交换节点位置,直至建出大根堆。

  4. 大根堆根节点一定是被排序的这段数值的最大值,交换堆尾堆首数值,堆尾指针前移*(有没有冒泡的感觉?最大值逐渐飘到堆尾)*

  5. 当前堆只有根节点不符合大根堆性质,所以从根节点开始,向下找到合适的位置即可

#include<stdio.h>
#include<iostream>
using namespace std;

/**刚还奇怪,为什么从小到大排序用的是大根堆?
仔细看,建好堆以后,根节点是最大值,可是!!!
交换了堆尾和堆首的值,那么最大值就跑到了后面.
交换后,这个堆尾就算最大值了 ,不用管,再排序前面 n-1 个节点
同理,每次最大值都跑到了后面
*/


/**以 k 为根的完全二叉树,分别以2*K 和 2*k+1 为根的左右子树为大根堆*/
/**使 k 为根的树满足堆的性质*/
void sift(int a[],int k,int m)
{
    int x=a[k];
    int i=k;
    int j=k*2;
    bool finish=false;
    while(j<=m&&!finish)
    {
        if(j+1<=m&&a[j]<a[j+1])
            j=j+1;/**此时 j 指向最大子树根节点*/

        if(x>=a[j])
            finish=true;
        else/**若是比子树根小,交换后还得考虑下层大小关系*/
        {
            a[i]=a[j];
            i=j;
            j=2*i;
        }
    }
    a[i]=x;
}
void crateHeap(int a[],int n)
{
    /**从最后一个非叶子节点往上筛选建堆*/
    for(int i=n/2;i>=1;i--)
        sift(a,i,n);
}
void heapSort(int a[],int n)
{
    /**建大根堆后 ,保证了下面的数字不会大于上面的数字*/
    crateHeap(a,n);
    /**对 a 数组进行 从小到大 堆排序*/
    for(int i=n;i>=2;i--)
    {
        /**每次得到的最大值跑到了后面*/
        swap(a[i],a[1]);


        /**交换以后只有堆顶元素是不合法的,所以下面只是为了
        给堆顶元素找到一个合适的位置*/
        /**对a[1~i-1]调整成堆*/
        sift(a,1,i-1);
    }
}
int main()
{
    int a[12]={0,88,32,34,12,34,66,52,33,25,20};
    heapSort(a,10);
    for(int i=1;i<=10;i++)
        printf("%d ",a[i]);
    return 0;
}

六、计数排序

排序思想: 对于数组 a[ ] 排序 ,先用数组c[ a[ i ] ] 记录其中的值出现的次数,然后计算前缀和;得出的值的意义就是 对于c[ a[i] ] 的值就是 对于所有的 a[ i ] 最后一个 a[ i ] 在数组中有序的排名,所以借助 ans[ ] 数组记录下标c[a[i] ] 的值为 a[i] ,- - c[ a [ i ] ] 就是 对于所有a[i] ,a[ i ] 的倒数第二个位置。

/**计数排序 1~10以内的数*/
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
void printArr(int a[],int n)
{
    for(int i=0;i<n;i++)
        printf("%d%c",a[i],i==n-1?'\n':' ');
}
void countSort(int a[],int n)
{
    int c[12];
    for(int i=0;i<=11;i++)
        c[i]=0;/**清空*/

    for(int i=0;i<n;i++)
        c[a[i]]++;/**计数*/

    for(int i=1;i<=n;i++)
        c[i]+=c[i-1];/**前缀和*/

    /**
    有了前缀和后,数字最后一个a[j]的排名为c[a[j]]
    所以访问每个数字时,只是把a[j]放到对应的名次上
    因为可能有多个a[j],所以名次可以从最后一个向前分配
    */
    int ans[12];
    for(int j=n-1;j>=0;j--)
    {
        ans[--c[a[j]]]=a[j];
    }
    printf("排序后:\n");
    printArr(ans,10);
}
int main()
{
    int a[12];
    int n=10;
    /*srand函数设定rand函数所用随机数演算法的种子值*/
    //srand(time(NULL));
    /*time(t) t==NULL 返回当前时间,若非空:返回当前时间的同时,将返回值赋予t 指向的内存空间*/
    for(int i=0;i<n;i++)
        a[i]=rand()%7+1;
    printArr(a,10);
    countSort(a,10);
    return 0;
}

七、桶排序

排序思想: 首先通过最大最小值数据范围 maxx-minn 按照每个桶平均装的数量 得出桶的数量。然后遍历数组 a[ ] ,装入桶中,进行桶内排序。

#include<vector>
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<algorithm>
using namespace std;
void bucketSort(int a[],int n)
{
    int maxx=-9999,minn=9999;
    for(int i=0; i<n; i++)
    {
        if(maxx<a[i])
            maxx=a[i];
        if(minn>a[i])
            minn=a[i];
    }
    /**根据数据范围得出桶的数量(最多10个元素1个桶)*/
    int bucketNum=maxx/10-minn/10+1;

    vector<int>v[bucketNum];
    for(int i=0; i<n; i++)
    {
        int t=(a[i]-minn)/10;
        v[t].push_back(a[i]);
    }

    printf("Sort Over:\n");
    for(int i=0; i<bucketNum; i++)
    {
        /**
        桶内排序,可以插入排序,也可以用排序函数
        */
        if(v[i].size())
        {
            sort(v[i].begin(),v[i].end());
            for(int j=0; j<v[i].size(); j++)
                printf("%d ",v[i][j]);
        }
    }
}
int main()
{
    int n=10;
    int a[12];
    for(int i=0; i<n; i++)
        a[i]=rand()%7+1;
    for(int i=0;i<n;i++)
        printf("%d%c",a[i],i==n-1?'\n':' ');
    bucketSort(a,n);
    return 0;
}

八、基数排序

LSD排序思想: 初始化 exp=1 ,先按照低 exp 位进行计数排序,然后按照低 exp+1 位计数排序,直至exp/max=0 ,排序完成。

#include<stdio.h>
#include<algorithm>
using namespace std;
/**LSD排序(从右向左定位)*/
void countSort(int a[],int exp,int n)
{
    int c[10];
    for(int i=0;i<10;i++)
        c[i]=0;

    /**按照位权计数*/
    for(int i=0;i<n;i++)
        c[(a[i]/exp)%10]++;

    for(int i=1;i<10;i++)
        c[i]+=c[i-1];
    int b[20];
    /**暂时按照低exp位排序*/
    for(int i=n-1;i>=0;i--)
        b[--c[(a[i]/exp)%10]]=a[i];

    for(int i=0;i<n;i++)
        a[i]=b[i];
}
void bitSort(int a[],int n)
{
    int maxx=a[0];
    for(int i=1;i<n;i++)
        maxx=max(maxx,a[i]);

    for(int exp=1;maxx/exp>0;exp*=10)/**从右向左定位*/
    {
        /**计数排序*/
        countSort(a,exp,n);
    }
}
int main()
{
    int a[12]={103,9,1,7,15,25,109,209,5};
    int n=9;
    bitSort(a,n);
    printf("基数排序后:\n");
    for(int i=0;i<n;i++)
        printf("%d ",a[i]);

    return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值