堆排序与快速排序
前言
前面差不多学习了插入排序、选择排序、冒泡排序、归并排序。这些排序除了归并排序在时间上消耗为:θ(nlgn)外,其余排序时间消耗都为:θ(n2).
接下来要讲的就是两种比较优雅的比较排序算法:堆排序和快速排序。 堆排序最坏情况下可以达到上界:ο(nlgn).快速排序平均情况下可以达到:θ(nlgn)。
堆排序
堆排序的关键在于完全二叉树。堆排序开始要构建一个完全二叉树,且该完全二叉树必须满足某一个结点大于左子节点的值和右子结点的值。我们假设根结点中的值为数组中下标为0的元素,根结点的左子结点下标为1,根结点的右子结点下标为2...依此类推。假设某一个结点的值对应数组中下标为i的元素,那么该结点的左子结点的值对应数组的数组元素下标为(2*i+1),右子结点的值对应数组元素下标为(2*i+2)。堆排序的步骤是:
1. 首先要构造一个以数组中下标为0的元素作为根结点的完全堆二叉树。此二叉树构造完成之后可以保证根结点的值为数组中最大的元素且对于堆中的所有结点的值都大于它的左子结点和右子结点的值。
2. 将堆中的根结点和堆中最后一个结点替换,然后重新构造以数组下标为0,长度为数组长度减1的完全堆二叉树。
3. 重复第二部直到需要构建的堆中元素只剩下唯一一个。
具体代码实现如下:
1 public static void main(String[] args)
2 {
3 int[] arr = {1, 12, 11, 4, 5, 7, 3, 1, 23, 56, 34, 76, 25};
4 heapSort(arr);
5 }
6
7 /**
8 * 堆排序
9 * @param arr 待排序的数组
10 */
11 private static void heapSort(int[] arr) {
12 for (int i=arr.length/2; i>=0; i--)
13 {
14 buildHeapTree(arr, i, arr.length);
15 }
16
17 for (int i=arr.length-1; i>=0; i--)
18 {
19 swap(arr, 0, i); // 交换根结点和堆树中最后一个结点
20 buildHeapTree(arr, 0, i); // 再次以下标为0的元素开始构建层次递减的堆树
21 }
22
23 System.out.println(Arrays.toString(arr));
24 }
25
26 /**
27 * 构建堆树(满足时一颗完全二叉树,且任何结点大于他的子结点)
28 * @param arr 传入的数组
29 * @param rootIndex 根结点对应的数组下标(即以当前下标作为根结点来将数组中改下标之后的元素作为其子节点的堆树)
30 * @param deep 堆的深度(对于超过堆深度的数组中的元素不进行堆树的构建)
31 */
32 private static void buildHeapTree(int[] arr, int rootIndex, int deep) {
33 int leftChildIndex = rootIndex * 2 + 1;
34 int rightChildIndex = rootIndex * 2 + 2;
35 int maxIndex = rootIndex;
36 if (leftChildIndex < deep && arr[leftChildIndex] > arr[rootIndex])
37 {
38 // 如果左子结点大于右子结点,说明当前最大结点的下标应该为左子结点的下标
39 maxIndex = leftChildIndex;
40 }
41
42 if (rightChildIndex < deep && arr[rightChildIndex] > arr[maxIndex])
43 {
44 // 如果右子结点的值大于与左子结点比较之后得到的最大的结点的值,则说明最大结点的下标应该为右子结点的下标
45 maxIndex = rightChildIndex;
46 }
47
48 if (maxIndex != rootIndex)
49 {
50 // 以当前下标从数组中的元素开始没法够造成堆树,此时需要在树中将最大值所在的结点和根结点的位置互换,又因为堆树的构建是自下往上的,互换了之后需要考虑对前面最大值所在堆树的影响,所以需要再次递归调用以前面最大值元素所在位置为根结点构建堆树、
51 swap(arr, maxIndex, rootIndex);
52 buildHeapTree(arr, maxIndex, deep);
53 }
54 }
55
56 private static void swap(int[] arr, int index1, int index2) {
57 int temp = arr[index1];
58 arr[index1] = arr[index2];
59 arr[index2] = temp;
60 }
快速排序:
快速排序的思想其实和归并排序有些类似。都是通过分治的思想来实现的。只不过归并排序先是将数组均分排序,然后再进行归并整理。快速排序是先得到分开点,然后再对分出来的两个数组进行分别快速排序。
废话不多说直接上代码:
1 public static void main(String[] args)
2 {
3 int[] arr = {1, 12, 11, 4, 5, 7, 3, 1, 23, 56, 34, 76, 25};
4 quickSort(arr);
5 }
6
7 private static void quickSort(int[] arr) {
8 divideQuickSort(arr, 0, arr.length - 1);
9 System.out.println(Arrays.toString(arr));
10 }
11
12 private static void divideQuickSort(int[] arr, int start, int end) {
13 if (start >= end)
14 {
15 return;
16 }
17
18 int r = partition(arr, start, end);
19 divideQuickSort(arr, start, r - 1);
20 divideQuickSort(arr, r + 1, end);
21 }
22
23 /**
24 * 得到快速排序的分割点
25 * @param arr 传入的待排序的数组
26 * @param start 排序的起始元素下标
27 * @param end 排序的终止元素下标
28 * @return 分割点的下标
29 */
30 private static int partition(int[] arr, int start, int end) {
31 // 先要找一个对比的元素,这里取终止元素作为对比的元素
32 int compareValue = arr[end];
33
34 // 从起始元素到比较元素前一个元素循环与终止元素进行比对
35 int placeStartIndex = start;
36 for (int i = start; i <= end-1; i++)
37 {
38 if (arr[i] < compareValue)
39 {
40 // 发现比比较元素小的元素,通过交换位置依次从起始元素位置开始摆放
41 swap(arr, i, placeStartIndex++);
42 }
43 }
44
45 // 将比较元素放到比它都小的元素的右侧
46 swap(arr, placeStartIndex, end);
47
48 // 此时返回当前比较元素交换之后的位置
49 return placeStartIndex;
50 }
51
52 private static void swap(int[] arr, int index1, int index2) {
53 int temp = arr[index1];
54 arr[index1] = arr[index2];
55 arr[index2] = temp;
56 }
以上两种算法都是比较优雅的比较算法。实际上他们在实现的时候不需要额外的分配内存,直接在数组之内进行比较和位置调换。特别是堆排序,完美的实现了将数组又作为线性的元素列表,又做为树结点。可以看到比较算的时间极限应该就在Ω(nlgn),对于其他的一些关于数组长度线性的排序算法必须依赖于数组的某些特性,而那些也不能称作完全的比较算法。
- ios开发基础知识 - 1
- 为UINavigationBar添加自定义背景
- UIScrollView无法响应touches的解决方法
- WCF技术剖析之十七:消息(Message)详解(下篇)
- UITextView自动滚动的解决方案
- MVC解决Json DataGrid返回的日期格式是/Date(20130450000365)
- Easyui 让DataGrid适应浏览器宽度
- Easyui 让Window弹出居中与最大化后居中
- WCF技术剖析之十六:数据契约的等效性和版本控制
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(13)-系统日志和异常的处理③
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(12)-系统日志和异常的处理②
- Enterprise Library Policy Injection Application Block 之二: PIAB设计和实现原理
- 育碧与研究所共同开发的AI,是什么样的?
- 大家都在关注AI,但这些事你可能并不知道!
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 1. 初识Jackson -- 世界上最好的JSON库
- 实时应用程序中checkpoint语义以及获取最新offset
- Python2.6低版本Python如何实现爬虫功能
- python开发web服务 bottle框架
- 这样设置 IDEA,让你爽到飞起!
- GO 切片实力踩坑
- BeautifulSoup解析html介绍
- 微信会被封?!包子 Leetcode 1512 solution Number of Good Pairs
- VUE是什么?
- 爬虫如何抓取网页的动态加载数据-ajax加载
- Python 为什么用 # 号作注释符?
- 基于矩阵分解原理的推荐系统
- Python实现信息自动配对爬虫排版程序
- EOF是不是字符
- Alibaba开源Java诊断工具Arthas简单介绍