【数据结构】选择排序—直接选择排序、树形选择排序

简介: 【数据结构】选择排序—直接选择排序、树形选择排序

一、什么是选择排序?


1. 选择排序的主要思想是每一趟从待排序列中选取一个关键字值最小的记录,也即第1趟从n个记录中选取关键字最小的记录,在第2趟中,从剩下的n-1个记录中选取关键字值最小的记录,直到整个序列中的记录都选完位置,这样,由选取记录的顺序便可得到按关键字有序的排序。


2. 常见的三种选择排序方式:


直接选择排序


树形选择排序


堆排序


二、直接选择排序


1)概述


首先在所有记录中选出关键字值最小的记录,把它与第一个记录进行位置交换,


然后在其余的记录中再选出关键字值次小的记录,与第二个记录进行位置交换


依次类推,直到所有记录排好序。


2)示意图


c9e93d0a6fab4f339b58948380c339a0.gif


3)算法分析


主要步骤:


置i 的初值为0


当i<n-1时,重复下列步骤:


1.在无序子序列{r[i+1], ... , r[n-1]}中选出一个关键字值最小的记录r[min]


2. 若r[min]不是r[i](即min != i),则交换r[i]和r[min]的位置,否则不进行交换


3.将i的值加1


5eacf184ca824367b02ca59023195038.png

4)算法代码实现


代码

//【算法】直接选择排序
public void selectSort() {
 //   System.out.println("直接选择排序");
    RecordNode temp; //辅助结点
    for (int i = 0; i < this.curlen - 1; i++) {//n-1趟排序
        //每趟在从r[i]开始的子序列中寻找最小元素
        int min = i;               //设第i条记录的关键字最小
        for (int j = i + 1; j < this.curlen; j++) {//在子序列中选择关键字最小的记录
            if (r[j].key.compareTo(r[min].key) < 0) {
                min = j;             //记住关键字最小记录的下标
            }
        }
        if (min != i) {    //将本趟关键字最小的记录与第i条记录交换
            temp = r[i];
            r[i] = r[min];
            r[min] = temp;
        }
        System.out.print("第" + (i + 1) + "趟: ");
        display();
    }
}
  • 测试
public class TestSeqList7_select {
    public static void main(String[] args) throws Exception {
        int[] arr = {52,39,67,95,70,8,95,25};
        SeqList seqList = new SeqList(arr.length);
        System.out.print("序列号:");
        for (int i = 0; i < arr.length; i++) {
            System.out.print("  " + i);
            seqList.insert(i, new RecordNode(arr[i]));
        }
        System.out.println();
        // 直接选择排序
        seqList.selectSort();
    }
}
//直接选择排序
//序列号:  0  1  2  3  4  5  6  7
//第1趟:   8 39 67 95 70 52 95 25
//第2趟:   8 25 67 95 70 52 95 39
//第3趟:   8 25 39 95 70 52 95 67
//第4趟:   8 25 39 52 70 95 95 67
//第5趟:   8 25 39 52 67 95 95 70
//第6趟:   8 25 39 52 67 70 95 95
//第7趟:   8 25 39 52 67 70 95 95

5)性能分析


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

7754889159c04eb09ea96ae8f12e21d7.png



移动记录的次数,最小值为0,最大值为3(n-1)


时间复杂度:O(n2)


空间复杂度:O(1) ,仅用一个辅助单元


直接选择排序是不稳定排序算法。


6)练习


练习1:使用直接选择排序,写出每一趟的排序结果。


序列:16, 15, 50, 53, 64, 7


24740fa8cb7e45b2a53e75eeec196f88.png


练习2:使用直接选择排序,写出每一趟的排序结果。


序列:2,5,8,3,6,9,1,4,7


2e6f357d96b84283a8df4afda5b33e43.png


练习3:使用直接选择排序,写出每一趟的排序结果


序列:9 , 20 , 13 , 20 , 12


f658aff76e5a483e9bf7f16862d36346.png


三、树形选择排序


1)概述


在直接选择排序中,关键字的总比较次数是n(n-1)/2 。


实际上,该方法中,有许多关键字之间进行了不止一次的比较,也就是说,两个关键字之间可能进行了两次以上的比较。能否在选择最小关键字值记录的过程中,把关键字比较的结果保存下来,以便在以后需要的时候直接查看这个比较结果,而不需要再进行比较呢。 答案是肯定的。


