0


【数据结构】第10章 排序


9.1概述

1. 排序方法的稳定和不稳定

    在排序前后,含相等关键字的记录的**相对位置保持不变**,称这种排序方法是稳定的;    

    反之,含相等关键字的记录的相对位置有可能改变,则称这种排序方法是不稳定的。

2. 内部排序和外部排序

    在排序过程中,只使用**计算机的内存**存放待排序记录,称这种排序为内部排序。

    排序期间文件的全部记录不能同时存放在计算机的内存中,要借助**计算机的外存**才能完成排序,称之为“外部排序”。

    **内外存之间的数据交换次数**是影响**外部排序速度**的主要因素。

3. 存储结构

#define maxsize 1000//待排顺序表的最大长度
typedef int keytype;//关键字类型为int类型

typedef struct
{
    keytype key;//关键字项
    infotype otherinfo;//    其他数据项
}RcdType;    //记录类型

typedef struct
{
    RcdType r[maxsize+1];//r[0]闲置
    int length;//长度
}SqList;     //顺序表类型

4.效率分析

(1) 时间复杂度:
关键字的比较次数和记录移动次数
(2) 空间复杂度:
执行算法所需的附加存储空间
(3) 稳定算法和不稳定算法。

9.2 插入排序

9.2.1 直接插入排序

直接插入排序从什么位置开始?
​ 从第二个元素开始。一共进行n-1次。
0号单元不存,哨兵。
直接插入排序第i趟后序列有什么特征?
​ 前i+1个记录是有序的。

2. 插入排序的思想

    (1)一个记录是有序的;
     (2)从第二个记录开始,按关键字的大小将每个记录插入到**已排好序的序列**中;
     (3)一直进行到第n个记录。

3. 算法概述

    (1)将序列中的**第1个记录**看成是**一个有序的子序列**;
     (2)从第2个记录起按关键字大小逐个进行插入,直至整个序列变成按关键字有序序列为止;
     整个排序过程需要进行比较、后移记录、插入适当位置。从第二个记录到第n个记录共需n-1趟。

4. 直接插入排序算法

#define maxsize 1000//待排顺序表的最大长度
typedef int keytype;//关键字类型为int类型

typedef struct
{
    keytype key;//关键字项
    infotype otherinfo;//    其他数据项
}RcdType;    //记录类型

typedef struct
{
    RcdType r[maxsize+1];//r[0]闲置
    int length;//长度
}SqList;     //顺序表类型

//直接插入排序 
void InsertionSort(SqList &L)
{
    int i,j;
    for(i=2;i<=L.length ;i++)
    {
        if(L.r[i].key <L.r[i-1].key )//后一个元素<前一个元素 
        {
            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];//正确的位置 
        }
    }
    
}

**5. ****算法分析 **

**(1)****稳定性 **

** 直接插入排序是稳定的排序方法。 **

**(2)****算法效率 **

** a.时间复杂度 **

** 最好情况:比较****O(n),移动O(1); **

** 最坏情况:比较O(n2 **),移动O(n**2****); **

** 平均O(n2****) **

** b.空间复杂度 **

** O(1)。**哨兵的位置

9.2.2 其它插入排序

折半插入排序

**6. ****折半插入排序思想 **

**(1)在直接插入排序进行第i个元素时,L.r[1], ****L.r[2], …, L.r[i-1] 是一个按关键字有序的序列; **

**(2)可以利用折半查找实现在“L.r[1], L.r[2], …, L.r[i-1]”中查找****r[i]的插入位置; **

**(3)**称这种排序为折半插入排序。

**9. **折半插入排序算法分析

**(1)****稳定性 **

*折半插入排序是稳定的*排序方法。 **

**(2)****算法效率 **

**a.****时间复杂度 **

最好情况**:比较O(n),移动O(1); **

最坏情况**:比较O(log2 n!),移动O(n2); **

平均O(n2**) **

**b.****空间复杂度 **

**O(1)**

//折半插入排序
void BiInsertionSort(SqList &L)
{
    int i,j;
    for(i=2;i<=L.length,i++)//从第2到n个元素 
    {
        if(L.r[i].key < L.r[i-1].key )//比较 如果比后面值小 存到哨兵
        {
            L.r[0]=L.r[i];
        }
        // 1..i-1中折半查找位置
        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
        for(j=i-1;j>=high+1;--j)
        {
            L.r[j+1]=L.r[j];//后移 
        } 
        L.r[j+1]=L.r[0];//插入 
        
    }
 } 

