数据结构 第十章 排序

目录

 

10.1排序的概述

排序的定义

排序的分类

排序的操作

10.2插入排序

直接插入排序(基于顺序查找)

基本过程

算法分析

小结

折半插入排序

算法分析

希尔排序

基本思想

算法分析

10.3交换排序

基本思想

冒泡排序

冒泡排序步骤

算法分析

快速排序

基本思想

算法分析

10.4选择排序

基本思想

简单选择排序

算法分析

树形选择排序

堆排序

基本思想

堆的重新调整

算法分析

归并排序

2-路归并排序

算法分析

10.5基数排序

最高位优先法

最低位优先法

链式基数排序

链式基数排序步骤

小结

排序算法比较

排序算法选择规则


10.1排序的概述

排序的定义

将一批任意序列的数据元素(或记录),按关键字有序重新排列成一个新的序列叫排序,从小到大。

2. 排序的目的是什么? ——便于查找!

3. 什么叫内部排序?什么叫外部排序?

若待排序记录都在内存中,称为内部排序;

若待排序记录一部分在内存,一部分在外存,则称为外部排序。

注:外部排序时,要将数据分批调入内存来排序,中间结果还要及时放入外存,显然外部排序要复杂得多。 

4.排序算法的好坏如何衡量?

时间效率——排序速度(比较次数移动次数

空间效率——占内存辅助空间的大小

稳定性——AB的关键字相等,排序后AB的先后次序保持不变,则称这种排序算法是稳定的。

排序的分类

Ø插入排序:直接插入排序、折半插入排序、希尔排序

Ø交换排序:冒泡排序、快速排序

Ø选择排序:简单选择排序、堆排序

Ø归并排序:2-路归并排序

Ø基数排序

排序的操作

Ø比较:两个关键字大小

Ø移动:将记录从一个位置移动到另一个位置

10.2插入排序

基本思想: 每步将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象适当位置,直到对象全部插入为止。

即边插入边排序,保证子序列中随时都是排好序的

直接插入排序(基于顺序查找)

基本过程

i次插入的数是a[i],这时数组a中的a[0]~a[i-1]已经有序了,将a[i]插入到数组a中的合适位置。插入a[i]的过程,分为3个步骤:

Øa[i-1]开始,在a[0]~a[i-1]范围内找第一个小于a[i]的数,其位置记为pos

Øa[i-1]开始,将a[i-1]~a[pos]依次后移一个位置

Øa[i]放置在a[pos]位置上,替换a[pos]

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <map>
#include <vector>
#include <queue>
#include <set>
#define eps 1e-8
const double PI = acos(-1.0);
const int maxn = 1e5;
const int INF = 1e9;
using namespace std;
void InsertionSort(int A[],int n)
{
    int preidx,curr;
    for(int i = 1; i<n; i++)
    {
        preidx = i-1;
        curr = A[i];
        while(preidx>=0 && A[preidx]>curr)
        {
            A[preidx+1] = A[preidx];
            preidx--;
        }
        A[preidx+1] = curr;
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    int A[n+5];
    for(int i = 0; i<n; i++)
        scanf("%d",&A[i]);
    InsertionSort(A,n);
    for(int i = 0; i<n; i++)
        printf("%d ",A[i]);
    printf("\n");
    return 0;
}

算法分析

设对象个数为n,则执行n-1

比较次数移动次数与初始排列有关

最好情况下:

Ø每趟只需比较 1 次,不移动

Ø总比较次数为 n-1

最坏情况下:第 i  趟比较 i 次,移动 i+1

若出现各种可能排列的概率相同,则可取最好情况和最坏情况的平均情况

平均情况比较次数移动次数为(n^2)/4

Ø时间复杂度为 O(n2)

Ø空间复杂度为 O(1)

Ø是一种稳定的排序方法

小结

Ø排序趟数:n个数——n-1趟

Ø比较次数、移动次数——与对象排序码的初始排列有关

Ø比较次数最少n-1次,移动次数最少没有移动

Ø比较次数最多(n+2)(n-1)/2次,移动次数最多(n+4)(n-1)/2

Ø平均比较次数和移动次数——n2/4,时间复杂度为 O(n2)

Ø插入排序的稳定性——稳定

折半插入排序

在插入 r[i] 时,利用折半查找法寻找 r[i] 的插入位置

void BInsertSort(int A[])
{
    int curr;
    for(int i = 1; i<10; i++)
    {
        int low = 0;
        int high = i-1;
        curr = A[i];
        while(low<=high)
        {
            int mid = (low+high)/2;
            if(curr<A[mid])
                high = mid-1;
            else
                low = mid+1;
        }
        for(int j = i-1; j>=high+1; j--)
            A[j+1] = A[j];
        A[high+1] = curr;

    }
}

算法分析

折半查找比顺序查找快,所以折半插入排序就平均性能来说比直接插入排序要快

它所需要的关键码比较次数与待排序对象序列的初始排列无关,仅依赖于对象个数。在插入第 i 个对象时,需要经过 log2i +1  次关键码比较,才能确定它应插入的位置

n 较大时,总关键码比较次数比直接插入排序的最坏情况要好得多,但比其最好情况要差

在对象的初始排列已经按关键码排好序或接近有序时,直接插入排序比折半插入排序执行的关键码比较次数要少

折半插入排序的对象移动次数与直接插入排序相同,依赖于对象的初始排列

减少了比较次数,但没有减少移动次数

平均性能优于直接插入排序

Ø时间复杂度为 O(n2)

Ø空间复杂度为 O(1)

Ø是一种稳定的排序方法

希尔排序

基本思想

先将整个待排记录序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。

子序列的构成不是简单地“逐段分割”,将相隔某个增量dk的记录组成一个子序列,让增量dk逐趟缩短(例如依次取5,3,1)。直到dk1为止。

优点:小元素跳跃式前移,最后一趟增量为1时,序列已基本有序,平均性能优于直接插入排序

  • dk 值较大,子序列中对象较少,速度较快
  • dk 逐渐变小,子序列中对象变多,但大多数对象已基本有序,所以排序速度仍然很快。
void shellSort(int A[],int len)
{
    int i,j;
    int increment = len;//增量
    int key;
    while(increment > 1)//最后在增量为1并且是执行了情况下停止。
    {
        increment = increment/3 + 1;//根据公式
        printf("increment:%d\n",increment);
        for (i = increment; i<len; i++)//从[0]开始,对相距增量步长的元素集合进行修改。
        {
            key = A[i];
            //以下和直接插入排序类似。
            j = i-increment;
            while(j>=0)
            {
                if (key<A[j] )
                {
                    int temp = A[j];
                    A[j] = key;
                    A[j+increment] = temp;
                }
                j = j-increment;
            }
        }
    }
}

算法分析

时间复杂度是nd的函数:O(n^1.25)~O1.6n^1.25经验公式

空间复杂度为 O(1)

是一种不稳定的排序方法

如何选择最佳d序列,目前尚未解决

Ø最后一个增量值必须为1无除1以外的公因子

Ø不宜在链式存储结构上实现

10.3交换排序

基本思想

两两比较,如果发生逆序则交换,直到所有记录都排好序为止。

冒泡排序

两两比较待排序数据元素的大小,发现两个数据元素的次序相反时即进行交换,直到没有反序的数据元素为止。

冒泡排序步骤

比较第0个数与第1个数,若为逆序,即a[0]>a[1],则交换;然后比较第1个数与第2个数;以此类推,直至第n-2个数和第n-1个数比较完为止,即第0趟冒泡排序结束。第0趟排序的结果是最大的数被安置在最后一个元素位置上。

对前n-1个数进行第1趟冒泡排序,结果使次大的数被安置在第n-1个元素位置。

如此重复上述过程,共经过n-1趟冒泡排序后,排序结束。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <map>
#include <vector>
#include <queue>
#include <set>
#define eps 1e-8
const double PI = acos(-1.0);
const int maxn = 1e5;
const int INF = 1e9;
using namespace std;
void BubbleSort(int A[],int n)
{
    int t;
    int flag = 1;
    for(int i = 0; i<n-1&&flag; i++)
    {
        flag = 0;
        for(int j = 0; j<n-1-i; j++)
        {
            if(A[j]>A[j+1])
            {
                t = A[j];
                A[j] = A[j+1];
                A[j+1] = t;
                flag = 1;
            }
        }
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    int A[n+5];
    for(int i = 0; i<n; i++)
        scanf("%d",&A[i]);
    BubbleSort(A,n);
    for(int i = 0; i<n; i++)
        printf("%d ",A[i]);
    printf("\n");
    return 0;
}

算法分析

设对象个数为n

比较次数移动次数与初始排列有关

最好情况下:

只需 1趟排序,比较次数为 n-1,不移动

最坏情况下:

n-1趟排序,i趟比较n-i次,移动3(n-i)

时间复杂度为 O(n2)

空间复杂度为 O(1)

是一种稳定的排序方法

快速排序

基本思想

任取一个元素 (如第一个) 为中心

所有比它的元素一律前放,比它的元素一律后放,形成左右两个子表

对各子表重新选择中心元素并依此规则调整,直到每个子表的元素只剩一个

每一趟的子表的形成是采用从两头向中间交替式逼近法;

②由于每趟中对各子表的操作都相似,可采用递归算法。

void quickSort(int a[],int st,int ed)
{
    if(ed - st <= 0)
        return;
    int key =a[st];
    int low = st;
    int high = ed;
    while(low != high)
    {
        while(low<high && a[high]>=key)
            high--;
        while(low<high && a[low]<=key)
            low++;
        if(low<high)
        {
            int t = a[low];
            a[low] = a[high];
            a[high] = t;
        }

    }
    a[st] = a[low];
    a[low] = key;
    quickSort(a,st,low-1);
    quickSort(a,low+1,ed);
}

算法分析

可以证明,平均计算时间是O(nlog2n)

实验结果表明:就平均计算时间而言,快速排序是我们所讨论的所有内排序方法中最好的一个。

快速排序是递归的,需要有一个栈存放每层递归调用时参数(新的lowhigh

最大递归调用层次数与递归树的深度一致,因此,要求存储开销为 O(log2n)

最好:划分后,左侧右侧子序列的长度相同

最坏:从小到大排好序,递归树成为单支树,每次划分只得到一个比上一次少一个对象的子序列,必须经过 n-1 趟才能把所有对象定位,而且第 i 趟需要经过 n-i 次关键码比较才能找到第 i 个对象的安放位置

时间效率:O(nlog2n) 每趟确定的元素呈指数增加

空间效率:Olog2n递归要用到栈空间

稳 定 性: 不稳定   可选任一元素为支点。

10.4选择排序

基本思想

每一趟在后面 n-i +1个中选出关键码最小的对象, 作为有序序列的第 i 个记录

简单选择排序

void SelectionSort(int A[],int n)
{
    int minidx,t;
    for(int i = 0; i<n-1; i++)
    {
        minidx = i;
        for(int j = i+1; j<n; j++)
        {
            if(A[minidx]>A[j])
            {
                minidx = j;
            }
        }
        t = A[i];
        A[i] = A[minidx];
        A[minidx] = t;
    }
}

算法分析

移动次数

最好情况:0;最坏情况:3(n-1)

比较次数:

时间复杂度:O(n²) 空间复杂度:O(1) 不稳定

树形选择排序

改进:简单选择排序没有利用上次选择的结果,是造成速度满的重要原因。如果,能够加以改进,将会提高排序的速度。

堆排序

n个元素的序列{k1,k2,…,kn}当且仅当满足下列关系时,成为堆

如果将序列看成一个完全二叉树,非终端结点的值均小于或大于左右子结点的值。

利用树的结构特征来描述堆,所以树只是作为堆的描述工具,堆实际是存放在线形空间中的。

基本思想

  • 将无序序列建成一个堆
  • 输出堆顶的最小(大)值
  • 使剩余的n-1个元素又调整成一个堆,则可得到n个元素的次小值
  • 重复执行,得到一个有序序列

堆的重新调整

  • 输出堆顶元素后,以堆中最后一个元素替代之
  • 将根结点与左、右子树根结点比较,并与小者交换
  • 重复直至叶子结点,得到新的堆

算法分析

时间效率:O(nlog2n)  空间效率:O1) 稳 定 性:不稳定 适用于n 较大的情况

归并排序

归并:将两个或两个以上的有序表组合成一个新有序表

2-路归并排序

排序过程

  • 初始序列看成n有序子序列,每个子序列长度为1
  • 两两合并,得到n/2个长度为21的有序子序列
  • 再两两合并,重复直至得到一个长度为n的有序序列为止

算法分析

时间效率:O(nlog2n)  空间效率:On) 稳 定 性:稳定

10.5基数排序

前面的排序方法主要通过关键字值之间的比较和移动;而基数排序不需要关键字之间的比较

多关键字排序:

最高位优先MSD ( Most Significant Digit first )

最低位优先LSD ( Least Significant Digit first)

链式基数排序

链式基数排序:用链表作存储结构的基数排序

最高位优先法

Ø先对最高位关键字k1如花色)排序,将序列分成若干子序列,每个子序列有相同的k1值;

Ø然后让每个子序列对次关键字k2如面值)排序,又分成若干更小的子序列;

