学习笔记——排序

目录

 

简单排序

冒泡排序

插入排序

选择排序

快速排序

合并排序

堆排序

计数排序

桶排序

基数排序


简单排序

冒泡排序

每次比较相邻的两个元素,把较大(小)者换到后面,每一趟能使一个在无序序列中最大(小)的元素确定自己的位置。执行n趟即可使整个数组有序,时间复杂度最好为O(N),平均、最坏都为O(N^2),空间复杂度O(1),是稳定的。

void BubbleSort(Item a[],int n)
{
    for(int i=0;i<n-1;i++)    //执行n趟
        for(int j=0;j<n-1-i;j++)    //第i趟的a[n-i]~a[n]已经排好序,不用重复比较
        {
            if(a[j]>a[j+1])    //以升序排列为例
            {
                swap(a[j],a[j+1]);
            }
        }
}

插入排序

每次把无序序列的第一个元素来与有序序列中的元素逐个比较,插在合适的位置,形成新的有序序列,执行n次,直到整个序列都是有序的。这种方法能保证子前序列都是排好序的。时间复杂度最好为O(N),平均、最坏为O(N^2),空间复杂度为O(1),是稳定的。

void InsertSort(Item a[],int n)
{
    for(int i=1;i<n;i++)    //执行n趟,每趟i都指向无序序列的第一个元素
    {
        int j=i-1;    //初始化j,指向有序序列的最后一个元素
        while(j>1&&a[j]>a[i])    //当待插入元素小于当前j指向的元素,则将a[j]往后移一位,为待插入元素腾位置
        {
            a[j]=a[j-1];    //再继续检查前面的所有元素,直到a[j]<a[i],此时j指向的就是需要插入的位置
            j--;
        }
        a[j]=a[i];
    }
        
}

选择排序

每次从无序序列中找出最值,然后与无序序列的第一个元素交换位置,这种方法能保证子前序列都是排好序的。时间复杂度最好、最坏、最差都为O(N^2),因为无论是否有序都要一个个探测最小值。空间复杂度为O(1),是不稳定的,例如:[5 8 5 2 9],第一个5与2交换后,就到了第二个5后面了,它们的相对位置改变了。

void SelectSort(Item a[],int n)
{
    for(int i=0;i<n;i++)
    {
        for(int j=i,min_pos=i;j<n;j++)    //a[i]~a[n]是无序区间,初始化最小元素下标为i
        {
            if(a[j]<a[min_pos])    //更新最小元素下标
            {
                min_pos=j;
            }
        }
        swap(a[i],a[min_pos])    //交换无序区间第一个元素与无序区间最小元素的位置
    }
}

快速排序

以数组中某个元素为枢轴(划分基准),每趟递归调用都使枢轴左边都是小于枢轴的值,右边都是大于枢轴的值,因此每次递归调用都能确定一个元素的最终位置。递归结束的条件是L>=R,即待排序的序列中只有一个元素时,它显然有序,则结束当前递归返回上一层递归。

void QuickSort(Item a[],int l,int r)
{
    if(l>=r) return;    //序列中只有一个元素时结束递归
    int i=partition(a,l,r);    //获得枢轴的下标
    QuickSort(a,l,i-1);    //对枢轴左半部分调用快排
    QuickSort(a,i+1,r);    //对枢轴右半部分调用快排
}

int partition(Item a[],int l,int r)
{
    //规定一个元素为枢轴,并获得它的最终位置,使在它左边的数都小于它,在它右边的数都大于它
    //让元素a[l]的值作为枢轴值
    int x=a[l];
    int i=l,j=r;
    while(i!=j)
    {
        while(j>i&&a[j]>=x) j--;
        if(j>i)    //此时退出while是因为j指向比枢轴值小的数
        {
            a[i]=a[j];    //直接覆盖i指向的元素,放在枢轴左边
            i++;
        }
        while(j<i&&a[i]<x) i++;
        if(j>i)    //此时i指向比枢轴值大的数
        {
            a[j]=a[i];    //放在枢轴右边
            j--;
        }
        //由于第一次覆盖的元素就是枢轴,是我们已知的,所以直接覆盖不会丢失数据。
        //而在这之后被覆盖的元素都是无关紧要的,因为它的值在上一步已经去了它该去的位置了。
        //执行到最后,只有枢轴值x不知道它该去的位置在哪里,显然,就是i=j的位置。
    }
    a[i]=x;
}

合并排序

将待排序列分成大小大致相等的左右两端,接着依次分别对这两端子序列递归地进行合并排序,直到序列中只有一个元素时,结束递归。每一轮递归结束时,某区域左右两边都分别有序。此时,再将排好的已经有序的两个子序列合并为一个序列,放在一个工作区,不断返回上一层递归,直到合并成原来长度的序列。最后用工作区排好序的全序列去更新原来待排序的元素序列。

