数据结构与算法·第10章【内部排序】

这篇具有很好参考价值的文章主要介绍了数据结构与算法·第10章【内部排序】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概念

排序问题可以分为内部排序和外部排序。若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序;反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。

在内部排序中,若对于两个相等的元素 K i 和 K j ( i ≠ j ) Ki 和 Kj(i≠j) KiKji=j,在排序前的序列中 R i Ri Ri 领先于 R j Rj Rj(即 i < j i<j i<j),排序后的序列中 Ri 仍领先于 Rj,则称所用的排序方法是稳定的;反之,若可能使排序后的序列中 R j Rj Rj 领先于 R i Ri Ri,则称所用的排序方法是非稳定的。

  • 当待排序的数据集包含多个关键字,并且需要根据其中一个关键字进行排序,同时又需要维持其他关键字的相对顺序时,稳定性非常重要。这样可以确保排序后的结果在关键字相同时仍然保持正确的顺序,不会打乱其他关键字的排序。

  • 在一些实际应用中,原始数据可能已经部分有序。如果排序算法是稳定的,那么相等元素的相对顺序将被保持,不会破坏原始的部分有序性。

  • 在某些场景下,排序的稳定性是问题的约束条件。例如,如果需要对学生成绩进行排序,并且要求相同分数的学生按照其在班级中的考号顺序排序,那么只有稳定的排序算法才能满足要求。

插入排序

插入排序的基本思想是将待排序的序列分成已排序和未排序的两部分,每次从未排序的部分取出第一个元素,插入到已排序部分合适的位置,直到未排序部分为空为止。具体操作如下:

  1. R [ 1.. i − 1 ] R[1..i-1] R[1..i1] 中查找 R [ i ] R[i] R[i] 的插入位置,使得 R [ 1.. j ] . k e y ≤ R [ i ] . k e y < R [ j + 1.. i − 1 ] . k e y R[1..j].key ≤ R[i].key < R[j+1..i-1].key R[1..j].keyR[i].key<R[j+1..i1].key
  2. R [ j + 1.. i − 1 ] R[j+1..i-1] R[j+1..i1] 中的所有记录均后移一个位置;
  3. R [ i ] R[i] R[i] 插入(复制)到 R [ j + 1 ] R[j+1] R[j+1] 的位置上。

这样就完成了一次插入操作,对于待排序的序列,重复执行以上操作直到全部排序完成。

直接插入排序

直接插入排序的基本思想是将待排序的序列分成已排序和未排序的两部分,每次从未排序的部分取出第一个元素,插入到已排序部分合适的位置,直到未排序部分为空为止。具体操作如下:

  1. 初始时,将 R [ 1 ] R[1] R[1] 看作是有序区, R [ 2.. n ] R[2..n] R[2..n] 构成无序区;
  2. 依次将无序区的元素插入到有序区中,使得有序区始终有序。插入操作包括以下三步:
    1)在 R [ 1.. i − 1 ] R[1..i-1] R[1..i1] 中查找 R [ i ] R[i] R[i] 的插入位置,使得 R [ 1.. j ] . k e y ≤ R [ i ] . k e y < R [ j + 1.. i − 1 ] . k e y R[1..j].key ≤ R[i].key < R[j+1..i-1].key R[1..j].keyR[i].key<R[j+1..i1].key
    2)将 R [ j + 1.. i − 1 ] R[j+1..i-1] R[j+1..i1] 中的所有记录均后移一个位置;
    3)将 R [ i ] R[i] R[i] 插入(复制)到 R [ j + 1 ] R[j+1] R[j+1] 的位置上。
  3. 重复执行 2 直到无序区为空,排序完成。
void InsertionSort(SqList& L) {
    // 对顺序表 L 作直接插入排序
    for (int i = 2; i <= L.length; ++i) {
        if (L.r[i].key < L.r[i - 1].key) {
            // 将当前待排序的记录暂存到监视哨中,等待插入
            L.r[0] = L.r[i];
            int j;
            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]; // 插入到正确位置
        }
    }
}