9.2.3 希尔排序

**1. **希尔排序的思想

**(1)****对待排记录序列先作“宏观”调整, ****再作“微观”调整; **

**(2)****所谓“宏观”调整,指的是,“跳跃 **式” 的插入排序。

**2. **希尔排序概述

**(1)****将记录序列分成若干子序列,分别对每个子序 **

列进行插入排序。

**例如:将 ****n ****个记录分成 ****d ****个子序列: **

**{R[1], R[1+d], R[1+2d], …, R[1+kd]} **

**{R[2], R[2+d], R[2+2d], …, R[2+kd]} **

**…………………………………… **

**{R[d], R[d+d], R[d+2d], …, R[d+kd]} **

**(2)其中,d ****称为增量,它的值在排序过程中从大 **

*到小逐渐缩小,直至最后一趟排序**减为 1*

**问题? **

在增量为d时,希尔排序从什么位置开始?

*答:从d+1*个记录开始。 **

*希尔排序完成增量为d*的排序后,序列有什 ****么特征? **

**答:子序列 ****L.r[i], L.r[i+d], L.r[i+2d], … , 是有序的,1 ****≤ i ≤ **d

**问 题 : 设 希 尔 排 序 的 增 量 序 列 为 *d1,d2,…,dr, 请问:dr*等于多少? **

*答:等于1*。 **

*问题:当希尔排序的增量为1*时的排序 ****过程是什么排序? **

答:是直接插入排序。

**5. ****希尔排序算法分析 **

**(1)****稳定性 **

*希尔排序是不稳定*的排序方法。 **

**(2)****算法效率 **

**(1)****时间复杂度 **

平均O(n1.3**)到平均O(n1.5) **

**(2)****空间复杂度 **

**O(1)**

**4. **希尔排序算法

void ShellInsert(SqList &L,int dk)
{
    int i,j;
    //从dk+1开始 
    for(i=dk+1 ; i<=L.length;i++)
    {
//从当前下标向前 与同一小组的数据进行比较,如果前面数据大,就把前面数据赋值给当前位置
        if( LT(L.r[i].key ,L.r[i-dk].key ) )//同一组元素中前一个相比较 
        {
            L.r[0]=R.r[i];//暂存哨兵
            for(j=i-dk; j>0 &&(LT(L.r[0].key,L.r[j].key ) ) ; j-=dk )
            {
                //后移
                L.r[j+dk]=L.r[j]; 
            } 
            //插入位置
            L.r[j+dk]=L.r[0]; 
        }
    }
}

void ShellSort(SqList &L,int delta[],int t)
{
//1.获取数组长度
    int len=L.length;
 //2.获取初始的间隔长度
    int dk=length/2;
//
    while(dk>=1)
    {
        ShellInsert(L,dk);    
        dk=dk/2;
    }
}

9.3 快速排序 ——交换排序类

*9.3.1***、起泡排序 **

**1. ****实例演示 **


** 问题:冒泡排序一趟后序列有什么特征? **

** 总共需要多少趟排序? **

** 答案:冒泡排序一趟后最大元素沉底,最大 **

** 元素位于它最终的位置上。总共需要n-1趟.**

**2. ****算法思想 **

**(1)从第一个记录开始,两两记录比较,若 L.r[i].key>L.r[i+1].key,则将两个记录交换; **

**(2)第1趟比较结果将序列中关键字最大的记 录放置到最后一个位置,称为“沉底”,而最小 **

**的则上浮一个位置; **

(3)n个记录比较n-1遍(趟)

如果某趟后序列没 有变化,就表示已 经排好了。

**4. **算法分析

       (1)稳定性

           起泡排序是稳定的排序方法。

       (2)时间是复杂性

           最好情况:比较O(n), 移动O(1)

           最坏情况:比较O(n2), 移动O(n2)

           平均情况:O(n2)

       (3)空间复杂性

            O(1)

**3. **算法

void BubbleSort(SqList &L)
{
    int i,j,noswap;
    SqList temp;
    //int n=L.length;
    for(i=1;i<=n-1;i++)
    {
        noswap=1;
        for(j=1;j<=n-i;j++)
        {
            if(L.r[j].key > L.r[j+1].key )//后面小 交换 
            {
                temp=L.r[j];
                L.r[j]=L.r[j+1];
                L.r[j+1] =temp;
                noswap=0;
            }
        }
        if(noswap==1)break;//如果某趟后序列没有变化,就表示已经排好了。
    }
} 

