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

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

一、什么是选择排序?


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

相关文章
|
1月前
|
算法
数据结构之博弈树搜索(深度优先搜索)
本文介绍了使用深度优先搜索(DFS)算法在二叉树中执行遍历及构建链表的过程。首先定义了二叉树节点`TreeNode`和链表节点`ListNode`的结构体。通过递归函数`dfs`实现了二叉树的深度优先遍历,按预序(根、左、右)输出节点值。接着,通过`buildLinkedList`函数根据DFS遍历的顺序构建了一个单链表,展示了如何将树结构转换为线性结构。最后,讨论了此算法的优点,如实现简单和内存效率高,同时也指出了潜在的内存管理问题,并分析了算法的时间复杂度。
53 0
|
2月前
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
124 64
|
27天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
53 5
|
1月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
89 16
|
1月前
|
算法
数据结构之文件系统模拟(树数据结构)
本文介绍了文件系统模拟及其核心概念,包括树状数据结构、节点结构、文件系统类和相关操作。通过构建虚拟环境,模拟文件的创建、删除、移动、搜索等操作,展示了文件系统的基本功能和性能。代码示例演示了这些操作的具体实现,包括文件和目录的创建、移动和删除。文章还讨论了该算法的优势和局限性,如灵活性高但节点移除效率低等问题。
54 0
|
2月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
32 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
2月前
|
算法 搜索推荐
数据结构与算法学习十一:冒泡排序、选择排序、插入排序
本文介绍了冒泡排序、选择排序和插入排序三种基础排序算法的原理、实现代码和测试结果。
25 0
数据结构与算法学习十一:冒泡排序、选择排序、插入排序
|
2月前
|
存储 编译器 C++
【初阶数据结构】掌握二叉树遍历技巧与信息求解:深入解析四种遍历方法及树的结构与统计分析
【初阶数据结构】掌握二叉树遍历技巧与信息求解:深入解析四种遍历方法及树的结构与统计分析
|
2月前
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解(三)
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解
|
2月前
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解(二)
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解

热门文章

最新文章