选择排序算法
选择类排序的基本思想是:每一趟从n-i+1(i=1,2,…,n)个元素中选取一个关键字最小的元素作为有序序列中的第i个元素。
1、简单选择排序
- 第一趟,从n个元素中找出关键字最小的元素与第一个元素交换;
- 第二趟,从第二个元素开始的n-1个元素中再选出关键字最小的元素与第二个元素交换
- 如此第k趟,则从第k个元素开始的n-k+1个元素中选出关键字最小的元素与第k个元素交换;直到整个序列按关键字有序,如下图所示。
代码实现:
import java.util.Arrays;
public class SelectSort {
public static void SimpleSort(int[] array) {
int min = 0,k=0;
//设置排序趟数
for(int i=0;i<array.length-1;i++) {
min = array[i];
k = i;
//通过比较选择关键字最小的元素
for(int j=i;j<array.length;j++) {
if(array[j]<min) {
min = array[j];
k = j;
}
}
//与第i个元素交换
array[k] = array[i];
array[i] = min;
System.out.println(Integer.toString(i+1)+":"+Arrays.toString(array));
}
}
public static void main(String[] args) {
int[] array = {26,53,48,11,13,48,32,15};
SimpleSort(array);
}
}
运行结果:
简单选择排序移动次数较少,但是比较操作较多,其时间复杂度为O(n^2)。
2、树型选择排序
简单选择排序的主要操作是元素间的比较操作,因此应从减少元素比较次数出发来改进。
由于简单选择排序每一趟比较都未利用之前比较操作的结果,因此树型选择排序通过把以前比较的结果记录下来来降低比较操作的次数
- 首先把待排序的n个元素两两进行比较,取出较小者;
- 然后在n/2个较小者中,采用同样的方法进行比较,再选出较小者;
- 反复进行这样的操作,直到选出关键字最小的元素为止,这个过程可以使用一个具有n个结点的完全二叉树,最终选出的关键字最小的元素就是这棵二叉树的根节点。
- 具体做法,如图所示:
- 关键字序列为:{26,53,48,11,13,48,32,15}
- 重复进行两两比较后,获得n个元素中的关键字最小元素,即为根节点11;
- 为了选出次小的关键字元素,可以将上一趟获得的最小关键字所对应的叶子结点设为正无穷;
- 重复上述过程,直到获得按关键字有序的序列。
代码实现:
import java.util.Stack;
import java.util.Queue;
import java.util.LinkedList;
class TreeNode{
double val=0;
boolean from=false;
TreeNode left = null;
TreeNode right = null;
public TreeNode() {
}
public TreeNode(double val) {
this.val = val;
}
//根据待排序关键字构造叶子节点,二叉树最后一层
public static TreeNode[] bulidList(double[] array) {
TreeNode[] leaves = new TreeNode[array.length];
for(int i=0;i<array.length;i++) {
leaves[i] = new TreeNode();
}
for(int i=0;i<array.length;i++) {
leaves[i].val = array[i];
}
return leaves;
}
//当序列大小奇数时,添加结点构成二叉树
public static TreeNode[] addMaxNode(TreeNode[] leaves,int n) {
TreeNode maxNode = new TreeNode(Double.POSITIVE_INFINITY);
TreeNode[] res = new TreeNode[n+1];
for(int i=0;i<n+1;i++) {
res[i] = new TreeNode();
}
for(int i=0;i<n;i++) {
res[i].val = leaves[i].val;
}
res[n] = maxNode;
return res;
}
//根据左右子树大小,创建树
public static TreeNode buildTree(TreeNode[] leaves,int n) {
if(n==1) {
return leaves[0]; //最终返回根节点
}
if(n%2==1) {
leaves = addMaxNode(leaves,n);
}
//二叉树每一层结点个数
int num = n/2+n%2;
TreeNode[] res = new TreeNode[num];
for(int i=0;i<num;i++) {
res[i] = new TreeNode();
}
for(int i=0;i<num;i++) {
res[i].left = leaves[2*i];
res[i].right = leaves[2*i+1];
boolean less = res[i].left.val <= res[i].right.val;
res[i].from = less;
res[i].val = less?res[i].left.val:res[i].right.val;
}
//迭代,一层层构造二叉树
return buildTree(res,num);
}
public static double rebulidTree(TreeNode tree) {
double result=tree.val;
TreeNode node = tree;
Stack<TreeNode> st = new Stack<TreeNode>();
st.add(node);
//将最小结点比较的路径上的结点入栈
while(node.left != null) {
node = node.from?node.left:node.right;
st.push(node);
}
//找到叶子结点中的最小元素结点,设置为无穷大
node.val = Double.POSITIVE_INFINITY;
st.pop();
//更新树
while(!st.isEmpty()) {
node = st.peek();
st.pop();
boolean less = node.left.val<=node.right.val;
node.from = less;
node.val = less?node.left.val:node.right.val;
}
return result; //返回最小元素
}
public static void printTree(TreeNode tree) {
System.out.println();
TreeNode node = tree;
//q1存放当前层的结点,q2存放下一层的结点
Queue<TreeNode> q1 = new LinkedList<TreeNode>();
Queue<TreeNode> q2 = new LinkedList<TreeNode>();
q1.add(tree);
while(!q1.isEmpty()) {
node = q1.peek();
if(node.left != null && node.right != null) {
q2.add(node.left);
q2.add(node.right);
}
q1.remove();
if(node.val == Double.POSITIVE_INFINITY) {
System.out.print("MAX ");
}else {
System.out.print(node.val+" ");
}
if(q1.isEmpty()) {
q1 = q2;
Queue<TreeNode> empty = new LinkedList<TreeNode>();
q2 = empty;
}
}
}
}
public class TreeSlectSort {
public static void main(String[] args) {
double[] array = {26,53,48,11,13,48,32,15};
//使用栈存放排序结果
Stack<Double> st = new Stack<Double>();
//根据待排关键字构造结点
//同时构造两两比较的二叉树
TreeNode tree = TreeNode.buildTree(TreeNode.bulidList(array), array.length);
//打印整棵树,按层打印
TreeNode.printTree(tree);
for(int i=0;i<array.length;i++) {
//一轮轮比较,将每一趟比较得到的最小元素入栈
double val = TreeNode.rebulidTree(tree);
TreeNode.printTree(tree);
st.add(val);
}
System.out.println();
for(int i=0;i<array.length;i++) {
System.out.print(st.pop()+ " ");
}
}
}
运行结果:
树型选择排序的时间复杂度为O(n^2)。
虽然树型选择排序时间复杂度减小了,但是其使用了较多的辅助空间,并且和无穷大进行多余比较。
1964年提出的堆排序进一步改进。
3、堆排序
- 堆的定义为:n个元素的序列{k1,k2,k3,…,kn},当且仅当满足ki<=k2i且ki<=k(2i+1),或满足ki>=k2i且ki>=k(2i+1)时,称之为堆。
- 将这个序列看成是一颗完全二叉树,则堆的定义说明,完全二叉树中非终端结点的值均不大于或不小于其左右孩子结点的值,如图所示,其中(a)是一个大顶堆,(b)是一个小顶堆。
- 设有n个元素的待排序列:
- 首先将这n个元素按关键字建成堆,将堆顶元素输出,得到n个元素中关键字最大的元素;
- 接着将剩下的n-1个元素重新建成堆,再输出堆顶元素;
- 反复执行,直到最后只剩下一个元素,则可以得到一个有序序列,整个过程称之为堆排序。
- 可以看到,整个排序过程需要解决两个问题:
- (1)如何将这n个元素的序列按关键字建成堆;
- (2)输出堆顶元素后,怎样调整剩下的n-1个元素,使其成为一个新堆。
代码实现:
import java.util.Arrays;
public class HeapSort {
public static void heapAdjust(int[] array,int low,int high) {
int temp = array[low];
for(int i=2*low;i<=high;i=i*2) {
if(i<high && array[i]<array[i+1]) {
i++;
}
if(temp>=array[i]) break;
array[low] = array[i];
low = i; //向下筛选
}
array[low] = temp;
}
public static void heapSort(int[] array) {
int n = array.length-1;
for(int i=n/2;i>=1;i--) {
heapAdjust(array,i,n);
}
for(int i=n;i>1;i--) {
int temp = array[1];
array[1] = array[i];
array[i] = temp;
heapAdjust(array,1,i-1);
}
}
public static void main(String[] args) {
int[] array = {0,28,26,17,36,20,42,11,53};
heapSort(array);
System.out.println(Arrays.toString(array));
}
}
堆排序只需要一个辅助空间,并且其在任何情况下的时间复杂度为O(nlogn)。