排序

排序方法的分类

  • 按数据存储介质:
  1. 内部排序:数据量大,数据在内存,无需内外存交换数据
  2. 外部排序:数据量大,数据在外存(文件排序),将数据分批调入内存排序

  • 按比较器个数:
  1. 串行排序:单处理机(同一时刻比较一对元素)
  2. 并行排序:多处理机(同一时刻比较多对元素)

  • 按主要操作:
  1. 比较排序:用比较方法:插入排序、交换排序、选择排序、归并排序
  2. 基数排序:不比较元素的大小,仅仅根据元素本身的取值确定其有序位置

  • 按辅助空间:
  1. 原地排序:辅助空间为O(1)的排序方法(所占的辅助空间与参加排序的数据量大小无关)
  2. 非原地排序:辅助空间用量超过O(1)的排序方法

  • 按稳定性:
  1. 稳定排序:能够使任何数值相等的元素,排序以后相对次序不变
  2. 非稳定排序:不是稳定排序的方法

  • 按自然性:
  1. 自然排序:输入数据越有序,排序的速度越快的排序方法
  2. 非自然排序:不是自然排序的方法

按排序所需工作量:
简单的排序方法:T(n)=O(n^2)
基数排序:T(n)=O(d.n)
先进的排序方法:T(n)=O(nlog(n))

一、插入排序

1、直接插入排序

直接插入排序算法:
//直接插入排序
void InsertSort(SqList &L){
    int i, j;
    for(i = 2; i <= L.length; ++i){
        if(L.r[i].key < L.r[i-1].key){  //若“<”,需要将L.r[i]插入有序子表
            L.r[0] = L.r[i];        //复制为哨兵
            for(j = i-1; L.r[0].key < L.r[j].key; --j){
                L.r[j+1] = L.r[j];  //记录后移
            }
            L.r[j+1] = L.r[0];      //插入到正确位置
        }
    }
}
需要一个哨兵,辅助空间为O(1), 时间复杂度最好为O(n),最坏和平均情况为O(n^2)

2、折半插入排序

查找插入位置时采用折半查找法
//折半插入排序
void BInsertSort(SqList &L){
    for(i = 2; i <= L.length; ++i){     //依次插入第2~n个元素
       L.r[0] = L.r[i];         //当前插入元素存到哨兵的位置
       low = 1; high = i-1;     //采用二分部查找法查到插入位置
       while(low <= high){
            mid = (low + high) / 2;
            if(L.r[0].key < L.r[mid].key)   
                high = mid - 1;
            else
                low = mid + 1;
       }//循环结束,high+1则为插入位置
       for(j = i-1; j >= high+1; --j)
            L.r[j+1] = L.r[j];  //移动元素
       L.r[high+1] = L.r[0];    //插入到指定位置    
    }
}
折半插入排序-算法分析
  1. 减少了比较次数,但没有减少移动次数
  2. 平均性能优于直接插入排序
  3. 时间复杂度为:O(n^2)
  4. 空间复杂度为:O(1)
  5. 是一种稳定的排序方法

3、希尔排序

基本思想:先将整个待排记录序列分割成若干个子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序
希尔排序特点:(1)缩小增量; (2)多遍插入排序

二、交换排序

1、冒泡排序

  • 外层循环控制比较轮数(趟数),
for(int i = 0; i < L.length-1; i++)
  • 内层循环控制第i躺需要比较的次数,
for(int j = 0; j < L.length-i-1; j++)

  • n个记录,总共需要n-1躺
for(m = 1; m <= n-1; m++)
  • 第m躺需要比较n-m次
for(j = 1; j <= n-m; j++)
改进的冒泡排序,引入flag作为是否有交换的标记
//改进的冒泡排序算法
void bubble_sort(SqList &L){    //改进的冒泡排序算法
    int m, j, flag = 1; RedType x;      //flag用来标记某一趟排序是否发生变换
    for(m = 1; m <= n-1 && flag == 1; m++){
        flag = 0;       //flag置为0,如果本躺排序没有发生交换,则不会执行下一趟排序
        for(j = 1; j <= m; j++){
            if(L.r[j].key > L.r[j+1].key){  //发生逆序
                flag = 1;   //发生交换,置为1
                x = L.r[j];
                L.r[j] = L.r[j+1];
                L.r[j+1] = x;       //交换
            }
        }
    }
}

冒泡排序的特点
  1. 最好的时间复杂度是O(n)
  2. 最坏的时间复杂度是O(n^2)
  3. 平均的时间复杂度是O(n^2)
  4. 增加一个辅助空间temp,辅助空间为S(n) = O(1)
  5. 冒泡排序是稳定的

