排序基础之归并排序、快排、堆排序、希尔排序思路讲解与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>头文件,因此需要手写快排(刚开始写的时候自己就出了很多问题....),另外本题需要在给横坐标排序后,需要记录纵坐标的顺序对的数量,因 ...
随机推荐
- 浴血黑帮第三季/全集Peaky Blinders迅雷下载
英文译名 Peaky Blinders (第2季) (2014-8月回归)BBC.本季看点:<浴血黑帮>由<东方的承诺>.<奇异的恩典>编剧斯蒂文·奈特打造,讲述了 ...
- Activity间用Intent、Bundle、onActivityResult进行传值
其实Activity间的传值就是通过Bundle,intent中也是自动生成了Bundle来传值,里面还有个onActivityResult()方法也可以传送数值. 如果一个Activity是由sta ...
- Svg.js 图片加载
一.SVG.Image 1.创建和修改图片 var draw = SVG('svg1').size(300, 300); //SVG.Image 加载图片文件 var image = draw.ima ...
- 服务器配置多版本CUDA、CUdnn(不同Linux账户使用不同CUDA、CUdnn版本)
一.由于实验室大家使用的CUDA.CUdnn不同,所以需要在同一台服务器安装多个版本,而且要不引起冲突,方法如下: 1.一般来说CUDA安装在 /usr/local 目录下(当然你可以通过“echo ...
- Java调用DLL有多种方式,常用的方式有JNative、JNA、JNI等。
JNative方式调用dll JNative是一种能够使Java语言使调用DLL的一种技术,对JNI进行了封装,可能有些读者会有这样一个问题,JNative对JNI进行了封装,并且是一种跨语言的使用D ...
- 【ContestHunter】【弱省胡策】【Round6】
KMP/DP+树链剖分+线段树/暴力 今天考的真是……大起大落…… String QwQ题意理解又出错了……(还是说一开始理解了,后来自己又忘了为什么是这样了?) 反正最后的结果就是……我当成:后面每 ...
- 细说SQL Server中的加密【转】
简介 加密是指通过使用密钥或密码对数据进行模糊处理的过程.在SQL Server中,加密并不能替代其他的安全设置,比如防止未被授权的人访问数据库或是数据库实例所在的Windows系统,甚至是数据库所在 ...
- asp.net 读取word 文档的方法
资料一:适合读取并显示(简单而明了) 第一种方法: Response.ClearContent(); Response.ClearHeaders(); Response.ContentTyp ...
- 从 SVM 到多核学习 MKL
SVM是机器学习里面最强大最好用的工具之一,它试图在特征空间里寻找一个超平面,以最小的错分率把正负样本分开.它的强大之处还在于,当样本在原特征空间中线性不可分,即找不到一个足够好的超平面时,可以利用核 ...
- 七牛云存储 qiniu 域名 回收 文件上传 备份 下载 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...