排序基础之归并排序、快排、堆排序、希尔排序思路讲解与Java代码实现
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6594855.html
一:归并排序==通过中间值进行左右划分递归,然后调用合并函数对左右递归的结果进行合并(用临时数组存放合并结果,再覆盖原数组对应区间)
第一步:把数组通过递归,划分成 一个元素区间大小;
第二步:相邻区间合并成一个更大的区间,合并过程中实现有序,小者在前(此步相当于排序了上一递归时划分出的元素区间);
第三步:把当前合并得到的有序区间返回上层递归,与同等大小的相邻有序区间再次进行合并;
...
第N/2步:把两个N/2大小的有序区间合并,得到有序的整个数组。
复杂度计算:T=N/2(合并次数)*logN(递归划分次数)=O(N*logN)
public class MergeSort {
public int[] mergeSort(int[] A, int n) {
if(A!=null && n>0){
MergeSort(A,0,n-1);
}
return A;
}
//划分:通过中间值划分出左右区间,然后对左右区间进行合并。
public void MergeSort(int[] A,int left,int right){
if(left<right){
int mid=(left+right)/2;
MergeSort(A,left,mid);
MergeSort(A,mid+1,right);
Combine(A,left,mid,right);
} }
//合并:通过临时数组存放合并结果,每次取两区间开头值的较小者合并,一方为空后另一方全部依次合并,最后把合并结果覆盖原数组对应区间
public void Combine(int[] A,int left,int mid,int right){
int[] temp=new int[right-left+1];//临时数组存放合并结果
int leftindex=left;//左区间下标
int rightindex=mid+1;//右区间下标
int tempindex=0;//临时数组当前保存元素下标 while(leftindex<=mid && rightindex<=right){//如果两个区间都还有元素
//则取两区间开头元素进行比较,最小值合并到临时数组
if(A[leftindex]<A[rightindex]){
temp[tempindex++]=A[leftindex++];
}else{
temp[tempindex++]=A[rightindex++];
}
} //如果左区间已经取完了,右区间还有,则把右区间的依次合并到临时数组
while(rightindex<=right){
temp[tempindex++]=A[rightindex++];
}
//如果右区间已经取完了,左区间还有,则把左区间的依次合并到临时数组
while(leftindex<=mid){
temp[tempindex++]=A[leftindex++];
} //最后,把临时数组的合并结果,覆盖到原数组中的合并区间
int temp_copy_index=0;
for(int i=left;i<=right;++i){
A[i]=temp[temp_copy_index++];
}
}
}
二:快速排序==挖坑填数思想进行排序:用左边第一个元素作为参照值,并挖出来,空出位置;然后从右边往左找到第一个小于参照值的数填进来;此时右边空出一个位置;然后从左边找到一个大于参照值的数,填到右边的空位,此时左边空出一个位置......不断左右找数填坑,最终左右遍历坐标相等时的坑位就是参照值的位置。由 挖坑填数 排序得到的参照值的位置划分序列,分别进行左右递归;
第一步排序左右:选一个参照值,定义两个下标left和right,一个从头到尾,一个从尾到头遍历。各自找到一个大于/小于 参照值 的元素则停下,然后把这个元素与参照值进行交换,使得小于参照值的都在右边,大于的在右边。两下标相等时停止遍历。此时参照值的位置就划分了左右序列,把此时参照值位置返回。
第二步递归左右:对第一步得到的参照值的左、右序列继续执行排序操作——选参照值、前后遍历与参照值比较交换,最终使参照值处于合适位置。
复杂度计算:T=N(每次排序交换左右遍历N个元素)*logN(每次平分序列,logN次可以把序列分至单个元素一个区间)=O(N*logN)。
public class QuickSort {
public int[] quickSort(int[] A, int n) {
quick(A,0,n-1);
return A;
}
//由 挖坑填数 排序得到的参照值的位置划分序列,分别进行左右递归
public void quick(int[] A,int left,int right){
if(left<right){
int mid=sort(A,left,right);//先进行左右排序,并把排序结果的分界点下标返回,作为递归的中间值
quick(A,left,mid);//然后对左右排序的左序列、右序列进行递归
quick(A,mid+1,right);
}
}
//由挖坑填数思想进行排序:用左边第一个元素作为参照值,并挖出来,空出位置;然后从右边往左找到第一个小于参照值的数填进来;此时右边空出一个位置;然后从左边找到一个大于参照值的数,
//填到右边的空位,此时左边空出一个位置......不断左右找数填坑,最终左右遍历坐标相等时的坑位就是参照值的位置。
public int sort(int[] A,int left,int right){
int i=left;
int j=right;
int temp=A[i];
while(i<j){
while(j>i && A[j]>=temp){//从右往左找到第一个小于参照值的元素
--j;
}
if(j>i){//根据元素位置判断是否可以作出交换,是i则把参照值交换到右边,使找到的元素处于参照值左边,然后移动左边下标
A[i++]=A[j];
}
while(i<j && A[i]<=temp){
++i;
}
if(i<j){
A[j--]=A[i];
}
}
if(i==j){
A[i]=temp;//当左右遍历下标相等时,这个位置就是参照值的位置:左边都是小于它的数,右边都是大于它的数
}
return i;
}
}
三:希尔排序==每个步长的排序都从A[K]开始遍历,对每个元素(下标j),与j-k位比较交换,交换后修改j=j-k继续比较交换,插入到合适位置;遍历完后修改步长k;
第一轮:初始步长为k,从A[k]开始遍历,每个元素A[i]与前面A[i-n*k]元素进行插入排序(比较交换到合适位置);
第二步:--k,从A[k]开始遍历,每个元素A[i]与前面A[i-n*k]元素进行插入排序(比较交换到合适位置);
。。。
第k步:k==1,从A[1]开始遍历整个数组执行一次插入排序。
复杂度计算:T=(n-k)+(n-k+1)+...+n,T取决于k的选取,上限为O(n^1.5)。
public class ShellSort {
public int[] shellSort(int[] A, int n) {
//如果数组为空或数组只有一个元素,直接返回
if(A==null||n<2){
return A;
}
//令步长为n/2
int k=n/2;
//对步长k进行遍历
while(k>=1){
//每个步长的排序都从A[K]开始遍历
for(int i=k;i<=n-1;++i){//遍历k~n-1的元素
//对每个元素(下标j),与j-k位比较交换,交换后修改j=j-k继续比较交换,插入到合适位置
for(int j=i;j>=k;){//下标j负责执行当前元素与向前k位的元素进行比较交换.注意是 j>=k,因为比较时front=j-k就可以取到0了。
int front=j-k;
if(front>=0){//检查j的前k位是否越界
//如果当前元素小于前面第k个元素,则交换二者位置,同时修改当前元素下标j为j-k;继续与前面第k个进行比较
if(A[j]<A[front]){
int temp=A[front];
A[front]=A[j];
A[j]=temp;
j=front;
}else{//一旦交换到j大于j-k下标的元素,则说明当前元素已经插入到当前步长的合适位置。跳出循环,遍历下一元素
break;
}
}
}
}
--k;//修改步长
}
return A;
}
}
四:堆排序==由数组建立最大堆(从小塔堆出大塔),然后重复:取堆头值并删除堆头(A[0]与A[len]交换,无序数组长len减一);调整堆成为最大堆(移动金字塔);当堆中只有一个元素时,该数组已经有序(因为每次把堆头移到构造堆的数组的末尾,而构造堆的数组是基于原数组的无序部分的)
复杂度计算:T=t(n)(建堆)+n(取堆头n次)*logn(每次调整堆需要logn)=O(n*logn)
堆的定义:n个长数组array[0,...,n-1],当且仅当满足下列要求:(0 <= i <= (n-1)/2)
① array[i] <= array[2*i + 1] 且 array[i] <= array[2*i + 2]; 称为小根堆;
② array[i] >= array[2*i + 1] 且 array[i] >= array[2*i + 2]; 称为大根堆;//区分开大根堆和BST!大根堆是父节点大于两子结点即可。
调整方法为:当前节点与左右儿子(2r+1/2r+2)比较,若小于左右儿子中的较大者,则交换;此时,为了维持子堆的是最大堆,继续对交换后的结点与左右儿子比较交换...直到一个处于一个其左右儿子均小于它的位置。我用“移动金字塔”来描述这个过程——从要调整的结点p开始,p与其左右儿子p1、p2构成一个金字塔,如果p小于p1、p2中的较大者,则交换使得三者中最大者处于塔尖;然后移动金字塔,令p继续处于塔尖,继续与其新的左右儿子p1、p2的较大者比较......直到一个位置,p处于塔尖,p1、p2均小于p,则p处于合适位置了。
建立最大堆:
我们来看建立一个最大堆的右下角的第一个大根子堆:最后一个叶子结点为n-1,由公式 父节点=floor[(r-1)/2] 得父节点下标 (n-2)/2,比较父节点与左右儿子(叶子),把三者之间最大者交换成为父节点。构成了整个最大堆的最底层的一个小大根堆;
我们知道最大堆在数组中是逐层存放的,所以最后一个叶子结点的父节点(n-2)/2在数组中就是划分中间结点与叶子结点的分界;
我们从 (n-2)/2 倒过来逐个结点进行调整(建立小金字塔),就实现了逐层建立起最大堆的过程(从最底层金字塔开始堆叠出完成的大金字塔);
public class HeapSort {
public int[] heapSort(int[] A, int n) {
for(int i=(n-2)/2;i>=0;--i){//从最后一个中间结点开始,倒着逐层建立最大堆
siftdown(A,i,n);
}
//从最大堆取数:每次把堆头与无序数组最后一个元素交换;然后无序数组长度减一,把堆头从无序数组建立的堆中调整好位置
for(int i=0;i<n;++i){
swap_head(A,n-i);
siftdown(A,0,n-i-1);
}
return A;
}
public void siftdown(int[] A,int i,int len){ int curr_father=i;//要调整的结点作为父节点 while(curr_father*2+1<len){//当至少一个儿子存在时
int left=curr_father*2+1;
int max_of_son=left;//记录儿子中较大者,如果没有右儿子的情况下默认是左儿子 if(left!=len-1){//由于是完全二叉树,如果左儿子不是数组最后一个结点,则绝对有右儿子
//比较左右儿子,取较大者
int right=left+1;
max_of_son=(A[left]>A[right]?left:right);
} //比较父节点与儿子较大者,父节点大于儿子中较大者,则位置适合,调整结束;否则交换位置,并更新待调整结点下标为原max_of_son的下标,移动金字塔,该结点仍然为curr_father
if(A[curr_father]>A[max_of_son]){
break;
}else{
int temp=A[curr_father];
A[curr_father]=A[max_of_son];
A[max_of_son]=temp;
curr_father=max_of_son;
}
}
} public void swap_head(int[] A,int len){
int temp=A[0];
A[0]=A[len-1];
A[len-1]=temp;
}
}
排序基础之归并排序、快排、堆排序、希尔排序思路讲解与Java代码实现的更多相关文章
- JavaScript 数据结构与算法之美 - 归并排序、快速排序、希尔排序、堆排序
1. 前言 算法为王. 想学好前端,先练好内功,只有内功深厚者,前端之路才会走得更远. 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 JavaScript ,旨在入门数据结构与算 ...
- 排序算法的实现(归并,快排,堆排,希尔排序 O(N*log(N)))
今天跟着左老师的视频,理解了四种复杂度为 O(N*log(N))的排序算法,以前也理解过过程,今天根据实际的代码,感觉基本的算法还是很简单的,只是自己写的时候可能一些边界条件,循环控制条件把握不好. ...
- HDOJ(HDU) 1862 EXCEL排序(类对象的快排)
Problem Description Excel可以对一组纪录按任意指定列排序.现请你编写程序实现类似功能. Input 测试输入包含若干测试用例.每个测试用例的第1行包含两个整数 N (<= ...
- java排序,冒泡排序,选择排序,插入排序,快排
冒泡排序 时间复杂度:O(n^2) 空间复杂度O(1) 稳定性:稳定 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.这步做完后,最 ...
- 列表排序之NB三人组附加一个希尔排序
NB三人组之 快速排序 def partition(li, left, right): tmp = li[left] while left < right: while left < ri ...
- 题目1023:EXCEL排序(多关键字+快排+尚未解决)
http://ac.jobdu.com/problem.php?pid=1023 题目描述: Excel可以对一组纪录按任意指定列排序.现请你编写程序实现类似功能. 对每个测试用例,首先输出1行“Ca ...
- sort(排序) qsort(快排) bsearch(二分查找)
sort: 一.对int类型数组排序 int a[100]; int cmp ( int a , int b ) //不必强制转换 { return a < b;//升序排列. } sort ( ...
- 排序基础之插入排序、冒泡排序、选择排序详解与Java代码实现
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6594533.html 一:插入排序==逐个往前相邻数比较交换,小的在前 第一轮:A[1]与A[0]比较,小的 ...
- ACM/ICPC 之 快排+归并排序-记录顺序对(TSH OJ-LightHouse(灯塔))
TsingHua OJ 上不能使用<algorithm>头文件,因此需要手写快排(刚开始写的时候自己就出了很多问题....),另外本题需要在给横坐标排序后,需要记录纵坐标的顺序对的数量,因 ...
随机推荐
- Swift - EasingAnimation绘制圆环动画
Swift - EasingAnimation绘制圆环动画 效果 源码 https://github.com/YouXianMing/Swift-Animations // // CircleView ...
- 吸血鬼日记第一季/全集The Vampire Diaries迅雷下载
本季The Vampire Diaries 1 第一季(2009)看点: <吸血鬼日记>由美国女作家L.J.史密斯的同名畅销系列小说改编而成.4个月前一场可怕的车祸夺去了他们双亲的生命,但 ...
- [Android Pro] 跨平台反编译工具 jadx (ubuntu亲测 可用)
github : https://github.com/skylot/jadx 输入: .dex, .apk, .jar or .class 输出: 资源文件 和 class文件 不支持重新打包, ...
- Round() 四舍五入 js银行家算法
首先问一下round(0.825,2) 返回的结果,大家猜一猜, 首先SQL server 返回的是 0.83 js的返回结果 是0.83,code 如下: var b = 0.825; ...
- JS读取XML文件数据并以table显示数据(兼容IE火狐)
先看xml文件: <?xml version="1.0" standalone="yes"?> <student> <stuinf ...
- Context Application 使用总结 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- jquery 控制css样式
一.CSS 1.css(name) 访问第一个匹配元素的样式属性. 返回值 String 参数 name (String) : 要访问的属性名称 示例: $("p").css(&q ...
- Nginx如何启用ETag,提高访问速度
ETag全称EntityTags,HTTP协议规格说明中定义“ETag”为“被请求变量的实体值”. 我们也可以把ETag理解为是一个客户端与服务器关联的记号.这个记号告诉客户端,当前网页在上次请求之后 ...
- C++ 学习路线和看法
C++ 学习路线和看法 原文地址:http://shundacao.blog.163.com/blog/static/1340404812010101982751101/ C++大体分为C++ ...
- Android -- 获取视频第一帧缩略图
干货 从API 8开始,新增了一个类: android.media.ThumbnailUtils这个类提供了3个静态方法一个用来获取视频第一帧得到的Bitmap,2个对图片进行缩略处理. public ...