树形选择排序(Tree Selection Sort)的原理与此类似。


树形选择排序又称为锦标赛排序。


也就是,体育比赛中的淘汰制,每两个对手经过比赛留下 获胜者继续参加下一轮的淘汰赛,直到最后剩下两个对手进行决赛争夺冠军。


2)算法分析


待排序序列为:52, 39, 67, 95, 70, 8, 25, 52


首先针对n个记录进行两两比较,比较的结果是把关键字值较小者作为优胜者上升到父节点,得到n/2个优胜者,作为第一步比较的结果保留下来。


然后对n/2个记录再进行关键字的两两比较,如此重复,直到选出一个关键字值最小的记录为止。


cb5acae4a6714a90b49781a220aba380.png


这个过程可用一个含有n个叶子节点的完全二叉树来表示,这种树称为胜者树。


接着,再求出次小关键字记录,只需将叶子节点中最小的关键字值改为∞,然后从该叶子节点开始与其左(或右)兄弟的关键字进行比较,用较小关键字修改父节点关键字,用此方法,从下到上修改从该叶子结点到根节点路径上的所有结点的关键字,则可得到次小关键字记录。


8650c6e6194f4f5590481ef90657bfac.png


以此类推,可依次选出从小到大的所有关键字。


55a7f66a585346e1b57449cd00a697d8.png


3)树结点类

class TreeNode {      //胜者树的结点类
    public RecordNode data;  //排序记录结点数据值
    public int index;        //结点在满二叉树中的序号
    public int active;       //参加选择标志,1表示参选,0表示不参选
}

4)算法:树形选择排序


1.【算法】 树形选择排序(锦标赛排序)

//【算法】 树形选择排序(锦标赛排序)
//建立树的顺序存储数组tree,并对其排序,并将结果返回到r中
void tournamentSort() {
    TreeNode[] tree;                    // 胜者树结点数组
    int leafSize = 1;                   // 胜者树的叶子结点数
    // 得到胜者树叶子结点(外结点)的个数,该个数必须是2的幂,例如:7个结点,需要8个叶子
    while (leafSize < this.curlen) {
        leafSize *= 2;
    }
    int TreeSize = 2 * leafSize - 1;   // 胜者树的所有节点数 P178
    int loadindex = leafSize - 1;      // 叶子结点(外结点)存放的起始位置
    tree = new TreeNode[TreeSize];
    int j = 0;          //用于处理每层中的所有结点,每次处理2个,j和j+1
    //给叶子结点填充数据
    //把待排序结点复制到胜者树的叶子结点中,如果有数据active为1,如果没有数据active为0
    for (int i = loadindex; i < TreeSize; i++) {
        tree[i] = new TreeNode();
        tree[i].index=i;
        if (j < this.curlen) {          // 复制结点
            tree[i].active=1;
            tree[i].data=r[j++];
        } else {
            tree[i].active=0;          // 空的外结点,叶子结点没有填满,相当于∞
        }
    }
    // 给非叶子结点赋值,值的来源是两个叶子节点的获胜者
    // 使用i记录每一层第一个结点的索引值
    int i = loadindex;                 // 进行初始比较查找关键码最小结点
    while (i > 0) {                    // 产生胜者树,先下层后上层,每层从头开始
        j = i;
        // 处理某一层中的所有结点,获胜者将赋值给父节点,处理数据2个2个处理
        while (j < 2 * i) {           // 处理各对比赛者,处理当前层,每次处理2个结点
            if (tree[j + 1].active == 0 || ((tree[j].data).key.compareTo((tree[j + 1].data).key)) <= 0) {
                tree[(j - 1) / 2] = tree[j];      // 左孩子(胜者)赋值给父结点
            } else {
                tree[(j - 1) / 2] = tree[j + 1];  // 右孩子(胜者)赋值给父结点
            }
            j += 2;                   // 下【一对】比赛者(2个结点)
        }
        i = (i - 1) / 2;             // 处理上层结点(获得上层第一个结点的下标)
    }
    // 依次更新数据,将不参赛的结点剔除,tree[0]存放每次比较的获胜者
    for (i = 0; i < this.curlen - 1; i++) {  // 处理剩余的n-1个记录
        r[i] = tree[0].data;                // 将胜者树的根(最小者)存入数组r
        tree[tree[0].index].active=0;       // 该元素相应外结点不再比赛
        updateTree(tree, tree[0].index);   // 调整胜者树,将最小元素移至到根元素
        display();
    }
    // 整理n-1完成后,树中存放最后一个结点,也就是最大值
    r[this.curlen - 1] = tree[0].data;
}