Ø依次重复,直至就每个子序列对最低位关键字kd排序,就可以得到一个有序的序列。

十进制数比较可以看作是一个多关键字排序

最低位优先法

首先依据最低位排序码Kd对所有对象进行一趟排序

再依据次低位排序码Kd-1对上一趟排序结果排序

依次重复,直到依据排序码K1最后一趟排序完成,就可以得到一个有序的序列。

这种方法不需要再分组,而是整个对象组都参加排序

链式基数排序

先决条件

知道各级关键字的主次关系

知道各级关键字的取值范围

利用“分配”和“收集”对关键字进行排序

过程

首先对低位关键字排序,各个记录按照此位关键字的值‘分配’到相应的序列里。

按照序列对应的值的大小,从各个序列中将记录‘收集’,收集后的序列按照此位关键字有序。

在此基础上,对前一位关键字进行排序。

链式基数排序步骤

设置10个队列f[i]e[i]分别头指针和尾指针

第一趟分配对最低位关键字(个位)进行,改变记录的指针值,将链表中记录分配至10个链队列中,每个队列记录的关键字的个位相同

第一趟收集是改变所有非空队列的队尾记录的指针域,令其指向下一个非空队列的队头记录,重新将10个队列链成一个链表

重复上述两步,进行第二趟、第三趟分配和收集,分别对十位、百位进行,最后得到一个有序序列

