排序算法总结

本文详细介绍了直接插入排序、折半插入排序、希尔排序、快速排序、选择排序、堆排序、归并排序和基数排序的原理与实现。各种排序算法在不同场景下的时间复杂度、空间复杂度以及稳定性进行了对比。直接插入排序在基本有序时表现优秀,快速排序平均性能最佳,而归并排序则在稳定性与效率上均有保证。选择排序和堆排序虽然效率较高,但不保证稳定性。基数排序适用于关键字较短且数量大的情况。
摘要由CSDN通过智能技术生成

排序
存储结构

#define MAXSIZE 20
typedef int KeyType
typedef struct
{
    KeyType key;
} RcdType;
typedef struct
{
    RcdType r[MAXSIZE+1];
    int length;
} SqList;

一、插入排序

1.直接插入排序
思路:将无序块首元素与左侧元素由后向前逐个比较,边比较边后移;
最后定位到一个大于等于待插入元素的记录左侧,填入即可

void InsertionSort(SqList &L)
{
    for(int i=2; i<=L.length; i++)
    {
        int j=i-1,k;
        if(L.r[j]>L.r[i])
        {
            L.r[0]=L.r[i];
            L.r[i]=L.r[j];
        }
        for(k=i-2; L.r[k]>L.r[0]; k--)
        {
            L.r[k+1]=L.r[k];
        }
        L.r[k+1]=L.r[0];
    }
}


时间复杂度:最好O(n),最坏O(n的平方)
稳定性:稳定

2.折半插入排序
思路:折半法定位第一个大于待插入元素的记录位置,在其左侧插入

void BinInsertionSort(SqList &L)
{
    for(int i=2; i<=L.length; i++)
    {
        int low=1,high=i-1;
        while(low<=high)
        {
            int mid=(low+high)/2;
            if(L.r[mid].key>L.r[i].key)
                high=mid-1;
            else
                low=mid+1;
        }
        L.r[0]=L.r[i];
        for(int j=i-1; j>=high+1; j--)
        {
            L.r[j+1]=L.r[j];
        }
        L.r[high+1]=L.r[0];
    }
}


时间复杂度:O(n的平方)3.希尔排序
思路:设一个递增的增量序列,如5-3-1
每一趟都将记录序列分成若干子序列,各子序列分别进行直接插入排序
最后一趟增量为1,对全部记录进行直接插入排序

void ShellInsert(SqList &L,int dk)
{
    for(int i=dk+1; i<=L.length; i++)
    {
        if(L.r[i].key<L.r[i-dk].key)
        {
            L.r[0]=L.r[i];
            for(int j=i-dk; j>0&&(L.r[j]>L.r[0]); j-=dk)
            {
                L.r[j+dk]=L.r[j];
            }
            L.r[j+dk]=L.r[0];
        }
    }
}

void ShellSort(SqList &L,int dlta[],int length)
{
    for(int k=0; k<length; k++)
    {
        ShellInsert(L,dlta[k]);
    }
}


时间性能:O(n的1.3次方)
空间性能:O(1)
稳定性:不稳定

二、快速排序
思路:冒泡排序的优化,选择一个元素做枢轴,将原序列一分为二,左侧小于枢轴,右侧大于枢轴
左右两侧子序列重复上述过程,递归完成各自的排序

递归边界:序列长为0或1则不必排序
递归关系:序列长度大于1,则选择某元素做枢轴(如最左侧元素),根据枢轴元素将原序列一分为二
左右两侧子序列通过重复上述过程,递归完成各自的排序

int Partition(SqList L,int low,int high)
{
    L.r[0]=L.r[low];
    while(low<high)
    {
        while(L.r[high]>L.r[0])
        {
            high--;
        }
        L.r[low]=L.r[high];
        while(L.r[low]<L.r[0])
        {
            low++;
        }
        L.r[high]=L.r[low];
    }
    L.r[low]=L.r[0];
    return low;
}
void QSort(SqList L,int low,int high)
{
    if(!(low<high))
        return;
    else
    {
        int pivotLoc=Partition(L,low,high);
        QSort(L,low,pivotLoc-1);
        QSort(L,pivotLoc+1,high);
    }
}


