1.排序算法--选择排序和堆排序

一.选择排序

JAVA代码:
//泛型选择排序
	public static <T extends Comparable<? super T>> void selecttionSort(T[] arr){
		int smallIndex;
		int n=arr.length;
		T temp;
		
		for (int i = 0; i < n-1; i++) {
			smallIndex=i;
			for (int j = i+1; j < n; j++) {
				if (arr[j].compareTo(arr[smallIndex])<0) {
					smallIndex=j;
				}
			}
			temp=arr[i];
			arr[i]=arr[smallIndex];
			arr[smallIndex]=temp;
		}
	}

C语言代码:
#include<stdio.h>

void selectSort(int a[],int n)
{
    int i,j;
    for(i=0;i<n-1;i++)
     for(j=i+1;j<n;j++)
     {
         if(a[j]<a[i])
         {
             int temp =a[i];
             a[i]=a[j];
             a[j]=temp;
         }
     }
}

void main()
{
    int a[]={2,6,5,3,4,4,2,5,7,1};
    selectSort(a,10);
    for(int i=0;i<10;i++)
        printf("%d\t",a[i]);
}

1. 性能分析

简单选择排序所需进行记录移动的操作次数较少,这一点上优于冒泡排序,最佳情况下(待排序序列有序)记录移动次数为0,最坏情况下(待排序序列逆序)记录移动次数n-1。外层循环进行了n-1趟选择,第i趟选择要进行n-i次比较。每一趟的时间:n-i次的比较时间+移动记录的时间(为一常数0或1,可以忽略)。总共进行了n-1趟。忽略移动记录的时间,所以总时间为(n-1)*(n-i)=n^2-(i+1)*n+i。时间复杂度为O(n^2)。不管是最坏还是最佳情况下,比较次数都是一样的,所以简单选择排序平均时间、最坏情况、最佳情况 时间复杂度都为O(n^2)。同时简单选择排序是一种稳定的原地排序算法。

二. 堆排序

堆排序是利用堆的性质进行的一种选择排序。下面先讨论一下堆。

1.堆

堆实际上是一棵完全二叉树,其任何一非叶节点满足性质:

Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2]

即任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字。

堆分为大顶堆和小顶堆,满足Key[i]>=Key[2i+1]&&key>=key[2i+2]称为大顶堆,满足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]称为小顶堆。由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。

2.堆排序的思想

利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

其基本思想为(大顶堆):

1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无须区;

2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];

3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

3.操作过程如下:

1)初始化堆:将R[1..n]构造为堆;

2)将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。

因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。

4.性能分析

从上述过程可知,堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从R[1...n]中选择最大记录,需比较n-1次,然后从R[1...n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。堆排序为不稳定排序,不适合记录较少的排序。

5.反思堆排序 ------ 揭开选择排序的本质和基本思想

其实待排序的整个过程中都是数组元素在不断的交换移动,树形的堆只是能形象的表示这个过程。通过观察这个数组的变化,我们发现了什么呢?

仔细回想一下筛选法调整堆的过程我们发现,第i次调整堆,其实就是把A中的第i大元素放到首位置A[1],然后A[1]和A[n-i+1]互换.这样A[(n-i+1)...n]就已经有序,于是又把A[1...n-i]看成一个堆再进行排序,如此重复。所以堆排序的本质和选择排序的本质是一样的。选择一个待排序序列中的最小(大)值,这就是选择排序的本质。

选择排序的基本思想是:每一趟从n-i+1个记录中选择最小(大)记录和第i(n-i+1)个记录交换。

JAVA代码:
package algorithm.sort;

public class HeapSort<T extends Comparable<T>> {
	//大顶堆
	private void adjustHeap(T[] data,int s,int size){
		int lchild=2*s+1;	//左孩子序号
		int rchild=2*s+2;	//右孩子序号
		int temp=s;			//临时变量
		if(s<size/2){
			if (lchild<size&&data[lchild].compareTo(data[temp])>0) {
				temp=lchild;
			}
			if (rchild<size&&data[rchild].compareTo(data[temp])>0) {
				temp=rchild;
			}
			if (temp!=s) {
				swap(data,s,temp);
				adjustHeap(data,temp,size);	//避免调整后以temp为父节点的子树不是堆
			}
		}
	}
	
	private void buildHeap(T[] data,int size){
		for (int i = size/2-1; i >=0; i--) {
			adjustHeap(data,i,size);
		}
	}
	
	public void heapSort (T data[]){
		int size=data.length;
		buildHeap(data,size);		
		for (int i = size-1; i >=0; i--) {
			swap(data, i, 0);
			adjustHeap(data, 0,i);
		}
	}
	
	private void swap(T[] data,int i,int j){
		T temp=data[i];
		data[i]=data[j];
		data[j]=temp;	
	}	
}


package algorithm.sort;

public class HeapSortTest {

	//测试
	public static void main(String[] args) {
		 
//		Integer[] arry = new Integer[10000];
//		for (int i = 0; i < 10000; ++i) {
//		   arry[i]= (int) (Math.random() * 10000);
//		}
		Integer[] arry={17,5,2,4,1,4,1,3,5};
		HeapSort<Integer> hs=new HeapSort<Integer>();
		hs.heapSort(arry);
		for (int i = 0; i < arry.length; i++) {
			System.out.println(arry[i]);
		}
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值