算法分析

n个记录 每个记录有 d 位关键字 关键字取值范围rd(如十进制为10)

  • 重复执行d趟“分配”与“收集”
  • 每趟对 n 个记录进行“分配”,对rd个队列进行“收集”
  • 需要增加n+2rd个附加链接指针。

时间效率:O(d( n+rd))  空间效率:O(n+rd) 稳 定 性:稳定

小结

(数据不是顺次后移时将导致方法不稳定)

排序算法比较

按平均时间排序方法分为四类

    O(n2)O(nlogn)O(n1+e)O(n)

快速排序是基于比较的内部排序中平均性能最好的

基数排序时间复杂度最低,但对关键字结构有要求(知道各级关键字的主次关系,知道各级关键字的取值范围

为避免顺序存储时大量移动记录的时间开销,可考虑用链表作为存储结构

   直接插入排序、归并排序、基数排序

不宜采用链表作为存储结构的

   折半插入排序、希尔排序、快速排序、堆排序

排序算法选择规则

n较大时:

1)分布随机,稳定性不做要求,则采用快速排序

2)内存允许,要求排序稳定时,则采用归并排序

3)可能会出现正序或逆序,稳定性不做要求,则采用排序或归并排序

n较小时:

1)基本有序,则采用直接插入排序

2)分布随机,则采用简单选择排序,若排序码不接近逆序,也可以采用直接插入排序

 

 

 

 