时间复杂度:平均O(N*log以2为底N),最坏O(N的平方)
空间复杂度:平均O(log以2为底N),最坏O(N)
不稳定:3 2 2* -> 2* 2 3

三、选择排序
1.简单选择排序
思路:每一趟选择出当前最小记录,将其交换到无序块的最前面。如此无序块逐渐变小,N-1趟完成。

void selectSort(SqList &L)
{
    int i,j,min,tmp;
    for(int i=1;i<=L.length-1;i++)
    {
        min=i;
        for(int j=i+1;j<=L.length;j++)
        {
            if(L.r[j]<L.r[min])
            {
                min=j;
            }
        }
        tmp=L.r[i];
        L.r[i]=L.r[min];
        L.r[min]=tmp;
    }
}

2.堆排序
一个序列,设它对应某完全二叉树的层序序列,若该二叉树任意一个节点均比其左右孩子节点大(小),
则称该序列为大(小)顶堆
筛选 互换

void HeapAdjust(SqList &H,int head,int tail)
{
    H.r[0]=H.r[head];
    int lchild=head*2,rchild=2*head+1;
    while(lchild<=tail)
    {
        int bigchild;
        if(rchild<tail&&H.r[lchild].key>H.r[rchild].key)
            bigchild=lchild;
        else
            bigchild=rchild;
        if(H.r[0].key<H.r[bigchild].key)
        {
            H.r[head]=H.r[bigchild];
            head=bigchild;
            lchild=head*2;
            rchild=2*head+1;
        }
        else
            break;
    }
    H.r[head]=H.r[0];
}
void HeapSort(SqList &H)
{
    for(int i=H.length/2; i>=0; i--) //初始大顶堆的建立
    {
        HeapAdjust(H,i,H.length);
    }
    for(int i=1; i<H.length; i++)
    {
        H.r[0]=H.r[1];
        H.r[1]=H.r[H.length-i+1];
        H.r[H.length-i+1]=H.r[0];      //交换堆顶堆尾后只有根不符合要求
        HeapAdjust(H,1,H.length-i);
    }
}


时间复杂度:O(n*log以2为底的n)
空间复杂度:O(1)
稳定性:不稳定

四、归并排序

void Merge(SqList L,int low,int mid,int high,SqList T)
{
    int left=low,right=mid+1,result=low;
    while(left<=mid&&right<=high)
    {
        if(L.r[left]<L.r[right])
        {
            T.r[result]=L.r[left];
            result++;
            left++;
        }
        else
        {
            T.r[result]=L.r[right];
            result++;
            right++;
        }
    }
    while(left<=mid)
    {
        T.r[result]=L.r[left];
        result++;
        left++;
    }
    while(right<=high)
    {
        T.r[result]=L.r[right];
        result++;
        right++;
    }
    for(int i=low;i<=high;i++)
    {
        L.r[i]=T.r[i];
    }
}
void MSort(SqList L,int low,int high)
{
    if(low==high)
        return;
    else
    {
        int mid=(low+high)/2;
        MSort(L,low,mid);
        MSort(L,mid+1,high);
        Merge(L,low,mid,high,T);
    }
}


时间复杂度:O(n*logn)
空间复杂度:O(n)
稳定

五、基数排序
1.多关键字的排序
分为分配和收集两个过程

