详谈排序算法之选择类排序(两种方法实现堆排序)

   今天我们再来讨论一下选择类排序,选择类排序分为:简单排序,树形选择排序和堆排序。但我们主要说的是简单和堆排序两个,因为树形选择排序使用了较多的辅助空间,以及和∞进行多余比较,为弥补树型选择排序的这些缺点, J.W.J.Williams 在 1964 年提出了进一步的改进方法,即堆排序.对于我个人而言。。一开始并不是很理解它的算法思想,纠结了许久。在网上查找资料的时候发现这位大神的文章思路十分清晰,而且把创建堆以及堆化数组的算法讲解的十分详细。如果有不明白堆排序思路的,可以先看看这篇文章~堆与堆排序 
  下面我们先从简单排序算法说起~

   简单排序算法的基本思想非常简单,即:第一趟,从 n 个元素中找出关键字最小的元素与第一个元素交换;第二趟,在从第二个元素开始的 n-1 个元素中再选出关键字最小的元素与第二个元素交换;如此,第 k 趟,则从第 k 个元素开始的 n-k+1 个元素中选出关键字最小的元素与第 k 个元素交换,直到整个序列按关键字有序。

<strong>	public void Selection_Sort(int[] A, int N) {
		for (int i = 0; i < N; i++) {
			int Min = i;
			for (int j = i + 1;j < N;j++){  //找出最小元
				if(A[j] < A[Min])
					Min = j;
			}
			if(Min != i){  //交换位置:将未排序部分的最小元换到有序部分的最后位置
				int temp = A[Min];
				A[Min] = A[i];
				A[i] = temp;
			}
		}
		for(int i = 0 ; i < N; i++){
			System.out.print(A[i] + " ");
		}
	}</strong>

【效率分析】

空间效率: 显然简单选择排序只需要一个辅助空间。

时间效率: 在简单选择排序中,所需移动元素的次数较少,在待排序序列已经有序的情况下,简单选择排序不需要移动元素,在最坏的情况下,即待排序序列本身是逆序时,则移动元素的次数为 3(n-1)。然而无论简单选择排序过程中移动元素的次数是多少,在任何情况下,简单选择排序都需要进行n(n-1)/2 次比较操作,因此简单选择排序的时间复杂度为Ο(n2)。

算法改进思想: 从上述效率分析中可以看出,简单选择排序的主要操作是元素间的比较操作,因此改进简单选择排序应从减少元素比较次数出发。在简单选择排序中,首先从 n个元素的序列中选择关键字最小的元素需要 n-1 次比较,在 n-1 个元素中选择关键字最小的元素需要 n-2 次比较……,在此过程中每次选择关键字最小的元素都没有利用以前比较操作得到的结果。欲降低比较操作的次数,则需要把以前比较的结果记录下来,由此得到一种改进的选择类排序算法,即树型选择排序。

2.树形选择排序

   树形选择排序的基本思想是:先把待排序的n个元素两两进行比较,取出较小者,若轮空则直接进入下一轮比较;然后在[n/2]个较小者中,采用同样的方法进行比较,再选出较小者;如此反复,直到选出关键字最小的元素为止。这个过程可以使用一颗具有n个结点的完全二叉树来表示,最终选出的关键字最小的元素就是这棵二叉树的根结点。

 
 由此,我们可以看出在树型选择排序过程中为找到关键字最小的元素一共进行了 n-1 次比较,此后每次找出一个关键字所需要的比较次数等于完全二叉树的高度 h,而具有 n 个叶子结点的完全二叉树其高度为[log n],由此可知除最小关键字外,每选择一个次小关键字需要进行[log n]次比较.

3.堆排序(选择算法的一种改进)

   堆是一种重要的数据结构,为一棵完全二叉树, 底层如果用数组存储数据的话,假设某个元素为序号i(Java数组从0开始,i为0到n-1), 如果它有左子树,那么左子树的位置是2i+1,如果有右子树,右子树的位是2i+2,如果有父节点,父节点的位置是(n-1)/2取整。分为最大堆和最小堆,最大堆的任意子树根节点不小于任意子结点,最小堆的根节点不大于任意子结点。所谓堆排序就是利用堆这种数据结构来对数组排序,我们使用的是最大堆。处理的思想和冒泡排序,选择排序非常的类似,一层层封顶,只是最大元素的选取使用了最大堆。最大堆的最大元素一定在第0位置,构建好堆之后,交换0位置元素与顶即可。堆排序为原位排序(空间小),