最好的情况:

  • 序列顺序有序,比较的次数:n-1,移动的次数:0

最坏的情况:
数据结构与算法·第10章【内部排序】
时间复杂度大概 O ( n 2 ) O(n^2) O(n2)

其他插入排序

折半插入

void BiInsertionSort(SqList &L) {
    for (int i = 2; i <= L.length; ++i) {
        L.r[0] = L.r[i];      // 将 L.r[i] 暂存到 L.r[0]
        int low = 1, high = i - 1;
        while (low <= high) { 
            int mid = (low + high) / 2; // 折半
            if (L.r[0].key < L.r[mid].key)
                high = mid - 1;   // 插入点在低半区
            else
                low = mid + 1;    // 插入点在高半区
        }
        for (int j = i - 1; j >= high + 1; --j) {
            L.r[j + 1] = L.r[j];      // 记录后移
        }
        L.r[high + 1] = L.r[0];  // 插入
    } 
}

L.r[high + 1] = L.r[0]; // 插入是在 h i g h + 1 high+1 high+1的位置插入(此时,low>high)

希尔排序

希尔排序(又称缩小增量排序)

基本思想:对待排记录序列先作“宏观”调整,再作“微观”调整。所谓“宏观”调整,指的是“跳跃式”的插入排序。具体做法为:

1.将记录序列分成若干子序列,分别对每个子序列进行插入排序。

2.待整个序列中的纪录‘基本有序’时,再对全体记录进行一次直接插入排序。

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

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

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

… …

R [ d ] , R [ 2 d ] , R [ 3 d ] , … , R [ k d ] , R [ ( k + 1 ) d ] { R[d],R[2d],R[3d],…,R[kd],R[(k+1)d] } R[d]R[2d]R[3d]R[kd]R[(k+1)d]

其中, d d d 称为增量,它的值在排序过程中从大到小逐渐缩小,直至最后一趟排序减为 1。

数据结构与算法·第10章【内部排序】

冒泡排序

void BubbleSort(Elem R[], int n) {
    int i = n;
    while (i > 1) { 
        int lastExchangeIndex = 1; 
        for (int j = 1; j < i; j++) {
            if (R[j+1].key < R[j].key) { 
                Swap(R[j], R[j+1]);
                lastExchangeIndex = j;
            } //if
        } //for
        i = lastExchangeIndex; // 本趟进行过交换的最后一个记录的位置          
    } // while
} // BubbleSort
  1. 起泡排序的结束条件为,最后一趟没有进行“交换记录”。

  2. 一般情况下,每经过一趟“起泡”,i减1,但并不是每趟都如此。具体来说,每一趟排序时,我们都记录下最后一次交换操作的位置,如果在一趟排序结束之后,最后一次交换操作的位置和上一趟排序结束时的位置相同,那么说明这次排序并没有进行任何交换操作,也就是说从该位置之后的元素已经有序。此时,我们便可以认为序列已经有序了,因此结束算法的执行。

数据结构与算法·第10章【内部排序】
时间复杂度大概 O ( n 2 ) O(n^2) O(n2)

快速排序

找一个记录,以它的关键字作为“枢轴”,凡其关键字小于枢轴的记录均移动至该记录之前,反之,凡关键字大于枢轴的记录均移动至该记录之后。

经过一趟排序之后,记录的无序序列 R [ s . . t ] R[s..t] R[s..t]将分割成两部分: R [ s . . i − 1 ] R[s..i-1] R[s..i1] R [ i + 1.. t ] R[i+1..t] R[i+1..t],且 R [ j ] . k e y ≤ R [ i ] . k e y ≤ R [ p ] . k e y ( s ≤ j ≤ i − 1 )  枢轴  ( i + 1 ≤ p ≤ t ) R[j].key\leq R[i].key \leq R[p].key (s\leq j\leq i-1)~ 枢轴 ~(i+1\leq p\leq t) R[j].keyR[i].keyR[p].key(sji1) 枢轴 (i+1pt)其中 i i i表示枢轴记录的位置, j ≤ i − 1 j \leq i-1 ji1的记录的关键字都小于等于枢轴的关键字, p ≥ i + 1 p \geq i+1 pi+1的记录的关键字都大于等于枢轴的关键字。注意,这里假设枢轴所在的位置不是 s s s t t t,否则就没有对应的一侧了。

