1、简单选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
常见的选择排序包括:简单选择排序,树形选择排序,堆排序。
1.1、简单选择排序算法思想
采用最简单的选择方式,从头到尾扫描待排序列,找一个最小的记录(递增排序),和第一个记录交换位置,再从剩下的记录中继续反复这个过程,直到全部有序。
简单选择排序是不稳定排序。
1.2、简单选择排序基本步骤
- 在一组对象r[i]-r[n]中选择最小的一个对象。
- 如果所选择出来的这个对象不是这组对象中的第一个对象,则将选择出来的对象与第一个对象交换。
- 交换之后,在剩下的r[i+1]-r[n]中重复以上步骤,直到剩余对象只有一个为止。
1.3、简单选择排序Java实现
/**
* @Comment 选择排序算法
* @Author Ron
* @Date 2017年11月3日 上午11:06:35
* @return
*/
public class SelectSort {
/**
* @Comment 选择排序算法实现
* @Author Ron
* @Date 2017年11月3日 上午11:07:27
* @return
*/
static void sort(int[] source){
int n = source.length-1;
int temp;
for(int i=0; i<n; i++){
int index = i;
for(int j=i+1; j<=n; j++){
if(source[j] < source[index]){
index=j;
}
}
if(index != i){
//说明在r[i]-r[n]中找到比r[i]小的对象,将选择出来的对象与第一个对象交换
temp = source[i];
source[i]=source[index];
source[index]=temp;
}
}
}
public static void main(String[] args) {
int[] sources={3,9,9,4,4,1,7,6,2,0,8};
sort(sources);
String result="";
for(int i = 0 ; i < sources.length ; i++){
result += sources[i]+" ";
}
System.out.println(result);
}
}
1.4、算法分析
空间复杂度:O(1)
时间复杂度:O(n²)
外循环需执行n-1次,内循环需执行n-1-i次,因此总比较次数是∑(n-1-i)=1/2n(n-1)。最坏情况(逆序)下的移动次数是3(n-1),最好情况(有序)下的移动次数是0。算法稳定性:不稳定
2、树形选择排序
在简单选择排序中,每次的比较都没有用到上次比较的结果,所以比较操作的时间复杂度是O(N^2),想要降低比较的次数,则需要把比较过程中的大小关系保存下来。树形选择排序是对简单选择排序的改进。
2.1、树形选择排序算法思想
树形选择排序也叫锦标赛排序,是一种按照锦标赛的思想进行选择的排序方法,该方法是在简单选择排序方法上的改进。
首先对 n 个记录的关键字进行两两比较,然后在其中不大于n/2的整数个较小者之间再进行两两比较,直到选出最小关键字的记录为止。可以用一棵有 n 个叶子结点的完全二叉树表示。
2.2、树形选择排序基本步骤
- 变量初始化,令待排序结点个数为n,则叶子结点个数为n,树的结点总数为2*n-1,叶子结点在顺序表中的起始存放位置n-1。
- 将a[0……n-1]依次赋值到tree[loadindex……TreeSize-1]。
- 构造树,n个结点两两比较,得到n/2个关键字值小的结点;再将n/2个结点比较得到n/4,直到得到最小的关键字。
- 调整树,先将根结点保存到原数组a中,再把对应叶子结点值改为“极大值”,然后从该叶子结点开始修改到根上各结点的值。
- 重复上面步骤。
2.3、树形选择排序算法Java实现
/**
* @Comment 树形选择排序
* @Author Ron
* @Date 2017年11月3日 上午11:44:25
* @return
*/
public class TreeSelectionSort {
/**
* @Comment 树形选择排序
* @Author Ron
* @Date 2017年11月3日 下午12:18:51
* @return
*/
static void sort(int[] source) {
TreeNode[] tree; // 树结点数组
int leafSize = 1; // 树叶子结点数
// 得到叶子结点的个数,该个数必须是2的次幂
while (leafSize < source.length) {
leafSize *= 2;
}
int TreeSize = 2 * leafSize - 1;// 树的所有节点数
int loadIndex = leafSize - 1;// 叶子节点存放位置的起始位置
tree = new TreeNode[TreeSize];
// 把待排序结点复制到树的叶子结点中
int j = 0;
for (int i = loadIndex; i < TreeSize; i++) {
tree[i] = new TreeNode();
tree[i].setIndex(i);
if (j < source.length) {
tree[i].setActive(1);
tree[i].setData(source[j++]);
} else {
tree[i].setActive(0);
}
}
int i = loadIndex;// 进行初始化,比较查找关键字值最小的结点
while (i > 0) {
j = i;
// 处理并设置每一对子节点的父节点(即比较每对子节点,将最小的一个节点设置为这对子节点的父节点)
while (j < TreeSize - 1) {
if (tree[j + 1].getActive() == 0 || (tree[j].getData() <= tree[j + 1].getData())) {
// 左节点比右节点小或者右节不参选,那么左节点为参与比较的两个节点的父节点
tree[(j - 1) / 2] = tree[j];
} else {
// 右节点为参与比较的两个节点的父节点
tree[(j - 1) / 2] = tree[j + 1];
}
j += 2;
}
i = (i - 1) / 2;// 处理上层节点
}
// 处理剩余的n-1个元素
for (i = 0; i < source.length - 1; i++) {
source[i] = tree[0].getData(); // 将树的根(最小值)存入数组
tree[tree[0].getIndex()].setActive(0); // 冠军不再参加比赛
updateTree(tree, tree[0].getIndex()); // 调整树
}
//最后一个元素只需赋值就结束了 不需要再调整
source[source.length - 1] = tree[0].getData();
}
/**
* @Comment 树形选择排序的调整算法
* 从当前最小关键字的叶子结点开始到根结点路径上的所有结点关键字的修改
* @Author Ron
* @Date 2017年11月3日 下午2:18:14
* @return
*/
static void updateTree(TreeNode[] tree, int i) {
// 因为i是此时最小的关键字(已是冠军),所以在叶子结点中要将其除去比赛资格,对手直接晋级(升为父结点)
if (i % 2 == 0) {
// i为偶数,自己是右结点,对手是左结点,左结点晋级
tree[(i - 1) / 2] = tree[i - 1];
} else {
tree[(i - 1) / 2] = tree[i + 1];
}
i = (i - 1) / 2;
int j = 0;
while (i > 0) {
if (i % 2 == 0) {
// i为偶数,自己是右结点,对手是左结点
j = i - 1;
} else {
j = i + 1;
}
//比赛对手中有一个为空
if (tree[i].getActive() == 0 || tree[j].getActive() == 0) {
if (tree[i].getActive() == 1) {
tree[(i - 1) / 2] = tree[i];
} else {
tree[(i - 1) / 2] = tree[j];
}
}else{
// 比赛对手都在
if (tree[i].getData() < tree[j].getData()) {
tree[(i - 1) / 2] = tree[i];
} else {
tree[(i - 1) / 2] = tree[j];
}
}
i = (i - 1) / 2;
}
}
public static void main(String[] args) {
int[] sources={3,9,9,4,4,1,7,6,2,0,8};
sort(sources);
String result="";
for(int i = 0 ; i < sources.length ; i++){
result += sources[i]+" ";
}
System.out.println(result);
}
/**
* @Comment 树形选择排序的树结点结构
* @Author Ron
* @Date 2017年11月3日 下午12:21:21
* @return
*/
private static class TreeNode {
private int data; // 数据域
private int index; // 待插入结点在满二叉树中的序号
private int active; // 参加选择标志,1表示参选,0表示不参选
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public int getActive() {
return active;
}
public void setActive(int active) {
this.active = active;
}
}
}
2.4、算法分析
- 空间复杂度:树形选择排序算法的叶子结点个数是2的k次幂,所需存储空间是2*2^k-1
- 时间复杂度:O(n㏒₂n)
树形选择排序算法高度为k+1。第一趟需进行n-1次关键字比较,其余趟关键字比较次数为O(㏒₂ n),总的关键字比较次数为O(n㏒₂n)。由于结点移动次数不会超过比较的次数,所以时间复杂度是O(n㏒₂n) - 算法稳定性:稳定
3、堆排序
在树形选择排序算法中,叶子结点的个数必须是2的次幂,如果要排序的一组对象个数不满足2的次幂数,就会导致很多多余的比较(如上示例中和active=0的节点进行比较)和使用很多的辅助空间(如上示例中和active=0的节点所占用的空间)。
为了弥补这些缺点,1964年,堆排序诞生。
3.1、堆的定义
n 个元素的序列 (k1, k2, …, kn),当且仅当满足下列关系:
k[i] >= k[i*2] && k[i] >= k[i*2+1] 或者 k[i] <= k[i*2] && k [i] <= k[i*2+1],i = (1, 2, 3, 4, . . . , n/2)
时称序列K为一个堆积(heap),简称堆。有时将满足第一种条件的堆积称为大顶堆积,满足第二种条件的堆积称为小顶堆积,大顶堆积的第一个元素具有最大值。
可将堆序列看成完全二叉树,则: k[i*2] 是 k[i] 的左孩子;k[ i*2+1] 是 k[i] 的右孩子。所有非终端结点的值均不大(小)于其左右孩子结点的值。堆顶元素必为序列中 n 个元素的最小值或最大值。
3.2、堆排序算法思想
由于堆所对应序列的第一个元素具有最大值(或者堆对应的完全二叉树的根结点具有最大值),于是,堆排序的核心思想可以描述为:
- 首先设法将原始序列构造成第一个堆,这个堆称为初始堆,使得n个元素的最大值(或最小值)处于序列的第一个位置。
- 然后将序列第一个元素(最大值)与序列最后一个元素交换位置。
- 此后设法将序列的前 n - 1个元素组成的子序列设法构成一个新的堆,这样又得到第2个最大值(最小值),将这个最大值(最小值)与序列的第 n - 1 个元素交换位置;
- 然后再将前 n - 2 个元素组成的序列设法构成一个新的堆… … ,如此重复,最终把整个序列变换成一个按值有序的序列。
简单的说,堆积排序的第 i 趟排序就是将序列的前 n - i + 1个元素组成的子序列转换成一个堆积,然后将堆积的第一个元素与堆积的最后那个元素交换位置。
3.3、堆排序算法步骤
- 将待排序元素建成一棵完全二叉树。
- 将下标为[n/2]-1的元素作为开始调整的子树的根结点。
- 找出此结点的两个孩子中的关键字值较小者,与此结点比较;若此结点大,则交换,然后交换后的子结点作为新的父结点,父结点就成了子结点重复此步骤直到没有子结点为止。
- 以上步骤的原父结点的位置往前推进一个位置,作为新的调整的子树的根结点,继续重复上步骤。
3.4、堆排序算法Java实现
/**
* @Comment 堆排序算法实现
* @Author Ron
* @Date 2017年11月3日 下午4:01:20
* @return
*/
public class HeapSort {
/**
* @Comment 堆排序
* @Author Ron
* @Date 2017年11月3日 下午4:01:58
* @return
*/
static void sort(int[] source){
int n=source.length;
int temp;
//创建初始堆
System.out.println("创建初始堆");
printSrc(source);
for(int i = n / 2 - 1; i >= 0; i--){
initHeap(i, n, source);
}
//创建初始堆
System.out.println("创建初始堆完毕");
for(int i = n - 1; i > 0; i--){
//每趟将最小关键字值交换到后面,再调整成堆
temp = source[0];
source[0] = source[i];
source[i] = temp;
initHeap(0, i, source);
}
}
/**
* @Comment 筛选法调整堆算法
* @param low 以low为根结点的子树调整成小顶堆
* @param high 需要比较的范围,即剩余的无序数组的最大索引
* @param
* @Author Ron
* @Date 2017年11月3日 下午4:03:58
* @return
*/
static void initHeap(int low, int high,int[] source){
int i = low; //子树的根结点
int l = 2 * i + 1;//子树左节点
int temp = source[i];
while (l < high) {
//判断条件l < high - 1 表示有右结点,即l+1 < high
if(l < high-1 && source[l] > source[l + 1]){
l++;
}
if(temp > source[l]){
source[i] = source[l]; //孩子结点中的较小值上移
i = l;
l = 2 * i + 1;
}else {
l = high+1;
}
}
source[i] = temp;
printSrc(source);
}
static void printSrc(int[] sources) {
String result="";
for(int i = 0 ; i < sources.length ; i++){
result += sources[i]+" ";
}
System.out.println(result);
}
public static void main(String[] args) {
int[] sources={3,9,4,1,7,6,2,0,8,5};
sort(sources);
}
}
3.5、算法分析
算法不稳定,适合元素个数较多时,时间复杂度O(n*logn),空间复杂度O(1)。
参考:http://blog.csdn.net/liuquan0071/article/details/50462145