且最坏运行时间是O(n2),是渐进最优的比较排序算法。

设有 n 个元素,欲将其按关键字排序。可以首先将这 n 个元素按关键字建成堆,将堆顶元素输出,得到 n 个元素中关键字最大(或最小)的元素。然后,再将剩下的 n-1 个元素重新建成堆,再输出堆顶元素,得到 n 个元素中关键字次大(或次小)的元素。如此反复执行,直到最后只剩一个元素,则可以得到一个有序序列,这个排序过程称之为堆排序。

从对排序的过程中可以看到,在实现对排序时需要解决两个问题:

1. 如何将 n 个元素的序列按关键字建成堆;

2. 输出堆顶元素后,怎样调整剩余 n-1 个元素,使其按关键字成为一个新堆。

我们首先第二个问题,即输出堆顶元素后,对剩余元素重新建成堆的调整过程。设有一个具有 m 个元素的堆,输出堆顶元素后,剩下 m-1 个元素。具体的调整方法是:首先,将堆底元素(最后一个元素)送入堆顶,此时堆被破坏,其原因仅是根结点不满足堆的性质,而根结点的左右子树仍是堆。然后,将根结点与左、右子女中较大(或较小)的进行交换。若与左孩子交换,则左子树堆被破坏,且仅左子树的根结点不满足堆的性质;若与右孩子交换,则右子树堆被破坏,且仅右子树的根结点不满足堆的性质。继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,则堆被重建。我们称这个自根结点到叶子结点的调整过程为筛选。

 
 例如图(a)为一个大顶堆,在输出堆顶元素 53 之后,将堆底元素 26 送入堆顶,如图(b)所示;然后将 26 与 36、 42 中大的元素交换,交换后,以 26 为根的子树已是一个堆;此时筛选结束,得到一个新堆,如图(c)所示。如果继续输出堆顶元素 42,然后重建堆,则结果如图( d)所示。

由此,如果我们能够建立一个堆,那么排序的过程就是不断输出堆顶并进行筛选的过程。现在的关键问题是如何由一个元素的初始序列构造一个堆,实际上建堆的方法是逐层向上对每个非终端结点进行一次筛选即可。

例如,对于待排序的初始序列{28
, 26 , 17 , 36 , 20 , 42 , 11 , 53},初始建堆的过程如图所示。(a)是由初始序列得到的完全二叉树;初始建堆首的过程,以按层从下到上的第一个非叶子结点开始,即从 36 开始,对 36 进行调整,过程如图(b)所示,调整结果如图(c)所示;然后对下一个非叶子结点 17 进行调整,调整过程如图(c),结果如图(d)所示;继续上述过程直到根结点 28 为止,对 28 进行调整后,即得到一个大顶堆,结果如图(f)所示。

(因为自己一开始没有写出来。。于是把自己觉得思路清晰的代码Copy过来,供大家学习交流
=。= |||) 

