数据结构--排序之选择排序

本节来聊聊选择排序,它的基本思想是:每一趟从排序的记录中选出关键字最小的记录,按顺序放在已排序的记录序列的最后,知道全部排完为止。本节首先将一种简单选择排序,然后给出另一种改进的选择排序方法--堆排序。

一、简单选择排序

这应该每个人接触的最早的排序算法,也是最简单的排序算法(我个人认为(`・ω・´))。

这个不多讲,直接上代码!٩(๑❛ᴗ❛๑)۶

#include <bits/stdc++.h>
using namespace std;

#define MAXSIZE 100
typedef int KeyType;
typedef struct///定义每个节点信息
{
    KeyType key;
//    InfoType otherinfo;
}RType;
typedef struct///定义顺序表
{
    RType r[MAXSIZE + 1];
    int length;
}SqList;
void SelectSort(SqList &L)
{
    for(int i = 1; i <= L.length - 1; i ++)
    {
        int k = i;
        for(int j = i + 1; j <= L.length; j ++)
        {
            if(L.r[j].key < L.r[k].key)k = j;
        }
        if(k != i)
        {
            L.r[0] = L.r[i];
            L.r[i] = L.r[k];
            L.r[k] = L.r[0];
        }
    }
}
int main()
{
    SqList L;
    printf("请输入序列的个数:");
    scanf("%d", &L.length);
    printf("\n\n请输入序列:");
    for(int i = 1; i <= L.length; i ++)
    {
        scanf("%d", &L.r[i].key);
    }
    SelectSort(L);
    for(int i = 1; i <= L.length; i ++)
    {
        printf("%5d", L.r[i].key);
    }
    printf("\n");
    return 0;
}


/**
10
     70   30   52   29   50   62   49   86    2   13
**/

二、堆排序

堆排序主要用到二叉堆,在讲算法之前,先介绍一下什么是二叉堆,本质上就是一个具有特殊性质的完全二叉树。

若树中任意一个节点的权值都小于等于其父节点的权值,则称该二叉树满足“大根堆性质”,称之为大根堆

若树中任意一个节点的权值都大于等于其父节点的权值,则称该二叉树满足“小根堆性质”,称之为小根堆

在此以大根堆为例介绍一下堆的基本操作:(定义数组int heap[maxn],表示堆 ;int n,表示堆中元素的个数)

a、向上更新调整:

即从当前位置往上调整,使从当前位置往上满足堆的性质;

void Up(int p)
{
    while(p > 1)
    {
        if(heap[p] > heap[p/2])///如果子节点的值大于父节点的值,则需要调换子、父节点
        {
            swap(heap[p], heap[p/2]);
            p /= 2;///p减半,继续往上跟新
        }
        else break;///否则可以直接跳出循环,不必细节更新
    }
}

b、向下更新操作:

即从当前元素往下调整,是从当前节点往下满足堆的性质;

void Down(int p)
{
    int s = p * 2;
    while(s <= n)
    {
        if(s < n && heap[s] < heap[s+1]) s ++;///取子节点的较大一个
        if(heap[s] > heap[p])///若子节点大于父节点则交换
        {
            swap(heap[s], heap[p]);
            p = s; s = 2 * p;
        }///否则跳出循环
        else break;
    }
}

c、插入操作:

在最后一个元素的后面加入插入的元素,然后进行向上调整;

void Insert(int val)
{
    heap[++n] = val;
    Up(n);
}

d、删除操作:

将最后一个元素赋值成将要删除的元素,然后向上和向下调整即可;

void Remove(int k)
{
    heap[k] = heap[n--];
    Up(k);Down(k);
}

堆的重要操作大致如上。可能对接下来的堆排序没什么用O(∩_∩)O哈哈~!

现在我们来思考如何利用堆对一个序列进行排序?

给定一个序列 r[1...n]

首先你得让它满足堆的性质,所以要进行一个堆初始化、初始化结束后就可以进行排序。

如何初始化?

对数组从后往前遍历,每次调整区间r[i...n](i = n - 1, n- 2,...,1)(实际上从n/2开始就行)满足堆的性质(调整区间是一个重点,稍后再看)

如何排序?

对于区间r[1...n]满足堆的性质,所以r[1]不就是区间内最大的值,将r[1] 和人r[n]交换,那么r[n]岂不就是该区间的最大值;向上调整区间r[1...n-1],使其满足堆的性质,将r[1]r[n-1]交换,r[n-1]不就是区间r[1...n-1]的最大值、区间r[1...n-1]的次大值;以此类推不断循环操作,最终序列不就有序了吗!。◕ᴗ◕。。◕ᴗ◕。

我们在代码中给出调整区间的操作及其解析

上代码:

#include <bits/stdc++.h>
using namespace std;

#define MAXSIZE 100
typedef int KeyType;
typedef struct///定义每个节点信息
{
    KeyType key;
//    InfoType otherinfo;
}RType;
typedef struct///定义顺序表
{
    RType r[MAXSIZE + 1];
    int length;
}SqList;
///调成区间[s...m],使其满足堆的性质
void HeapAdjust(SqList &L, int s, int m)
{
    RType tmp = L.r[s]; ///临时变量存储区间起点r[s]
    for(int j = 2 * s; j <= m; j *= 2)
    {
         ///遍历s的子节点
        if(j < m && L.r[j].key < L.r[j+1].key) j ++;///去子节点中关键字较大的
        ///将较大的子节点和tmp比较,若小于则已经满足堆的性质,可直接跳出循环
        if(tmp.key >= L.r[j].key)break;        
        ///否则将子节点移到其父节点的位置,接着向下调整
        L.r[s] = L.r[j]; s = j;
    }
    L.r[s] = tmp;///找到tmp最终合适的位置,存放
}
///初始化
void CreatHeap(SqList &L)
{
    int n = L.length;
    for(int i = n / 2; i >= 1; i --)
    {
        HeapAdjust(L, i, n);
    }
}
void HeapSort(SqList &L)
{
    CreatHeap(L);
    for(int i = L.length; i >= 2; i--)
    {
        swap(L.r[1], L.r[i]);
        HeapAdjust(L, 1, i-1);
    }
}
int main()
{
    SqList L;
    printf("请输入序列的个数:");
    scanf("%d", &L.length);
    printf("\n\n请输入序列:");
    for(int i = 1; i <= L.length; i ++)
    {
        scanf("%d", &L.r[i].key);
    }
    HeapSort(L);
    for(int i = 1; i <= L.length; i ++)
    {
        printf("%5d", L.r[i].key);
    }
    printf("\n");
    return 0;
}
/**
10
    70   30   52   29   25   62   49   86    2   13
**/

分析一下:

堆排序不太稳定(我也不知道为什么 o(╥﹏╥)o),时间复杂度为O(n(log n)), 空间复杂度为O(1),效率还是很高的。

OVER!

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值