本节来聊聊选择排序,它的基本思想是:每一趟从排序的记录中选出关键字最小的记录,按顺序放在已排序的记录序列的最后,知道全部排完为止。本节首先将一种简单选择排序,然后给出另一种改进的选择排序方法--堆排序。
一、简单选择排序
这应该每个人接触的最早的排序算法,也是最简单的排序算法(我个人认为(`・ω・´))。
这个不多讲,直接上代码!٩(๑❛ᴗ❛๑)۶
#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!