各种排序算法的实现

2 篇文章 0 订阅
1 篇文章 0 订阅

数据类型设置

数据存储方式为顺序存储结构,待排序的一组记录存放在地址连续的一组存储单元上

typedef int KeyType;        //定义关键字类型为整数类型
typedef struct
{
    KeyType key[MAXSIZE+1]; //key[0]闲置或用作哨兵
    int length;             //顺序表长度
}


插入排序

1.直接插入排序

主要思想:

将一个记录插入到已经排好序的有序表中,得到一个新的、长度加1的有序表

算法:

void InsertSort(SqList &l)
{
    //插入排序
    int i,j;
    for(i=2;i<=l.length;i++)
    {
        if(l.key[i]<l.key[i-1])                 //待插入关键字l.key[i]比已排好序部分l.key[1...i-1]小时进行插入
        {
            l.key[0]=l.key[i];                  //复制哨兵
            for(j=i-1;l.key[0]<l.key[j];j--)    //记录后移
                l.key[j+1]=l.key[j];
            l.key[j+1]=l.key[0];                //插入到正确位置
        }
    }
}

直接插入排序容易实现,辅助空间只需一个。时间上,最好情况比较次数为n-1,不需要移动。最坏情况比较次数为(n+2)(n-1)/2,移动次数(n+4)(n-1)/2,时间复杂度为O(n²)


2.折半插入排序

主要思想:

插入排序的基本操作为在一个有序表中进行查找和插入,查找利用折半查找实现

算法:

void BInsertSort(SqList &l)
{
    //折半插入排序
    int i,j,low,high,mid;
    for(i=2;i<=l.length;i++)
    {
        l.key[0]=l.key[i];                      //l.keyp[i]暂存到l.key[0]
        low=1;
        high=i-1;
        while(low<=high)                        //折半查找有序插入的位置
        {
            mid=(low+high)/2;
            if(l.key[0]<l.key[mid])
                high=mid-1;
            else
                low=mid+1;
        }
        for(j=i-1;j>=high+1;j--)                //记录后移
            l.key[j+1]=l.key[j];
        l.key[high+1]=l.key[0];                 //插入
    }
}

折半插入排序辅助空间和直接插入排序形同。时间上,折半排序减少了比较次数,记录移动次数不变,时间复杂度为O(n²)


3.希尔排序

主要思想:

将整个待排序列分割成若干个子序列分别进行直接插入排序,等到整个序列整体有序时,再对全体记录进行一次直接插入排序


算法:

void ShellSort(SqList &l,int dlta[],int t)
{
    //希尔排序
    int i,j,k;
    for(k=0;k<t;k++)                            //增量序列中的值没有除1之外的公因子
    {                                           //最后一个增量值必须为1
        int dk=dlta[k];
        for(i=dk+1;i<=l.length;i++)
        {
            if(l.key[i]<l.key[i-dk])            //将l.key[i]插入有序增量子表,此过程为直接插入排序
            {
                l.key[0]=l.key[i];              //l.key[0]暂存数据
                for(j=i-dk;j>0&&l.key[0]<l.key[j];j-=dk)
                    l.key[j+dk]=l.key[j];
                l.key[j+dk]=l.key[0];
            }
        }
    }
}

希尔排序时间效率较前两种排序方法有较大进步,时间跟所取的增量序列有关,增量序列中的的值没有除1之外的公因子,且最后一个增量值必须为1



交换排序

1.冒泡排序

主要思想:

第一趟,将关键字最大的记录安置在最后一个记录位置上;第二趟排序,将前n-1个记录中关键字最大的记录安置在第n-1个位置;以此类推

整个排序要进行k(1≤k≤n-1),当一趟排序中没有进行交换记录的操作时结束排序

算法:

void BubbleSort(SqList &l)
{
    //冒泡排序
    int i,j,temp,flag=1;
    for(i=1;flag==1;i++)                //当在一趟排序中没有进行交换记录的操作时结束排序
    {
        flag=0;
        for(j=1;j<=l.length-i;j++)
        {
            if(l.key[j]>l.key[j+1])
            {
                temp=l.key[j+1];
                l.key[j+1]=l.key[j];
                l.key[j]=temp;
                flag=1;
            }
        }
    }
}

冒泡排序借助交换进行排序,辅助空间只需一个。时间上,最好情况进行n-1比较,不移动记录,最坏情况需要进行n-1趟排序,要进行n(n-1)/2次比较,时间复杂度为O(n²)


2.快速排序

主要思想:

取一个记录作为枢纽,通过一趟排序将待排记录分割成独立的两部分,一部分记录关键字记录小于枢纽记录,另一部分记录大于枢纽记录,继续对两部分记录进行排序


算法:

void QuickSort(SqList &l,int low,int high)
{
    //快速排序
    if(low<high)
    {

        int pivotloc=Partition(l,low,high);     //找出枢纽,将l.key[low...high]一分为二
        QuickSort(l,low,pivotloc-1);            //对低子表递归排序
        QuickSort(l,pivotloc+1,high);           //对高子表递归排序
    }
}
int Partition(SqList &l,int low,int high)
{
    //将枢纽移至正确位置
    int pivotkey=l.key[low];                    //用第一个记录作为枢纽记录
    l.key[0]=l.key[low];                        //记录枢纽关键字
    while(low<high)                             //从表的两端向中间扫描
    {
        while(low<high&&l.key[high]>=pivotkey)
            high--;
        l.key[low]=l.key[high];                 //将比枢纽记录小的记录移到低端
        while(low<high&&l.key[low]<=pivotkey)
            low++;
        l.key[high]=l.key[low];                 //将比枢纽记录大的记录移到高端
    }
    l.key[low]=l.key[0];
    return low;                                 //返回枢纽位置
}