while

void BubbleSort(Elem R[],int n)
{
    i=n;
    while(i>1)
    {
        lastindex=1;
        for(j=1;j<i;j++)
        {
            if(R[j+1].key < R[j].key )
            {
                Swap(R[j],R[j+1]);
                lastindex=j;//记下进行交换的记录位置
            }//if
        }
        i = lastExchangeIndex; // 本趟进行过交换的
    }// while // 最后一个记录的位置
 } //表明i之后是有序的

9.3.2****、快速排序

*1. 基本思想*分治算法

      任取待排序对象序列中的某个对象v(枢轴,基准,支点),按照该对象的关键字大小,将整个序列划分为左右两个子序列;

     (1)左侧子序列中所有对象的关键字都小于或等于对象v的关键字;

     (2)右侧子序列中所有对象的关键字都大于或等于对象v的关键字;

     (3)对象v则排在这两个子序列中间(也是它最终的位置)。

目标:找一个记录,以它的关键字作为“枢轴”

凡其关键字小于枢轴的记录均移动至该记录之前,反

之,凡关键字大于枢轴的记录均移动至该记录之后

致使一趟排序之后,记录的无序序列R[s..t]将**分割成两 **

部分:R[s..i-1]和R[i+1..t],且

R[j].key≤ R[i].key ≤ R[j].key

**(**s≤j≤i-1) **枢轴 **

(i+1≤j≤t)。

*问题:***快速排序一趟后序列有什么特征? **

**答案:存在一个元素,这个元素左边元素的 **

**关键字不大于它的关键字,它右边元素的关 **

键字不小于它的关键字

**4. **算法分析

**(1)****稳定性 **

*快速排序是不稳定*的排序方法。 **

**(2)****时间复杂度 **

**最坏情况: **

*1,2,3,…,n n,n-1,…2,1***。 **

比较O(n2**)**

**时间复杂性最好情况是每次选择的基准把左 **

**右分成相等两部分: **

**C(n)≤n+2C(n/2) **

≤n+2(n/2+2C(n/4))=2n+22C(n/2**2) **

**…… **

≤kn+2kC(n/2**k) **

*最后组中只有一个元素:n=2k ,k=log2***n, **

*C(n)≤nlog2*n+nC(1) **

*最好情况的时间复杂度为O(nlog2***n) **

平均时间复杂度也是O(nlog2****n)

**(3)****空间复杂度 **

**由于快速排序是一个递归过程,需一个栈的附加空 **

*间,栈的平均深度为O(log2***n)****。 **

假设一次划分所得枢轴位置 i=k,则对

***n ***个记录进行快排所需时间:

其中 Tpass(n)为对 ***n ***个记录进行一次划分

所需时间。

若待排序列中记录的关键字是随机分布

的,则 ***k ***取 1 至 ***n ***中任意一值的可能性相

结论**: 快速排序是所有同量级Onlogn****) **

的排序 方法中,平均性能最好的

int Partition(SqList &L,int low,int high)
{
    keytype pivotkey;//基准
    L.r[0]=L.r[low];
    pivotkey=L.r[low].key;
    while(low<high)
    {
        while(low<high && L.r[high].key>=pivotkey)
        {
            --high;
        }//找到比基准小的key对应的下标high 
        L.r[low]=L.r[high];//原本low的位置用这个值代替 
        while(low<high && L.r[low].key<=pivotkey)
        {
            low++;
        }
        L.r[high]=L.r[low];//原本high所在位置用此时low的值代替 
    }
    L.r[low]=L.r[0];  // 枢轴记录到位
    return low;     // 返回枢轴位置
}  // Partition

void QSort(SqList &L,int low,int high)//递归 
{
    int pivotloc;
    if(low<high)
    {
        pivotloc=Partition(L,low,high);// 将L.r[low..high]一分为二
        QSort(L,low,pivotloc-1);  // 对低子表递归排序,pivotloc是枢轴位置
        QSort(L, pivotloc+1, high);// 对高子表递归排序
    }
 } 

**5. **算法改进

**若待排记录的初始状态为按关键字有序时,快速排序 **

将蜕化为起泡排序,其时间复杂度为O(n**2)****。 **

*为避免出现这种情况,***需在进行一次划分之前,进行 “予处理”,即: **

*先对 *R(s).key, R(t).key *和 ***,进行相互***比较,然后**关键字为 “三者之中”的记录为枢轴*记 **

录。

9.4 选择排序

