选择排序
选择排序算法有两种:直接选择排序和堆排序。
直接选择排序
直接选择排序(Straight Select Sort)算法思想:第一趟从n个元素的数据序列中选出关键字最小/大的元素并放到最前/后位置,下一趟再从n-1个元素中选出最小/大的元素并放到此前/后位置,以此类推,经过n-1趟完成排序。(就是一遍一遍的选择出最小/大的,所以)
直接选择排序算法分析:
-
直接选择排序的比较次数与数据序列数与初始排列有关,第i趟排序的比较次数是n-1;移动次数与初始排列有关,排序序列移动0次;反序排列的数据序列,每趟排序都要交换,移动3(n- 1)次。算法总比较次数C = n*(n-1)/2,约等于(n2)/2,**时间复杂度为O(n2)**。
-
直接选择排序的空间复杂度为O(1)。
-
直接选择排序算法是不稳定的。
一个案例:关键字序列{38,97,26,19,38,15}的直接选择排序(升序)过程如下图,其中,i表示子序列的起始位置,min表示最小元素位置,一趟扫描后将min位置元素交换到i位置,{}表示排序子序列
直接选择排序算法实现如下:
//直接选择排序(升序)
public static void selectSort(int[] keys) {
System.out.println("直接选择排序(升序)");
//外层循环控制选择第几个
for (int i=0; i<keys.length-1; i++){ //n-1趟排序
int min=i;
//每趟在从keys[i]开始的子序列中寻找最小元素
for (int j=i+1; j<keys.length; j++)
//if (keys[j]>keys[min]) //(降序)
//(升序)
if (keys[j]<keys[min]) {
//min记住本趟最小元素下标
min = j;
}
System.out.print("第"+(i+1)+"趟,下标"+i+"~"+(keys.length-1)+",min="+min+",");
//将本趟最小元素交换到前边
if (min!=i){
swap(keys, i, min);
}
}
}
//交换
static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
堆排序
堆的概念
堆是一种数据结构,它又可以叫二叉堆,因为它是一个完全二叉树(生成结点的顺序是从上到下,从左往右,依次生成)。
二叉堆满足二个特性:
- 父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
- 每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
最大堆和最小堆:
- 当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。
- 当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。
- 最大/最小堆用于求最大/小值,堆序列用于多次求极值的应用问题。
堆的存储
一般都用数组来表示堆,i结点的父结点下标为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。
以上就是二叉树性质5:完全二叉树中的第i个(0<=i<=n)个结点,如果有孩子,则左孩子为第2i+1个结点,右孩子为第2i+2个结点。
堆排序
堆排序算法描述:以升序为例,
- 将一个数据序列建立为一个最大堆,此时根节点就是整个数组中最大的数,
- 进行选择排序,每趟将这个最大的数与数组最后一个数交换位置(这一点就是找到最大的数放在最后,和直接选择排序的思路一样,只不过这里是用二叉堆来找最大数),再将其余值调整成最大堆,将根节点交换到后面,依次重复,直到子序列长度为1,排序完成。所以,关键是建立一个最大堆。
实现思路:
-
一步一步实现,先只考虑三个结点如何建立最大堆,
-
然后考虑如何将整个数组建立成一个最大堆:这一步就是为了找最大值(和直接选择排序找最大值的思路可以对比一下)
-
最后将最大的元素与最后的元素交换顺序,"除去"最后一个,剩下的继续建立最大堆,找出最大元素,再放到最后…
堆排序算法分析:
- 堆排序时间复杂度为O(n*log2n)。
- 堆排序空间复杂度为O(1)。
- 堆排序算法不稳定。
堆排序算法实现:
package com.wlw.algorithm;
/**
* 堆排序:利用最小/大堆的特性,每次选择出最小/大的值(这也是完全二叉树的一种应用,利用的节点与节点之前的顺序关系)
* 实现思路:一步一步实现:
* 1.先只考虑三个结点如何建立最大堆,
* 2.然后考虑如何将整个数组建立成一个最大堆,:从下往上的把每一棵子树建立成最大堆即可,所以应该从下标为 n/2-1 的节点开始,依次-1,就可把所有的子树都弄完
* 3.最后 将最大的元素与最后的元素交换顺序,"除去"最后一个,剩下的继续建立最大堆,找出最大元素,再放到最后......
*
*/
public class HeapSort {
public static void main(String[] args) {
int[] keys = {16, 32, 4, 7, 64};
heapSort(keys, keys.length);
print(keys);
}
//堆排序
public static void heapSort(int[] keys, int len) {
//一开始先建立一个堆
buildHeap(keys, len);
//建立第一个堆之后,将第一个(最大的)与数组最后的元素交换,之后再建立堆,这里有i来控制把排好序的最大值去掉
for (int i = len-1; i >= 0; i--) {
//第一个(最大的)与数组最后的元素交换
swap(keys, i, 0);
// 把整个树再建一次最大堆,目的都是找最大值。
//buildHeap(keys, i);
sift(keys, i);
}
}
//将整个数组,建立成最大堆
static void buildHeap(int[] keys, int len) {
for (int i = len/2-1; i >= 0; i--) {
heapify(keys, len, i);
}
}
//将某个父节点为parent的子树 调整为最大堆
static void heapify(int[] keys, int len, int parent) {
if (parent >= len) {
return;
}
int c1 = 2*parent + 1;
int c2 = 2*parent + 2;
int max = parent;
if (c1 < len && keys[c1] > keys[max]) {
max = c1;
}
if (c2 < len && keys[c2] > keys[max]) {
max = c2;
}
if (max != parent) {
swap(keys, max, parent);
}
}
// 将buildHeap() 与 heapify()方法合并
static void sift(int[] keys, int len) {
for (int parent = len/2-1; parent >= 0 ; parent--) {
int c1 = 2 * parent + 1;
int c2 = 2 * parent + 2;
int max = parent;
if (c1 < len && keys[c1] > keys[max]) {
max = c1;
}
if (c2 < len && keys[c2] > keys[max]) {
max = c2;
}
if (max != parent) {
swap(keys, max, parent);
}
}
}
static void swap(int[] keys, int i, int j) {
int temp = keys[i];
keys[i] = keys[j];
keys[j] = temp;
}
static void print(int[] keys) {
for (int key : keys) {
System.out.print(key+" ");
}
}
}
教材上的:
//(第5版)9.3.2 堆排序
public static void heapSort(int[] keys) //堆排序(升序),最大堆
{
heapSort(keys,true);
}
//堆排序,若asc取值为true,升序排序,创建最大堆;否则降序,创建最小堆
public static void heapSort(int[] keys, boolean asc)
{
for (int i=keys.length/2-1; i>=0; i--) //创建最小/大堆,根结点值最小/大
sift(keys, i, keys.length-1, !asc);
System.out.print("最"+((!asc)?"小":"大")+"堆:");
Array1.print(keys);
System.out.println("非递归算法,最小堆? "+isHeap(keys,true)+",最大堆? "+isHeap(keys,false));
System.out.print("堆排序("+((!asc)?"降":"升")+"序):");
for (int i=keys.length-1; i>0; i--) //每趟将最小/大值交换到后面,再调整成最小/大堆
{
swap(keys, 0, i); //交换keys[0]与keys[i]
sift(keys, 0, i-1, !asc);
}
Array1.print(keys);
}
//将keys数组中以parent为根的子树调整成最小/大堆,子序列范围为parent~end。
private static void sift(int[] keys, int parent, int end, boolean minheap)
{
// System.out.print("sift "+parent+".."+end+" ");
int child=2*parent+1; //child是parent的左孩子
int value=keys[parent];
while (child<=end) //沿较小/大值孩子结点向下筛选
{
if (child<end && (minheap ? keys[child]>keys[child+1] : keys[child]<keys[child+1]))
child++; //child记住孩子值较小/大者
if (minheap ? value>keys[child] : value<keys[child]) //若父母结点值较小/大
{ keys[parent] = keys[child]; //将较小/大孩子结点值上移
parent = child; //parent、child两者都向下一层
child = 2*parent+1;
}
else break;
}
keys[parent] = value; //当前子树的原根值调整后的位置
// Array1.print(keys);
}