排序作业 选择题(每题2分,共22分)。 1.若表R在排序前已按键递增顺序排列,则(   )算法的比较次数最少。 A.直接插入排序            B.快速排序     C.归并排序                D.选择排序 2.对各种内部排序方法来说,(   )。 A.快速排序时间性能最佳                             B.归并排序是稳定的排序方法 C.快速排序是一种选择排序                          D.堆排序所用的辅助空间比较大 3.  排序算法的稳定性是指(   )。 A.经过排序之后,能使相同的数据保持原顺序中的相对位置不变。 B.经过排序之后,能使相同的数据保持原顺序中的绝对位置不变。 C.排序算法的性能与被排序元素的数量关系不大 D.排序算法的性能与被排序元素的数量关系密切 4. 如下序列中,(   )序列是大顶堆。 A.  {4,5,3,2,1}               B.  {5,3,4,1,2}        C.  {1,2,3,4,5}               D.  {1,2,3,5,4} 5. 若将{3,2,5,4,1}排为升序,则实施快速排序一趟后的结果是(   )(其中,枢轴记录取首记录)。 A.  {1,2,3,4,5}                  B.  {1,2,4,5,3}        C.  {1,3,5,4,2}                  D.  {2,5,4,1,3} . 若将{1,2,3,4,5,6,7,9,8}排为升序,则(   )排序方法的“比较记录”次数最少。 A.  快速排序                   B.  简单选择排序     C.  直接插入排序               D.  冒泡排序 7. 若将{5,4,3,2,1}排为升序,则(   )排序方法的“移动记录”次数最多。 A.  快速排序                                B.  冒泡排序 C.  直接插入排序                       D.  简单选择排序 8. 用简单选择排序将顺序表{2,3,1 ,3′,2′}排为升序,实施排序1趟后结果是{1 ,3,2 ,3′,2′},则排序3趟后的结果是(   )。 A.  {1 ,2,3 ,3′,2′}                       B.  {1 ,2 ,2′,3 ,3′} C.  {1 ,2′,2 ,3 ,3′}                      D.  {1 ,2 ,2′,3′,3 } 9.下列排序算法中,(    )排序在某趟结束后不一定选出一个元素放到其最终的位置上。 A.选择             B.冒泡           C.归并           D.堆 10.下列排序算法中,稳定的排序算法是(  )。 A.堆排序                B.直接插入排序   C.快速排序              D.希尔排序 11.堆排序的时间复杂度是(    )。 A.O(n*n)                 B.O(n*log n)       C.O(n)                   D.O(log n) 填空题(每空4分,共4分)。 对n个元素进行归并排序,空间复杂度为         。 综合题(共24分)。 1. (共12分)有一组排序的关键字如下: (54,38,96,23,15,72,60,45,83) 分别写出希尔排序(d=5)、快速排序、堆排序、归并排序第一趟升序排序后的结果(其中堆排序的第一趟指序列完成初始建堆、将堆顶元素置为最末位置后其余元素调整为堆的结果)(每个3分)。 希尔排序:   快速排序: 堆排序: 归并排序:  2. (共12分)已知数据序列为(12,5,9,20,6,31,24),对该项数据序列进行排序,分别写出直接插入排序、简单选择排序、快速排序、堆排序、二路归并排序及基数排序第一趟升序排序结果(其中堆排序的第一趟指序列完成初始建堆、将堆顶元素置为最末位置后其余元素调整为堆的结果)(每个2分)。 直接插入排序: 简单选择排序: 快速排序: 堆排序: 二路归并排序: 基数排序:    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值