9.4.1 简单选择排序

**1. ****算法思想 **

** (1)第一次从n个关键字中选 择一个最小值,确定第一个; **

** (2)第二次再从剩余元素中选 择一个最小值,确定第二个; **

** (3)共需n-1次选择。**

**2. ****实例演示 **

** 问题:简单选择****排序一趟后序列有什么特征? **

** 答案:**最小元素排在第一位。

**3. ****算法概述 **

*设需要排序的表是***R[n+1]: **

**(1)第一趟排序是在无序区R[1]A[n]****中选出关 **

*键字最小的记录,将它与***R[1]交换,确定最小值; **

**(2)第二趟排序是在R[2]r[n]****中选关键字最小 **

*的记录,将它与***R[2]****交换,确定次小值; **

**(3)i趟排序是在R[i]R[n]****中选关键字最小的 **

*记录,将它与***R[i]****交换; **

**(4)n-1**趟排序。

**5. **算法分析

**(1)****稳定性 **

*简单选择排序方法是不稳定*的。 **

**(2)****时间复杂度 **

*比较O(n*2 ****),移动最好O(1),最差O(n) **

**(3)****空间复杂度 **

****O(1)**

对 n 个记录进行简单选择排序,所 需进行的 **关键字间的比较次数 **总计为:

移动记录的次数,最小值为 0, 最大 值为3(n-1) 。 //交换两个值需要三次交换 temp=a a=b b=temp

**4. ****算法 **

算法概要:

void SelectSort (Elem R[], int n ) 
{
    // 对记录序列R[1..n]作简单选择排序。
    
    for (i=1; i<n; ++i) {
        // 选择第 i 小的记录,并交换到位
        j = SelectMinKey(R, i);
        // 在 R[i..n] 中选择关键字最小的记录
        if (i!=j) R[i]←→R[j];// 与第 i 个记录交换
    }
} // SelectSort

算法:

void Select(SqList &L)
{
    int i,j,min;//min存储L.r[i...n]中最小值的下标
    for(i=1;i<L.length;i++)
    {
        min=i;
        for(j=i+1;j<=L.length;j++)
        {
            if(L.r[j].key<L.r[min].key)
                min=j;
            
        }
        if(min!=i)//如果min较比初值发生变化,则交换
        {
            L.r[0]=L.r[i]; 
            L.r[i]=L.r[min]; 
            L.r[min]=L.r[0];
        }
    }
 } 

**9.4.1 **堆排序

**1. ****引入 **

** 堆排序属于选择排序****:**出发点是
** 利用选择排序已经发生过的比较, **

** 记住比较的结果,减少重复“比较” 的次数**

**2. ****堆的定义 **

在大根堆中,哪个结点关键字最大?

答:根节点的关键字最大。

左右不论 (二叉排序树则是左小于右)

根节点与最后一个节点交换

问题:利用大根堆排序一趟后序 列有什么特点?

答:最大元素在最后。

**3. ****堆排序算法思想 **

**堆排序需解决两个问题: **

**(1) ****由一个无序序列建成一个堆。 **

**(2) ****在输出堆顶元素之后,调整剩余元 **

素成为一个新的堆。

4. 堆排序算法概要(采用大根堆)

**(1) 按关键字建立R[1],R[2],…R[n]的大根堆; **

**(2) 输出堆顶元素,采用堆顶元素R****[1]****与最后 **

一个元素**R[n]****交换,最大元素得到正确的 **

排序位置**; **

**(3) 此时前n-1****个元素不再满足堆的特性,需重 **

建堆**; **

**(4) 循环执行(2),(3)**两步,到排序完成。

**5. ****筛选算法实例演示 **

 (1)先建一个完全二叉树:

(2)从最后一个非终端结点 开始建堆;

n个结点,最后一个非终端结点的下标是

**8. **算法分析

**(1)堆排序是不稳定****的排序。 **

**(2)时间复杂度为O(nlog2n)**

*最坏情况下时间复杂度为O(nlog2***n)****的算法。 **

**(3)空间复杂度为O(1)**

**1. ****对深度为 ****k ****的堆,“筛选”所需进行的关键字 **

*比较的次数至多为***2(k-1)****; **

2. **对 *n 个关键字,建成深度为h*(=***log*2n****+1)****的堆, **

*所需进行的关键字比较的次数至多 *4**n**

**3. ****调整“堆顶” *****n-1 *****次,总共进行的关键 **

**字比较的次数不超过 **

