排序——选择排序

前面的文章介绍了排序的概念,插入排序和交换排序,大家可以通过下面的链接再去学习:

排序的概念及插入排序           

交换排序

这篇文章则介绍一下选择排序的相关内容。

一,简单选择排序

基本思想:

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

用代码实现排序过程:

void SelectSort(SqList &K)
 { 
    for (i=1; i<L.length; ++i)
    { //在L.r[i..L.length] 中选择key最小的记录
        k=i;     
        for( j=i+1;j<=L.length ; j++)
            if ( L.r[j].key <L.r[k].key) k=j; 
        if(k!=i)L.r[i]←→L.r[k];            
    }  
}

 算法分析:

 移动次数


 二,树形选择排序

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

例:

选出次最小者,应利用上次比较的结果。从被 13 打败的结点 277638 中加以确定。

算法步骤

        将所有记录的关键字作为叶子节点构建成完全二叉树;非叶子节点的关键字设置为其孩子节点中较小的那个;经过层层比较,最终在根节点得到最小关键字;之后修改叶子节点中的最小值为正无穷,重复上述过程找出次小值,直至完成排序。

性能分析: 

  • 时间复杂度:树形选择排序的时间复杂度为O(NlogN),因为除了第一次选择最小关键字需要进行n-1次比较外,每选择一个次小关键字仅需进行log2n次比较。
  • 空间复杂度:由于需要构建存储元素的二叉树,因此空间复杂度为O(N)。
  • 稳定性:树形选择排序的稳定性依赖于具体实现方式,在某些情况下可能不稳定。
  • 优势:相比简单选择排序,树形选择排序减少了不必要的比较次数,提高了排序效率。
  • 劣势:树形选择排序需要额外的存储空间来构建二叉树,并且与“最大值”的比较多余

以下是一段完整的c语言树形选择排序代码: 

#include <stdio.h>
#include <stdlib.h>

// 定义二叉树节点结构体
typedef struct Node {
    int data;
    struct Node *left, *right;
} Node;

// 创建新节点
Node* newNode(int data) {
    Node* node = (Node*)malloc(sizeof(Node));
    node->data = data;
    node->left = NULL;
    node->right = NULL;
    return node;
}

// 插入节点到二叉树中
Node* insert(Node* root, int data) {
    if (root == NULL) {
        return newNode(data);
    }
    if (data < root->data) {
        root->left = insert(root->left, data);
    } else if (data > root->data) {
        root->right = insert(root->right, data);
    }
    return root;
}

// 查找最小值节点
Node* findMin(Node* root) {
    while (root->left != NULL) {
        root = root->left;
    }
    return root;
}

// 删除节点
Node* deleteNode(Node* root, int data) {
    if (root == NULL) {
        return root;
    }
    if (data < root->data) {
        root->left = deleteNode(root->left, data);
    } else if (data > root->data) {
        root->right = deleteNode(root->right, data);
    } else {
        if (root->left == NULL) {
            Node* temp = root->right;
            free(root);
            return temp;
        } else if (root->right == NULL) {
            Node* temp = root->left;
            free(root);
            return temp;
        }
        Node* temp = findMin(root->right);
        root->data = temp->data;
        root->right = deleteNode(root->right, temp->data);
    }
    return root;
}

// 打印二叉树(中序遍历)
void inorder(Node* root) {
    if (root != NULL) {
        inorder(root->left);
        printf("%d ", root->data);
        inorder(root->right);
    }
}

// 树形选择排序函数
void treeSelectionSort(int arr[], int n) {
    Node* root = NULL;
    for (int i = 0; i < n; i++) {
        root = insert(root, arr[i]);
    }
    for (int i = 0; i < n; i++) {
        Node* minNode = findMin(root);
        arr[i] = minNode->data;
        root = deleteNode(root, minNode->data);
    }
}

int main() {
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);
    treeSelectionSort(arr, n);
    printf("Sorted array: \n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

三,堆排序 

 什么是堆?

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

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

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

 堆又分为大根堆和小根堆:

 下面的两个树就不满足堆的性质

例:判定(80,75,40,62,73,35,28,50,38,25,47,15)是否为堆

 如何将无序序列建成堆?

不超过 n/2的最大正整数,得到序号为3,以3号位置为根结点的树不满足大根堆的性质

这里我们将8和12换位置,12>8,12>10,经过调整这棵子树满足大根堆的性质

 同样调整左边的子树,交换60和70

最后将整个子树的根节点与其左右孩子比较,在满足大根堆性质的前提下交换

 至此就完成了将无序序列建成堆的过程。

堆的调整

如何在输出堆顶元素后调整,使之成为新堆?

        输出堆顶元素后,以堆中最后一个元素替代之

        将根结点与左、右子树根结点比较,并与大者交换

        重复 直至叶子结点,得到新的堆

这里就不再给出图示过程,与上面堆的构建类似,也是不断递归比较,调整的过程。

下面是完整的c语言堆排序的代码:

#include <stdio.h>
#include <stdlib.h>

// 交换两个整数的值
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 调整堆,使其满足最大堆的性质
void heapify(int arr[], int n, int i) {
    int largest = i; // 初始化最大值为当前节点
    int left = 2 * i + 1; // 左子节点的索引
    int right = 2 * i + 2; // 右子节点的索引

    // 如果左子节点存在且大于当前最大值,更新最大值索引
    if (left < n && arr[left] > arr[largest])
        largest = left;

    // 如果右子节点存在且大于当前最大值,更新最大值索引
    if (right < n && arr[right] > arr[largest])
        largest = right;

    // 如果最大值索引不是当前节点,交换并继续调整堆
    if (largest != i) {
        swap(&arr[i], &arr[largest]);
        heapify(arr, n, largest);
    }
}

// 堆排序算法
void heapSort(int arr[], int n) {
    // 构建最大堆
    for (int i = n / 2 - 1; i >= 0; i--)
        heapify(arr, n, i);

    // 逐个提取最大元素并调整堆
    for (int i = n - 1; i >= 0; i--) {
        swap(&arr[0], &arr[i]); // 将当前最大元素放到数组末尾
        heapify(arr, i, 0); // 调整剩余部分为最大堆
    }
}

int main() {
    int arr[] = {12, 11, 13, 5, 6, 7};
    int n = sizeof(arr) / sizeof(arr[0]);

    heapSort(arr, n); // 对数组进行堆排序

    printf("Sorted array is: ");
    for (int i = 0; i < n; ++i)
        printf("%d ", arr[i]); // 输出排序后的数组
    printf(" ");

    return 0;
}

堆排序的算法分析:

时间效率:O(nlog2n)

空间效率:O1

稳 定 性:不稳定

适用于n 较大的情况。


到此选择排序就结束了, 如果文章对你有用的话请点个赞支持一下吧!

下篇文章将更新归并排序的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值