数据结构与算法·第10章【内部排序】

int Partition (RedType& R[], int low, int high) {
    pivotkey = R[low].key;  // 用子表的第一个记录作枢轴记录
    while (low < high) {    // 从表的两端交替地向中间扫描
        while (low < high && R[high].key >= pivotkey)    
            --high;  
        R[low] ←→ R[high];   // 将比枢轴记录小的记录交换到低端
        while (low < high && R[low].key <= pivotkey) 
            ++low;   
        R[low] ←→ R[high];  // 将比枢轴记录大的记录交换到高端
    }
    return low;  // 返回枢轴所在位置
} // Partition

void QSort (RedType & R[], int low, int high) {
    // 对记录序列R[low..high]进行快速排序
    if (low < high) {  // 长度大于1
        pivotloc = Partition(R, low, high);
        // 对 R[s..t] 进行一次划分
        QSort(R, low, pivotloc - 1);
        // 对低子序列递归排序,pivotloc是枢轴位置
        QSort(R, pivotloc + 1, high); // 对高子序列递归排序
    }
} // QSort

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

选择排序

数据结构与算法·第10章【内部排序】
从未排序的序列选择一个最小的元素排到有序序列里

和插入排序的区别:

  • 插入:找到未排序序列的第一个元素,并在有序序列里面查找到插入位置
  • 选择:找到未排序序列最小的元素,并添加到有序序列的最后处
void SelectSort(Elem R[], int n) {
    // 对记录序列 R[1..n] 进行简单选择排序。
    for (int i = 1; i < n; ++i) {
        // 选择第 i 小的记录,并交换到位
        int j = SelectMinKey(R, i);
        // 在 R[i..n] 中选择关键字最小的记录
        if (i != j) {
            // 与第 i 个记录交换
            R[i] ↔ R[j];
        }
    }
} // SelectSort

数据结构与算法·第10章【内部排序】

堆排序

数据结构与算法·第10章【内部排序】
数据结构与算法·第10章【内部排序】
光看定义还有点不太明白,但是根据就应该很明晰了

建大顶堆

数据结构与算法·第10章【内部排序】
数据结构与算法·第10章【内部排序】
自下而上

归并排序

  • 归并排序的过程基于下列基本思想进行: 将两个或两个以上的有序子序列 “归并” 为一个有序序列
  • 在内部排序中,通常采用的是2-路归并排序。即:将两个位置相邻的记录有序子序列

数据结构与算法·第10章【内部排序】
比较简单,看一下即可

习题

各个排序