快速排序需要一个栈空间实现递归。时间上,快速排序平均时间复杂度为O(nlogn),平均性能最好,但不稳定当序列基本有序时,蜕化为冒泡排序,时间复杂度为O(n²)


选择排序

1.简单选择排序

主要思想:

通过n-i次关键字间的比较,从n-i+1个记录中选择出关键字最小的记录,并和第i个记录交换

算法:

void SelectSort(SqList &l)
{
    //简单选择排序
    int i,j,temp,k;
    for(i=1;i<=l.length;i++)
    {
        temp=l.key[i];
        k=i;
        for(j=i;j<=l.length;j++)        //选择第i小的记录
        {
            if(temp>l.key[j])
            {
                temp=l.key[j];
                k=j;
            }
        }
        if(k!=i)
        {
            l.key[k]=l.key[i];
            l.key[i]=temp;
        }
    }
}

简单选择排序辅助空间只需一个。时间上,比较次数为n(n-1)/2,移动次数为0-3(n-1),时间复杂度为O(n²)

2.堆排序



归并排序

主要思想:

将数组中前后相邻的两个有序序列归并为一个有序序列


算法:

void MergeSort(SqList l1,SqList &l2,int s,int t)
{
    //归并排序
    //将l1.key[s...t]归并排序为l2.key[s...t]
    if(s==t)
        l2.key[s]==l1.key[s];
    else
    {
        int m=(s+t)/2;
        MergeSort(l1,l1,s,m);        //递归将l1.key[s..m]归并为有序
        MergeSort(l1,l1,m+1,t);      //递归将l1.key[m+1..t]归并有序
        Merge(l1,l2,s,m,t);          //将l1.key[s..m]和l1.key[m+1..t]归并到l2[s..t]
    }
}
void Merge(SqList l1,SqList &l2,int i,int m,int n)
{
    //将有序的l1.key[i...m]和l1.key[m+1...n]归并为有序的l2.key[i..n]
    int j,k;
    for(j=m+1,k=i;i<=m&&j<=n;k++)
    {
        if(l1.key[i]<l1.key[j])
            l2.key[k]=l1.key[i++];
        else
            l2.key[k]=l1.key[j++];
    }
    if(i<=m)
        while(i<=m)
            l2.key[k++]=l1.key[i++];
    if(j<=n)
        while(j<=n)
            l2.key[k++]=l1.key[j++];
}

归并排序是一种稳点的排序方法,辅助空间需要n个,时间复杂度为O(nlogn)



基数排序

1.简单选择排序

主要思想:

借助多关键字排序的思想对单逻辑关键字进行排序


算法:

typedef struct LNode
{
    KeyType data;
    struct LNode *next;
}LNode,*LinkList;
void RadixSort(SqList &l)
{
    //基数排序
    LinkList ll[10];
    for(int i=0;i<10;i++)                           //构造静态链表
    {
        ll[i]=(LinkList)malloc(sizeof(LNode));
        ll[i]->data=0;
        ll[i]->next=NULL;
    }
    for(int i=1;i<=MAX_NUM_OF_KEY;i++)              //按最低位优先依次对各关键字进行分配和收集
    {                                               //MAX_NUM_OF_KEY为关键字的最大位数
        Distribute(l,ll,i);                         //第i趟分配
        Collect(l,ll);                              //第i趟收集
    }
}
void Distribute(SqList l,LinkList *ll,int i)
{
    int j,k;
    int a[l.length+1];
    for(j=1;j<=l.length;j++)                        //求各关键字的第i位上的值
    {
        ll[j-1]->data=0;                            //每趟分配中,初始化链表长度
        a[j]=l.key[j];
        for(k=1;k<=i;k++)
        {
            a[j]=a[j]/10;
        }
        a[j]=a[j]%10;
    }
    for(j=1;j<=l.length;j++)                        //映射到链表中
    {
        LinkList node;
        node=(LinkList)malloc(sizeof(LNode));
        node->data=l.key[j];
        node->next=NULL;

        int num=0;
        LinkList p=ll[a[j]];
        while(num++<(ll[a[j]]->data))
            p=p->next;
        p->next=node;
        ll[a[j]]->data++;                           //链表头结点记录链表的长度
    }
}
void Collect(SqList &l,LinkList *ll)
{
    int i,num,k;
    for(i=0,k=1;i<10;i++)                           //将各链表连接起来
    {
        LinkList p=ll[i];
        num=0;
        while(num<ll[i]->data)
        {
            p=p->next;
            l.key[k++]=p->data;
            num++;
        }
    }
}
基数排序的辅助空间需要rd个,r为基数值,d为关键字位数。时间上,每一趟分配时间复杂度为O(n),每一趟收集时间复杂度为O(rd),时间复杂为O(d(n+rd))



各种内部排序方法的比较




参考书目:数据结构(C语言版) 严蔚敏

图片来源于网络




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值