2.【算法】树形选择排序的调整算法

//【算法】树形选择排序的调整算法,i是当前最小关键字记录的下标
void updateTree(TreeNode[] tree, int i) {
    int j;
    // 当前记录i已经参选,将对手存放到父节点
    if (i % 2 == 0) {       // i为偶数,对手为左结点
        tree[(i - 1) / 2] = tree[i - 1];
    } else {                // i为奇数,对手为右结点
        tree[(i - 1) / 2] = tree[i + 1];
    }
    i = (i - 1) / 2;        // 最小元素输出后,其对手上升到父结点
    while (i > 0) {         // 直到i==0
        // 获得对手的索引,左孩子(i)的对手右孩子(i+1);右孩子(i)的对手左孩子(i-1)
        if (i % 2 == 0) {   // i为偶数,对手为左结点
            j = i - 1;
        } else {            // i为奇数,对手为右结点
            j = i + 1;
        }
        // 比赛对手中有一个为空
        if (tree[i].active == 0 || tree[j].active == 0) {
            if (tree[i].active == 1) {
                tree[(i - 1) / 2] = tree[i];  // i可参选,i上升到父结点
            } else {
                tree[(i - 1) / 2] = tree[j];  // 否则,j上升到父结点(j可参选或不可参选)
            }
        } else {    // 双方都可参选
            // 关键码小者上升到父结点
            if ((tree[i].data).key.compareTo((tree[j].data).key) <= 0) {
                tree[(i - 1) / 2] = tree[i];
            } else {
                tree[(i - 1) / 2] = tree[j];
            }
        }
        i = (i - 1) / 2;     // i上升到父结点
    }
}

3.测试

public class TestSeqList8_tree {
    public static void main(String[] args) throws Exception {
        int[] arr = {52,39,67,95,70,8,25,52};
        SeqList seqList = new SeqList(arr.length);
        System.out.print("序列号:");
        for (int i = 0; i < arr.length; i++) {
            System.out.print("  " + i);
            seqList.insert(i, new RecordNode(arr[i]));
        }
        System.out.println();
        // 树形选择排序(锦标赛排序)
        seqList.tournamentSort();
        seqList.display();
    }
}
//树形选择排序(锦标赛排序)

5)性能分析


空间复杂度:树形选择排序虽然减少了排序时间,但使用了较多的附加存储空间(多了2n-1个)。


时间复杂度:O(nlog2n)


树形选择排序是一种==稳定==的排序算法。


6)练习


练习1:使用树形选择排序,写出每一趟的排序结果


序列:16, 15, 50, 53, 64, 7

55e74bc2396149b7b2a8b123a9afcf54.png

相关文章
|
算法
数据结构之博弈树搜索(深度优先搜索)
本文介绍了使用深度优先搜索(DFS)算法在二叉树中执行遍历及构建链表的过程。首先定义了二叉树节点`TreeNode`和链表节点`ListNode`的结构体。通过递归函数`dfs`实现了二叉树的深度优先遍历,按预序(根、左、右)输出节点值。接着,通过`buildLinkedList`函数根据DFS遍历的顺序构建了一个单链表,展示了如何将树结构转换为线性结构。最后,讨论了此算法的优点,如实现简单和内存效率高,同时也指出了潜在的内存管理问题,并分析了算法的时间复杂度。
405 0
|
11月前
|
算法 Java
算法系列之数据结构-Huffman树
Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。
341 3
 算法系列之数据结构-Huffman树
|
11月前
|
存储 自然语言处理 数据库
【数据结构进阶】AVL树深度剖析 + 实现(附源码)
在深入探讨了AVL树的原理和实现后,我们不难发现,这种数据结构不仅优雅地解决了传统二叉搜索树可能面临的性能退化问题,还通过其独特的平衡机制,确保了在任何情况下都能提供稳定且高效的查找、插入和删除操作。
819 19
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
484 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
606 64
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
400 12
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
215 10
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
587 3
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
707 16
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
436 5