数据结构与算法·第10章【内部排序】
最后1个考试不涉及

  • 直接插入排序:503 087 512 061 908 170 897 275 653 426
    第一趟结果:087 503 512 061 908 170 897 275 653 426
    第二趟结果:087 503 512 061 908 170 897 275 653 426
    第三趟结果:061 087 503 512 908 170 897 275 653 426
    第四趟结果:061 087 503 512 908 170 897 275 653 426
    第五趟结果:061 087 170 503 512 908 897 275 653 426
    第六趟结果:061 087 170 503 512 897 908 275 653 426
    第七趟结果:061 087 170 275 503 512 897 908 653 426
    第八趟结果:061 087 170 275 503 512 653 897 908 426
    第九趟结果:061 087 170 275 426 503 512 653 897 908
    粗体为已经排好序的序列

  • 希尔排序初始关键字: 503 087 512 061 908 170 897 275 653 426
    第一趟结果:d[1]=5 170 087 275 061 426 503 897 512 653 908
    第二趟结果:d[2]=3 061 087 275 170 426 503 897 512 653 908
    第三趟结果:d[3]=1 061 087 170 275 426 503 512 653 897 908
    主要注意一下希尔排序是以下标号的后x个作排序——在d[1]=5,a[0]=503是a[5]=170排序的

  • 快速排序数据结构与算法·第10章【内部排序】
    注意,快速排序不是把比Key小的数直接随便放到Key前面,是用low和high遍历出来的

  • 堆排序
    数据结构与算法·第10章【内部排序】
    小顶堆

  • 归并排序(自底向上)
    503 087 512 061 908 170 897 275 653 426
    (087 503) (061 512) (170 908) (275 897) (426 653)
    (061 087 503 512) (170 275 897 908) (426 653)
    (061 087 170 275 503 512 897 908) (426 653)
    (061 087 170 275 426 503 512 653 897 908)

堆排序

数据结构与算法·第10章【内部排序】
数据结构与算法·第10章【内部排序】
第4问的答案应该是错的

监视哨

数据结构与算法·第10章【内部排序】

void directInsertSort(int L[], int k) {
    int i, j;
    for (i = 2; i <= k; i++) {
        L[k+1] = L[i]; 
        j = i - 1;
        while (L[j] > L[0]) {
            L[j + 1] = L[j];
            j--;
        }
        L[j + 1] = L[k+1]; 
    }
}

设计算法

数据结构与算法·第10章【内部排序】

void process(int A[n]) {
    int low = 0;
    int high = n - 1;
    while (low < high) {
        while (low < high && A[low] < 0)
            low++;
        while (low < high && A[high] > 0)
            high++;
        if (low < high) {
            // 交换 A[low] 和 A[high]
            int temp = A[low];
            A[low] = A[high];
            A[high] = temp;
            low++;
            high--;
        }
    }
    return;
}

双指针法
时间复杂度 O ( n ) O(n) O(n)

荷兰国旗问题

数据结构与算法·第10章【内部排序】文章来源地址https://www.toymoban.com/news/detail-491032.html

typedef enum {RED, WHITE, BLUE} color; // 定义枚举类型表示三种颜色
void Flag_Arrange(color a[], int n) {
    int i = 0;
    int j = 0;
    int k = n - 1;
    while (j <= k) {
        switch (a[j]) {
            case RED:
                // a[i] 与 a[j] 交换
                // 增加 i 和 j 的值,同时继续处理下一个元素
                swap(a[i], a[j]);
                i++;
                j++;
                break;
            case WHITE:
                // 当遇到白色时,只需要将 j 向右移动一位
                j++;
                break;
            case BLUE:
                // a[j] 与 a[k] 交换
                // 不增加 j 的值,因为可能需要再次检查交换后的 a[j]
                // 减少 k 的值,将蓝色元素移至数组末尾
                swap(a[j], a[k]);
                k--;
                break;
        }
    }
}