<strong>方法1:
public class HeapSort {
 /**
  * 堆排序,最坏时间复杂度O(nlog2n),平均性能接近于最坏性能。 由于建初始堆所需的比较次数多,故堆不适合记录较少的比较。
  * 堆排序为原地不稳定排序
  *
  * @param array
  */
 public void Heap_Sort(int[] A, int N) {
  for (int i = 1; i < A.length; i++) {
   makeHeap(A, i);
  }
  for (int i = A.length - 1; i > 0; i--) {
   int temp = A[i];
   A[i] = A[0];
   A[0] = temp;
   rebuildHeap(A, i);
  }
  for (int i = 0; i < N; i++) {
   System.out.print(A[i] + " ");
  }
 }
 /**
  * 堆排序辅助方法---创建堆
  * @param array
  * @param k
  */
 private static void makeHeap(int[] array, int k) {
  int current = k;
  while (current > 0 && array[current] > array[(current - 1) / 2]) {
   int temp = array[current];
   array[current] = array[(current - 1) / 2];
   array[(current - 1) / 2] = temp;
   current = (current - 1) / 2;
  }
 }
 /**
  * 堆排序辅助方法---堆的根元素已删除,末尾元素已移到根位置,开始重建
  * @param array
  * @param size
  */
 private static void rebuildHeap(int[] array, int size) {
  int currentIndex = 0;
  int right = currentIndex * 2 + 2;
  int left = currentIndex * 2 + 1;
  int maxIndex = currentIndex;
  boolean isHeap = false;
  while (!isHeap) {
   if (left < size && array[currentIndex] < array[left]) {
    maxIndex = left;
   }
   if (right < size && array[maxIndex] < array[right]) {
    maxIndex = right;
   }
   if (currentIndex == maxIndex) {
    isHeap = true;
   } else {
    int temp = array[currentIndex];
    array[currentIndex] = array[maxIndex];
    array[maxIndex] = temp;
    currentIndex = maxIndex;
    right = currentIndex * 2 + 2;
    left = currentIndex * 2 + 1;
   }
  }
 }
 public static void main(String[] args) {
  HeapSort heapSort = new HeapSort();
  int[] B = new int[] { 26, 53, 48, 11, 13, 48, 32, 15 };
  int N = 8;
  heapSort.Heap_Sort(B, N);
 }
}

方法2:
public class HeapSort2 {
 public static void main(String[] args) {
  int[] array = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3 };
  System.out.println("Before heap:");
  ArrayUtils.printArray(array);
  heapSort(array);
  System.out.println("After heap sort:");
  ArrayUtils.printArray(array);
 }
 //建立最大堆
 public static void heapSort(int[] array) {
  if (array == null || array.length <= 1) {
   return;
  }
  buildMaxHeap(array);
  for (int i = array.length - 1; i >= 1; i--) {
   ArrayUtils.exchangeElements(array, 0, i);
   maxHeap(array, i, 0);
  }
 }
 //调整堆
 private static void buildMaxHeap(int[] array) {
  if (array == null || array.length <= 1) {
   return;
  }
  int half = array.length / 2;
  for (int i = half; i >= 0; i--) {
   maxHeap(array, array.length, i);
  }
 }
 private static void maxHeap(int[] array, int heapSize, int index) {//最大堆
  int left = index * 2 + 1;// 左孩子的下标(如果存在的话)
  int right = index * 2 + 2;// 右孩子的下标(如果存在的话)
  int largest = index;
  if (left < heapSize && array[left] > array[index]) {
   largest = left;
  }
  if (right < heapSize && array[right] > array[largest]) {
   largest = right;
  }
  if (index != largest) {
   ArrayUtils.exchangeElements(array, index, largest);
   maxHeap(array, heapSize, largest);
  }
 }
}
class ArrayUtils {
 public static void printArray(int[] array) {
  System.out.print("{");
  for (int i = 0; i < array.length; i++) {
   System.out.print(array[i]);
   if (i < array.length - 1) {
    System.out.print(", ");
   }
  }
  System.out.println("}");
 }
 public static void exchangeElements(int[] array, int index1, int index2) {
  int temp = array[index1];
  array[index1] = array[index2];
  array[index2] = temp;
 }
}</strong>

显然堆排序只需要一个辅助空间,比树形排序的效率更高一些。堆排序在元素较少时由于消耗较多时间在初始建堆上,因此不值得提倡,然而当元素较多时还是很有效的排序算法。

PS:如果大家还想看其他的排序算法~请戳这里
 =。=

交换类排序 
   插入类排序

时间: 2024-10-10 19:11:42

详谈排序算法之选择类排序(两种方法实现堆排序)的相关文章

排序算法总结----比较类排序

概述:排序算法可分为比较性的排序,以及运算性的排序:这里详细介绍这些排序的原理,性能,实现,以及应用场合. 前面是维基百科的介绍,这里介绍几个比较典型的算法. 理论 计算复杂性理论 大O符号 全序关系 列表 稳定性 比较排序 自适应排序 排序网络 整数排序 交换排序 冒泡排序 鸡尾酒排序 奇偶排序 梳排序 侏儒排序 快速排序 臭皮匠排序 Bogo排序 选择排序 选择排序 堆排序 Smooth排序 笛卡尔树排序 锦标赛排序 循环排序 插入排序 插入排序 希尔排序 二叉查找树排序 图书馆排序 Pat

