内排序

本文深入探讨了排序算法的基本概念、实现细节及其性能评估,包括内排序与外排序的区别,稳定性、时间代价与空间代价的衡量标准。通过具体算法如直接插入排序、Shell排序、选择排序、堆排序、交换排序(冒泡排序)、快速排序与归并排序的分析,展示了不同排序方法的特点与适用场景。同时,介绍了优化方法,如R.Sedgewick算法在空间与时间效率上的改进,以及分配排序和基数排序在特定情况下的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内排序外排序中的“内外”指的是内存和外存。

基本概念

  • 记录(record ):结点
  • 关键码(key ):序号
  • 排序码(sort key ):作为排序依据的一个或多个域,不一定是关键码
  • 序列sequence):线性表
  • 排序(sort )将序列中的记录按照排序码排列起来,使序列不增或不减。
  • 内排序,在内存中进行的排序
  • 正序
  • 逆序

  • 稳定性:排序码相同时保持原来的相对次序。

  • 衡量标准:

    • 时间代价:记录的比较和移动次数
    • 空间代价:占用的存储空间
    • 算法本身繁杂程度

插入排序

直接插入排序

  • 和抓牌一样

代码

//直接插入排序算法
template <class Record>
void ImprovedInsertSort(Record Array[],int n)
{
    Record TempRecord;
    for(int i = 1;i < n;i++)    
    {
        TempRecord = Array[i];
        if(Array[i] < Array[0]) //如果是最小值,先行处理
        {
            for(int j = i - 1;j >= 0;j--) 
                Array[j+1] = Array[j];
            Array[0] = Array[i];
        }   
        else
        {
            for(int j = i - 1;j >= 0;j--)
            {
                if(Array[i] >= Array[j])  //找到插入的位置
                {
                    for(int k = i - 1;k > j;k--)
                        Array[k+1] = Array[k];
                    Array[j+1] = TempRecord;
                    break;     //跳出循环
                }   
            }
        }
    }
}

算法分析

  • 稳定。保持稳定性方法:排序码相同时直接靠后放
  • 时间复杂度O(n2),平均复杂度O(n2),在最好情况下Θ(n)
  • 空间复杂度O(1)
  • 对于短序列,插入算法比较简单有效

Shell排序

  • 将序列变成很多小序列进行排序
  • 逐步扩大小序列规模,直到整个序列
  • 可调节步长

代码

//Shell排序算法
template <class Record>
void ShellSort(Record Array[],int n)
{
    int i,delta;
    for(i = 0;i < delta;i++)
        ModInsSort(&Array[i],n - i,delta); //传地址
    ModInsSort(Array,n,1);
}

class template<Record>
void ModInsSort(Record Array[],int n,int delta)
{
    for(int i = delta;i < n;i += delta)
    {
        for(int j = i;j >= delta;j -= delta)
        {
            if(Array[j] < Array[j - delta])
                swap(Array,j,j - delta);
            else break;
        }
    }

}

算法分析

  • 不稳定
  • 空间代价Θ(1)
  • 增量每次除以2时,时间代价是Θ(n2)
  • 适当选取增量序列,可以使时间代价接近Θ(n)
  • Hibbard增量序列{2k1},时间代价Θ(n3/2)

选择排序

直接选择排序

  • 选出剩下未排序记录中的最小记录,然后直接和数组的第i个记录交换。

代码

template<class Record>
void SelectSort(Record Array[],int n)
{
    for(int i = 0;i < n;i++)
    {
        int Smallest = i;  //先标记i最小
        for(int j = i;j < n;j ++)
            if(Compare::It(Array[j],Array[Smallest]))
                Smallest = j;  //找到剩下的记录中最小的,并且标记
        swap(Array,i,Smallest);  //替换
    }
}

算法分析

  • 不能保证稳定性(\?)
  • 时间代价Θ(n2):比较次数Θ(n2),交换次数n1
  • 空间代价Θ(1)

堆排序

  • 最大值堆
    • 建堆
    • 每次删除堆顶,按顺序放入数组尾部

代码

//最大值堆排序
template <class Record>
void sort(Record Array[],int n)
{
    MaxHeap<Record> max_heap=MaxHeap<Record>(Aray,n,n);
    for(int i = 0;i < n;i++)
    {
        max_heap.RemoveMax();
    }
}

算法分析

  • 时间复杂度:Θ(nlogn)1次建堆Θ(n),n次删除堆顶Θ(logi)
  • 空间代价Θ(1)

交换排序

冒泡排序

  • 不停比较相邻的记录,不满足要求就交换。

代码

//冒泡排序算法
template <class Record>
void BubbleSort(Record Array[],int n)
{
    for(int i = 0;i < n;i++)
        for(int j = i + 1;j < n;j++)
        {
            if(Array[j] < Array[i])
                swap(Array,i,j);
        }
}

算法分析

  • 稳定
  • 空间代价O(1)
  • 时间代价Θ(n2)