2、快速排序—改进的交换排序

基本思想
  1. 任取一个元素为中心,pivot:枢轴、中心点
  2. 所有比他小的元素一律前放,比他大的元素一律后放,形成左右两个子表
  3. 对各子表重新选择中心元素并依此规则调整(递归思想)
  4. 直到每个子表的元素只剩一个
//快速排序算法
void main(){
    QSort(L, 1, L.length);
}

void QSort(SqList &L, int low, int high){   //对顺序表L快速排序
    if(low < high){     //长度大于1
        pivotloc = Partition(L, low, high);
        //将L.r[low...high]一分为二,pivotloc为枢纽元素排好序的位置
        QSort(L, low, pivotloc-1);  //对低子表递归排序
        QSort(L, pivotloc+1, high); //对高子表递归排序
    }
}

int Partiton(SqList &L, int low, int high){
    L.r[0] = L.r[low];  pivotkey = L.r[low].key;
    while(low < high){
        while(low < high && L.r[high].key >= pivotkey)  --high;
        L.r[row] = L.r[high];
        while(low < high && L.r[low].key <= pivotkey)   ++low;
        L.r[high] = L.r[low];
    }
    L.r[low] = L.r[0];
    return low;
}
算法分析:
  1. 时间复杂度:Qsort()😮(log2(n)); Partiton(): O(n)
  2. 空间复杂度:平均情况需要O(nlog (n))的栈空间; 最坏情况下:栈空间可达到O(n)
  3. 不稳定的排序

三、选择排序

1、简单选择排序

基本操作
  1. 首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将他与第一个记录交换
  2. 再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将他与第二个记录交换
  3. 重复上述操作,共进行n-1躺排序后,排序结束
//简单选择排序算法
void SelectSort(SqList &K){
    for(i = 1; i < L.length; ++i){
        k = i;
        for(j = i+1; j < L.length; j++)
            if(L.r[j].key < L.r[k].key)
                k = j;      //记录最小位置
        if(k != i)  
            t = L.r[i]; L.r[i] = L.r[k]; L.r[k] = t;
    }
}
算法分析
  1. 时间复杂度:最好情况:0; 最坏情况:3(n-1)
  2. 空间复杂度:O(1)
  3. 不稳定的排序

2、堆排序

  • 小根堆:ai <= a2i ; ai <= a(2i+1)
  • 大根堆:ai>=a2i ; ai >= a(2i+1)

小根堆的调整

  1. 输出堆顶元素后,以堆中最后一个元素替代之
  2. 然后将根结点值与左右子树的根结点进行比较,并与其中小者进行交换。
  3. 重复以上操作,直至叶子结点

堆的建立

先按层次顺序构造好堆

  1. 单结点的二叉树是堆
  2. 在完全二叉树中所有以叶子结点为根的子树是堆
  3. 将序号为n/2, n/2-1, n/2 - 2 … 1的结点为根的子树均调整为堆即可
  4. 对应由n个元素组成的无序序列,“筛选”只需从第n/2个元素开始
  • Tw(n) = O(n)+O(nlogn) = O(nlogn)
  • 空间复杂度O(1)
//堆排序算法
void HeapSort(elem R[]){    //对R[1]到R[n]进行堆排序
    int i;
    for(i = n/2; i >= 1; i--)
        HeapAdjist(R, i, n);     //建初始堆
    for(i = n; i > 1; i--){      //进行n-1躺排序
        Swap(R[1], R[i]);        //根与最后一个元素交换
        HeapAdjust(R, 1 , i-1);  //对R[1]到R[i-1]重新建堆
    }
}

四、归并排序

基本思想:将两个或者两个以上的有序子序列“归并”为一个有序序列
若SR[i].key <= SR[j].key,则TR[k]=RS[i];k++;i++; 否则TR[k]=SR[j];k++;j++:

算法分析

  1. 时间效率:O(nlog(2n))
  2. 空间效率:O(n),因为需要一个同原始序列同样大小的辅助序列R1
  3. 稳定性,稳定

五、基数排序(桶排序或箱排序)

基本思想:分配+收集
设置若干个箱子,将关键字为k的记录放入第k个箱子,然后再按序号将非空的连接
数字是有范围的,均由0-9这十个数字组成,则只需设置十个箱子,相继按个、十、百。。。进行排序

算法分析:

  1. 时间效率:O(k*(n+m))
  • k:关键字个数
  • m:关键字取值范围为m个值
  1. 空间效率:O(n+m)
  2. 稳定性:稳定

在这里插入图片描述

link,根据青岛大学王卓老师数据结构总结。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值