排序算法总结----运算类排序

运算排序 第一:计数排序 1:原理 对于每个输入数,确定小于该数的个数.这样可以直接把数放在输出数组的位置. 2:性能 最差时间复杂度 最优时间复杂度 平均时间复杂度 最差空间复杂度 注:稳定算法 3:应用 适合0~100的范围的数,当然可以和基排序结合而扩展数的范围. 4:实现 void CountingSort(int *A, int *B, int array_size, int k) { int i, value, pos; int * C=new int[k+1]; for(i=0;

Javascript使用function创建类的两种方法

1.使用function类 //myFunction.js var CMyFunc=function() { //类的公共方法,供外部调用 this.Func1=function() { var i=0; return i; } this.Func2=function() { _privateFunc(); } //类中的私有方法,供公共方法调用 function _privateFunc() { return 0; ] } CMyFunc myFunc=new CMyFunc(); 使用:其它

详谈排序算法之插入类排序(两种思路实现希尔排序)

1. 排序( sorting) 的功能是将一个数据元素的任意序列,重新排列成一个按关键字有序的序列.其确切的定义为: 假设有n个数据元素的序列{R1 , R2 , - , Rn},其相应关键字的序列是{K1 , K2 , - , Kn} ,通过排序要求找出下标 1 , 2 , - , n的一种排列p1 , p2 , - , pn,使得相应关键字满足如下的非递减(或非递增)关系Kp1 ≤ Kp2 ≤ - ≤ Kpn这样,就得到一个按关键字有序的纪录序列{ Rp1 , Rp2 , - , Rpn }

详谈排序算法之交换类排序(两种方法实现快速排序【思路一致】)

1.冒泡排序    起泡排序的思想非常简单.首先,将 n 个元素中的第一个和第二个进行比较,如果两个元素的位置为逆序,则交换两个元素的位置:进而比较第二个和第三个元素关键字,如此类推,直到比较第 n-1 个元素和第 n 个元素为止:上述过程描述了起泡排序的第一趟排序过程,在第一趟排序过程中,我们将关键字最大的元素通过交换操作放到了具有 n 个元素的序列的最一个位置上.然后进行第二趟排序,在第二趟排序过程中对元素序列的前 n-1 个元素进行相同操作,其结果是将关键字次大的元素通过交换放到第 n-1

常见排序算法——七大比较类排序算法

算法 最坏复杂度 平均复杂度 最好复杂度 空间复杂度 选择排序 O($n^2$) O($n^2$) O($n^2$) O(1) 插入排序 O($n^2$) O($n^2$) O($n$) O(1) 希尔排序 O($nlog(n))$~O($n^2$) O($n^{1.3}$) O($n^2$) O(1) 冒泡排序 O($n^2$) O($n^2$) O(n)(用交换flag改进) O(1) 快速排序 O($n^2$) O($nlog(n)$) O($nlog(n)$) O(log(n))~O(n

http的get字符串类型和post自定义类的两种方法--测试成功

1. string类型的参数,get方法 /// <summary> /// 执行服务的方法 /// </summary> /// <param name="MethodName">方法名称</param> /// <param name="pars">参数列表</param> /// <param name="_type">返回值类型</param>

字符串反序列化成类的两种方法

ApiResult<AllowanceEntity> res = JsonConvert.DeserializeObject<ApiResult<AllowanceEntity>>(result.Value); str = JsonConvert.SerializeObject(obj); 还一个  javascript 里面  添加引用  system.web.extention

选择类排序总结

选择类排序总结 所谓选择类排序的思想就是:从数组的中选出最大或最小的,通过多次选择最后达到排序的目的 首先是简单选择排序 思想:每趟扫描中,选出最小的数字放在最前面,然后从第二个数字开始扫描,直到只剩下最后一个数不需要扫描 void EasySelect_Sort(int a[],int n) { int k; for(int i=1;i<n;i++) { k=i-1; for(int j=i;j<n;j++) { if(a[j]<a[k]) { k=j; } } if(k!=(i-1)