void MergeSort(Item a[],int l,int r)
{
    int mid=(l+r)/2;
    if(r<=l) return;
    MergeSort(a,l,mid);
    MergeSort(a,mid+1,r);
    MergeAB(a,b,l,m,r);
}


void MergeAB(Item a[],Item b[],int l,int m,int r)
{
    int i=l,j=mid+1,k=0;    //指针指向两个子序列的第一个元素
    while(i<=mid&&j<=r)    //若未超出所指示的子序列,则将较小值存入工作区b
    {
        if(a[i]<a[j]) b[k++]=a[i];
        else b[k++]=a[j]; 
    }
    if(i>m)    //若i先超出所子序列,则只需把j还未遍历的数组全部赋给工作区b
    {
        while(j<=r) b[k++]=a[j++];
    }
    else       //若i先超出所子序列,则只需把j还未遍历的数组全部赋给工作区b
        while(i<=mid) b[k++]=a[i++];
    for(int i=l;i<=r;i++)
    {
        a[i]=b[i];    //再把工作区b里的序列复制到数组a去
    }
}

堆排序

把待排序的序列按完全二叉树的结构特点存入数组,关键字存储必须从1开始,数组元素i的左孩子为2i,右孩子为2i+1。建好堆之后,要进行排序,对一个有n个元素的序列进行排序,相当于执行n次堆的调整,输出当前最小(大)的元素之后,用数组中最后一个元素(即完全二叉树的最后一个叶子结点)取代堆顶元素,引起堆的再次调整。

void Sift(Item R[],int low,int high)
{
    //这是一个自上而下的堆调整
    int i=low,j=2*i;    //j是i的左孩子
    int temp=R[i];
    while(j<=high)
    {
        if(j<high&&R[j]<R[j+1])    //右孩子更大
        {
            j++;    //j指向右孩子
        }
        /*以上操作使j指向左右孩子中较大的一个*/
        if(temp<R[j])
        {
            R[i]=R[j];
            i=j;    //i下降,不用管另一个孩子,因为R[i]与R[j]的交换没有影响到另一个孩子及其子树
            j=i*2;    //j仍指向i的左孩子
        }
        else break;
    }
    R[i]=temp;    //被调整结点的值放入最终位置
    //最终要找到一个R[j]不大于当前根节点R[low]的地方将temp放进去,如果j走到最后都没有这样的位置,说明R[low]就是最大的,就把一开始放在temp里的值再还给R[low]
}

/*堆排序函数*/
void HeapSort(Item R[],int n)
{
    for(int i=n/2;i>=1;i--)    //从最后一个叶子结点的父节点开始,自下而上,自右向左调整,每次以结点i为堆的根结点
        Sift(R,i,n);
    /*上面是堆的初始化,下面是每一轮最值的输出和后续的调整*/
    for(int i=n;i>=2;i--)    //从最后一个叶子结点开始,自下而上,自右向左,每次用最后一个叶子结点替换堆顶结点,从而引起新一轮的调整
    {
        temp=R[1];
        R[1]=R[i];
        R[i]=temp;
        Sift(R,1,i-1);    //开始新的一轮调整是从下到下,从左到右的,由于第i个元素已经有序,所以只需要调整到第i-1个元素
    }
}

也就是说,堆排序的排序过程就是从最后一个叶子结点的父节点开始,从右到左,从下到上地,对以该节点为根的树再进行从上到下,从左到右的调整,直到整个完全二叉树中的每个子树都满足大(小)根堆。输出堆顶后,用最后一个叶子结点替换堆顶结点,此时的堆可能不满足大(小)根堆的性质,需要重新进行调整,此时只需要自堆顶从上往下,从左往右调整即可,

计数排序

找出序列的最大值max和最小值min,以它们的差dist作为长度开辟一个数组空间c,遍历序列a,每次访问到一个a[i],就执行c[min+i]++,最后按顺序将c[min+i]个a[i]放到一个长度为length(a)的数组中去。

桶排序

比如对2位数进行排序,先根据基数申请10个桶,再按十位的取值将元素放进相应的桶中,然后对每个桶的内部进行排序,将排好序的桶内元素拼接起来就得到有序序列。

基数排序

其实思路跟桶排序差不多,但只适用于整数排序 ,先以个位为基准进行排序,排序结果拼接起来成为一个假有序序列,再对这个序列以十位为基准进行排序,排序结果拼接起来得到一个真正有序的序列。

图片转载自:https://www.jianshu.com/p/134a0eed5e3a

                      https://www.jianshu.com/p/0807c3557dc2

                      https://www.jianshu.com/p/1d04c34defd0

发布了35 篇原创文章 · 获赞 2 · 访问量 1388

猜你喜欢

转载自blog.csdn.net/weixin_41001497/article/details/102489066