*2 (***log*2 ****(n-1)+ log****2 (n-2)+ …+log22*) < 2n(log*2n***) **

因此,堆排序的时间复杂度为O(nlog***n*****)**

**6. ****筛选算法 **

**7. ****堆排序算法 **

void HeapAdjust(HeapType &H,int s,int m)
{//从H中调整下标s的位置  一共M个元素 
    int j;
    RedType rc;
    rc=H.r[s];//将要移动的元素 暂存 
    //暂存堆顶r[s]到rc
    for(j=2*s ; j<=m; j*=2)//2*s是左孩子 
    {
        //如果左孩子<右孩子 
        if(j<m && H.r[j].key<H.r[j+1].key )//横比,j初值指向左孩子
        {
            ++j;//就指向右孩子 
        }//如果右孩子大于左孩子,j指向右孩子,j指示关键字较大位置
        if(rc.key>=H.r[j].key)//如果基准值大于等于j的key 
            break;//纵比,如果…,定位成功
        H.r[s]=H.r[j];//指向当前结点的左孩子 
        s=j;//
        //否则,r[j]上移到s,s变为j, 然后j*=2,进入下一层
    }
    H.r[s]=rc;//插入 
    // 将调整前的堆顶记录rc到位置s中
}

void HeapSort(HeapType &H)
{
    int i;
    RcdType temp;
    for(i=H.length/2 ;i>0;--i)//建堆
    {
        HeapAdjust(H,i,H.length);
    }
    for(i=H.length ;i>1;--i)
    {
        //交换r[1]和r[i]
        temp=H.r[i];
        H.r[i]=H.r[1];
        H.r[1]=temp;
        HeapAdjust(H,1,i-1); //调整,使得1~i-1符合堆的定义
    }
 } 

9.5 归并排序

**1. ****归并的定义 **

** 归并又叫合并,是指把两个或两个以上的有序序列合并成一个有序序列。 **

**2. ****归并实例演示 **

**3. ****归并算法 **

**4. ****归并排序实例 **

**5. ****归并排序思想 **

**6. ****归并排序核心算法 **

**7. ****归并排序算法 **

**8. **算法分析

*将两个长度分别为nm*的递增有序顺序表归 **

**并成一个有序顺序表,其元素最多的比较次 **

**数是 **

**n **

**m+n **

**MIN(m,n) **

**m+n-1 **

A

问题分析

二路归并排序的思想是:将待排序 的数列分成相等的两个子数列(数量 相差±1),然后,再使用同样的算 法对两个子序列分别进行排序,最 后将两个排好的子序列归并成一个 有序序列。

算法设计与描述 MergeSort( A,p,r )算法分析输入:n个数的无序序列A[p,r] , 1<= p <= r <= n ,(1)输入n,计算规模是n输出:n个数的有序序列A[p,r]
MergeSort (A,p,r)

{
if( p<r )

    {

            q <- (p+r)/2 ;//中间

            MergeSort (A,p,q); //左侧

            MergeSort(A,q+1,r) ;//右侧

            Merge (A,p,q,r); 

    } 

}

(2)核心操作为 移动盘子

(3)

(4)

C
算法设计与描述 Merge( A,p,q,r )算法分析输入:n按递增顺序排好的 A[p...q] 和 Aq+1...r 输入n,计算规模是n输出:按照递增顺序排序的A[p,r]
Merge (A,p,q,r)

{

    x <- q-p+1 ,y <- r-q;//x,y分别是两个子数组的元素数

    A[p...q] -> B[1...x] , A[q+1...r] -> C[1...y] //两个新数组
     

    i <- 1 ,j<- 1 ,k <- p 

    while i<=x and j <= y do 

    {

            if ( B[i] <= C[ j ] ) //注意是i 和 j 

            {

                    A[k] <- B[i];//较小数

                    i <- i+1;

            }

            else

           {

                    A[k] <- C[j] ;

                    j <- j+1;

           }

            k=k+1;

    } 

    if(i>x)        C[j...y] -> A[k..r] //如果还要剩余的元素,就不需要比较,直接赋值回去就行

    else         B[i..x] -> A[k...r]

}

(2)核心操作为 移动盘子

(3)根据递推公式,。。。【不全】

9.6 基数排序

9.7 内部排序方法的比较


本文转载自: https://blog.csdn.net/iivan_cpp/article/details/121483752
版权归原作者 致命小学期 所有, 如有侵权,请联系我们删除。

“【数据结构】第10章 排序”的评论:

还没有评论