到了这里,关于数据结构与算法·第10章【内部排序】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构——排序算法——归并排序

    在第二个列表向第一个列表逐个插入的过程中,由于第二个列表已经有序,所以后续插入的元素一定不会在前面插入的元素之前。在逐个插入的过程中,每次插入时,只需要从上次插入的位置开始,继续向后寻找插入位置即可。这样一来,我们最多只需要将两个有序数组遍历

    2024年02月09日
    浏览(42)
  • 【排序算法】数据结构排序详解

    前言: 今天我们将讲解我们数据结构初阶的最后一部分知识的学习,也是最为“炸裂”的知识---------排序算法的讲解!!!! 排序 :所谓排序,就是使一串记录,按照其中的某个或某些的大小,递增或递减的排列起来的操作。 稳定性 :假定在待排序的记录序列中,

    2023年04月08日
    浏览(49)
  • 数据结构和算法笔记4:排序算法-归并排序

    归并排序算法完全遵循分治模式。直观上其操作如下: 分解:分解待排序的n个元素的序列成各具n/2个元素的两个子序列。 解决:使用归并排序递归地排序两个子序列。 合并:合并两个已排序的子序列以产生已排序的答案。 我们直接来看例子理解算法的过程,下面是要排序

    2024年01月21日
    浏览(62)
  • 【数据结构与算法】十大经典排序算法-希尔排序

    🌟 个人博客: www.hellocode.top 🏰 Java知识导航: Java-Navigate 🔥 CSDN: HelloCode. 🌞 知乎 :HelloCode 🌴 掘金 :HelloCode ⚡如有问题,欢迎指正,一起学习~~ 希尔排序是一种插入排序的改进版本,旨在解决插入排序在处理大规模数据时的效率问题。通过将数组分为多个子序列并对

    2024年02月12日
    浏览(75)
  • 【数据结构与算法】十大经典排序算法-插入排序

    🌟 个人博客: www.hellocode.top 🏰 Java知识导航: Java-Navigate 🔥 CSDN: HelloCode. 🌞 知乎 :HelloCode 🌴 掘金 :HelloCode ⚡如有问题,欢迎指正,一起学习~~ 插入排序(Insertion Sort)是一种简单直观的排序算法,其基本思想是将一个记录插入到已排好序的有序序列中,直到所有记录

    2024年02月13日
    浏览(80)
  • 【数据结构与算法】十大经典排序算法-冒泡排序

    🌟 个人博客: www.hellocode.top 🏰 Java知识导航: Java-Navigate 🔥 CSDN: HelloCode. 🌴 掘金 :HelloCode 🌞 知乎 :HelloCode ⚡如有问题,欢迎指正,一起学习~~ 冒泡排序(Bubble Sort)是一种简单的排序算法,它通过重复地交换相邻元素的位置来将最大(或最小)的元素逐步“冒泡”到

    2024年02月14日
    浏览(69)
  • 【数据结构与算法】十大经典排序算法-快速排序

    🌟 个人博客: www.hellocode.top 🏰 Java知识导航: Java-Navigate 🔥 CSDN: HelloCode. 🌞 知乎 :HelloCode 🌴 掘金 :HelloCode ⚡如有问题,欢迎指正,一起学习~~ 快速排序(Quick Sort)是一种高效的排序算法,是对冒泡排序的优化。它采用分治法(Divide and Conquer)的思想,将待排序序列

    2024年02月13日
    浏览(62)
  • 数据结构——排序算法之快速排序

        个人主页: 日刷百题 系列专栏 : 〖C/C++小游戏〗 〖Linux〗 〖数据结构〗   〖C语言〗 🌎 欢迎各位 → 点赞 👍+ 收藏 ⭐️+ 留言 📝  ​ ​ 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。 基本思想: 任取待排序元素序列中 的某元素作为基准值,按照

    2024年01月21日
    浏览(54)
  • 【数据结构与算法】排序算法(选择排序,冒泡排序,插入排序,希尔排序)

    基本概念这了就不浪费时间解释了,这四种都是很简单的排序方式,本专栏后续文章会出归并排序,计数排序,快速排序,堆排序,桶排序等排序算法,今天这篇文章中给出选择排序,冒泡排序,插入排序和希尔排序的实现; 如果发现文章中有错误,还请大家指出来,我会非

    2024年02月15日
    浏览(81)
  • 数据结构与算法-排序算法

    递归将整个函数的调用过程 调用过程 如何在CSDN博客中插入公式和各种符号 类似二叉树的后续遍历 递归行为和递归行为时间复杂度的估算 master 公式 : T ( n ) = a × T ( n b ) + O ( n d ) T(n) = a times T (frac{n}{b}) + O(n^d) T ( n ) = a × T ( b n ​ ) + O ( n d ) T ( n ) T(n) T ( n ) : 母问题的规模

    2024年02月15日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包