void Distribute(SqList L,Queue bucketArray[RADIX],int k)
{
    for(int i=1;i<=L.length;i++)
    {
        int radix=GetRadixNumber(L.r[i].key,k);
        EnQueue(bucketArray[radix],L.r[i]);
    }
}
void Collect(SqList L,Queue bucketArray[RADIX])
{
    int j=1;
    for(int i=0;i<RADIX;i++)
    {
        while(!QueueEmpty(bucketArray[i]))
        {
            DeQueue(bucketArray[i],e);
            L.r[j++]=e;
        }
    }
}
void RadixSort(SqList L)
{
    for(i=int i=0;i<RADIX;i++)
    {
        InitQueue(bucketArray[i]);
    }
    int len=GetRoundCount(L.r);
    for(int k=1;k<=len;k++)
    {
        Distribute(L,bucketArray,k);
        Collect(L,bucketArray);
    }
}


分配时间复杂度:O(n)
收集时间复杂度:O(n)
时间复杂度:O(length*n)
空间复杂度:O(n+RADIX)

2.链式基数排序

void Distribute(SLList &L,int i,ArrType &f,ArrType &e) //L为静态链表,i为趟数,f和e分别为头指针、尾指针数组
{
    for(int j=0;j<Radix;j++)
    {
        f[j]=0;
    }
    for(int p=L.r[0].next;p!=0;p=L.r[p].next)
    {
        int k=Ord(L.r[p],i);
        if(f[k]==0)
        {
            f[k]=p;
        }
        else
        {
            L.r[e[k]].next=p;
        }
        e[k]=p;
    }
}
void Collect(SLList &L,int i,ArrType &f,ArrType &e)
{
    int j=0;
    while(f[j]==0)
    {
        j++;
    }
    L.r[0].next=f[j];
    int tail=e[j];
    while(j<=Radix-2)
    {
        j++;
        while(f[j]==0)
        {
            j++;
        }
        if(j<=Radix-1)
        {
            L.r[tail].next=f[j];
            tail=e[j];
        }
    }
    L.r[tail].next=0;
}


时间复杂度:O(length*(n+radix))
空间复杂度:O(radix)
稳定

直接插入排序正序时最好时间复杂度O(n),逆序最坏O(n2),平均O(n2),空间复杂度O(1);稳定;原始序列基本有序时该方法好

折半插入排序逆序最坏O(n2),正序时O(NLogN) , S(n)=O(1);稳定 希尔排序(缩小增量排序):平均时间复杂度O(n1.4),S(n)=O(1);不稳定

冒泡排序(改进的)正序时间复杂度最好O(n),逆序最坏O(n2),平均O(n2),S(n)=O(1); 稳定.注意改进算法

快速排序平均时间复杂度O(nlogn),平均性能最优,正序或逆序最坏O(n2), 有辅助栈,空间复杂度最坏O(n),平均O(logn);不稳定. 枢轴改进

选择排序复杂度T(n)=O(n2),原本有序无序均如此,S(n)=O(1);稳定 堆排序T(n)=O(nlogn),S(n)=O(1);不稳定(因为间隔着比和移动)

归并排序最好最坏复杂度为O(nlogn),空间复杂度O(n),稳定

链式基数排序最好最坏时间复杂度为O(d*(n+ rd )),空间O(rd),稳定

内部排序方法分类:复杂度O(n2)的简单排序方法,O(nlogn)的高效排序方法(比较法的理论下界),O(d*(n+rd))的基数排序方法.

各排序方法各有优缺点,具体选择时考虑稳定性、记录多少、原始数据是否基本有序、关键字大小等因素。

直接插入排序适合基本有序、n值较小时 基数排序适合关键字较短、n较大、要求稳定时;  

快速排序适合n大、不要求稳定、分布随机时;

堆排序适合n大、关键字分布可能有序、不要求稳定;

归并排序适合n大、关键字分布可能有序、要求稳定且内存空间充裕时;

如果只选择最小的前几个元素,则简单选择排序优先 理论上可证明基于比较的排序方法可能达到的最快的时间复杂度为O(nlogn);基数排序不是基于“比较”

为避免顺序存储时大量移动记录的时间开销,可考虑用链表作为存储结构 :直接插入排序、归并排序(非递归)、基数排序

不宜采用链表作为存储结构的 :折半插入排序、希尔排序、快速排序、堆排序

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值