快速排序

  • 1962年Tony Hoare提出
  • 基于分治法的排序:快速、归并
  • 选择轴值(pivot )
  • 将序列划分为L和R两个子序列,L中的记录都小于等于轴值,R的记录大于轴值
  • 递归,对子序列L、R分别进行快速排序

  • 过程

    • 选择轴值并存储
    • 将最后一个元素放在轴值的位置
    • 初始化下标i,j,分别指着头尾
    • i递增直到遇到比轴值大的元素,把这个元素放到j的位置;
      j递增直到遇到比轴值小的元素,把这个元素放到i的位置;
    • 重复直到i==j,把轴值放回到i位置。

quicksort

代码

//快速排序算法
template<class Record>
void QuickSort(Record Array[], int left,int right)
{
    if(right <= left)
        return;
    int pivot = SelectPivot(left,right); //选择轴值
    swap(Array,pivot,right); //把右边最后一个元素调到轴值的位置
    pivot = Partition(Array,left,right); //分割一次
    QuickSort(Array,left,pivot-1); //递归,对两个子序列继续分割
    Quicksort(pivot+1,right);
}

int SelectPivot(left,right)
{
    return (left+right)/2;
}

//partition分割算法
template <class Record>
int Partition(Record Array,int left,int right)
{
    int i = left;
    int j = right;
    Record TempRecord = Array[j]; //这个Arrray[j]是交换过来的轴值
    while(i != j) //直到i,j相等时才停止
    {
        while(Array[i] <= TempRecord && i < j) //找到左边大于轴值的i
            i++;
        if(i < j)  //如果两头还没相遇,逆置元素调到右边空位
        {
            Array[j] = Array[i];
            j--;
        }
        while(Array[j] >= TempRecord && i < j) //找到右边小于轴值的j
            j--;    
        if(i < j)  //如果两头还没相遇,逆置元素调到左边空位
        {
            Array[i] = Array[j];
            i++;
        }   
        Array[i] = TempRecord; //轴值归位
        return i; //返回轴值
    }
}

算法分析

  • 最差情况
    • 时间代价Θ(n2)
    • 空间代价Θ(n)
  • 最佳情况
    • 时间代价Θ(nlogn)
    • 空间代价Θ(logn)
  • 平均情况
    • 时间代价Θ(nlogn)
    • 空间代价Θ(logn)
  • 不稳定
  • 优化方法
    • 轴值的选择,如RQS
    • 小子串不递归
    • 消除递归

归并排序

  • 简单地将原始序列划分为两个子序列
  • 分别对每个子序列进行递归
  • 归并,即将排好序的子序列合并成一个序列。

这里写图片描述

代码

//归并排序算法
template <class Record>
void MergeSort(Record Array[],Record TempArray,int left,int right)
{
    int middle;
    if(left<right)
    {
        middle = (left+right)/2;
        MergeSort(Array,TempArray,left,middle);
        MergeSort(Array,TempArray,middle+1,right);
        Merge(Array,TempArray,left,right,middle);
    }
}

template <class Record>
void Merge(Record Array[],Record TempArray[],int left,int right,int middle)
{
    int i,j,index1,index2;
    for(int j = left;j<right;j++)
        TempArray[j] = Array[j];
    index1 = left;
    index2 = middle + 1;
    i = left;
    while(index1 < middle && index2 < right)
    {
        if(TempArray[index1] <= TempArray[index2])
            Array[i++] = TempArray[index1++];
        else
            Array[i++] = TempArray[index2++];
    }
    while(index1 <=(?) middle)
        Array[i++] = TempArray[index1++];
    while(index2 <= right)
        Array[i++] = TempArray[index2++];
}

优化——R.Sedgewick算法

这里写图片描述

算法分析

  • 空间代价:Θ(n)
  • 时间代价Θ(nlogn),最大、最小、平均都是Θ(nlogn)
  • 稳定

分配排序和基数排序

  • 不需要进行两个记录之间的比较
  • 需要事先知道序列中记录的一些具体情况

桶式排序

  • 需要事先知道序列中记录在小区间[0,m)
  • 适合大量重复数值
  • 将相同值扔到同一个桶里,最后按照桶的顺序依次取出
  • 数据结构:只需要额外加两个和桶个数相当的两个数组
  • 数组长度n,区间[0,m),时间复杂度Θ(m+n)
  • 空间复杂度Θ(m+n)
  • 稳定

基数排序

  • 当桶式排序中的m很大时,可以进行拆分,比如多位数拆分成多个一位数
  • 设序列长度n,排序码可以拆分成d个子排序码
  • 设子排序码有优先级,比如多位数
  • 分为高位优先法(MSD)和低位优先法(LSD)
  • 高位优先法:对最高位进行桶式排序,序列分成若干个桶;在每个桶里,对次高位进行桶式排序,继续划分序列;…;最后将所有的桶收到一起。分、分、分…收的过程,空间代价较大。

排序算法的时间代价

方法时间复杂度
直接插入
shell
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值