本文是是时间复杂度为O(nlogn)的排序算法
堆排序
思路:(以最大堆举例)
将数组array变成最大堆,array[0]则为数组中的最大数,调换array[0]和array[i](i为未排序的数组的末尾)。然后逐步减小i,即array[0]到array[i]为未排序的数组。先确定数组的末尾。
public class Solution{
public void dui(int[] arr)
{
if(arr==null || arr.length==0) return;
//构建最大堆
for(int i=(arr.length-2)/2;i>=0;i--)
f(arr,i,arr.length-1);
//交换堆顶和未排序的最后的元素,并将未排序的重新构建最大堆
for(int i=arr.length-1;i>0;i--)
{
swap(arr,0,i);
f(arr,0,i-1);
}
}
public void f(int[] arr,int i,int j)
{
//k默认指向左孩子
if(j<=i) return;
int temp = arr[i];
int k = 0;
//向孩子遍历,默认指向左孩子
for(k=i*2+1;k<=j;k=k*2+1)
{
//让k指向大的孩子
if((k+1<=j) && (arr[k]<arr[k+1]))
k = k+1;
//如果temp比子节点都大,就结束调整
if(arr[k]<=temp)
break;
//否则让父节点的值等于大的子节点的值
arr[(k-1)/2] = arr[k];
}
arr[(k-1)/2] = temp;
}
public void swap(int[] arr,int i ,int j)
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
堆详解:
堆的构建的时间复杂度为O(n),因为假设有n个节点,从底向上,高度为logn,最底层最多挪动1次,倒数第2层最多挪动2次,,,最后加起来为o(n)
插入、删除的时间复杂度为o(logn)。
整体时间复杂度为O(nlogn),因为函数f的时间复杂度是logn。
归并排序
1、递归实现
整体思路:
f(array,start,end)是对array[start,end)进行从小到大的排序
f(array,start,end):
对前一半数组排序
对后一半数组排序
将前后排好序的数组再整体排序
//归并排序
public class Solution{
public void guibing(int[] arr)
{
if(arr==null || arr.length<2) return;
int[] copy = new int[arr.length];
f(arr,0,arr.length-1,copy);
}
public void f(int[] arr,int s,int e,int[] copy)
{
if(e<=s) return;
int mid = (s+e)/2;
f(arr,s,mid,copy);
f(arr,mid+1,e,copy);
int p1 = s;
int p2 = mid+1;
for(int i=s;i<e+1;i++)
{
if((p1<=mid)&&(p2<=e))
{
if(arr[p1]<arr[p2])
copy[i]=arr[p1++];
else
copy[i] =arr[p2++];
}
else if(p1==mid+1)
copy[i] = arr[p2++];
else
copy[i] = arr[p1++];
}
for(int i=s;i<e+1;i++)
arr[i] = copy[i];
}
}
2、非递归实现
public class Solution{
public void guibing(int[] arr)
{
if(arr==null || arr.length<1) return;
int j = 0;
//j为间隔
//[i,i+j-1],[i+j,i+2*j-1]内部为已经排序好的,需要将两者合并。
for(j=1;j<arr.length;j=j*2)
{
for(int i=0;i<arr.length;i=i+2*j)
{
if(i+j >= arr.length)
continue;
else if(i+2*j>arr.length)
{
merge(arr,i,arr.length-1,j);
}
else
{
merge(arr,i,i+2*j-1,j);
}
}
}
}
public void merge(int[] arr,int s,int e,int j)
{
int[] copy = new int[e-s+1];
int p1 = s;
int p2 = s+j;
for(int i=0;i<copy.length;i++)
{
if(p1>s+j-1)
copy[i] = arr[p2++];
else if(p2>e)
copy[i] = arr[p1++];
else if(arr[p1]<arr[p2])
copy[i] = arr[p1++];
else
copy[i] = arr[p2++];
}
for(int i=0;i<copy.length;i++)
arr[i+s] = copy[i];
}
}
快速排序
基本思路:
选取数组中的一个数作为关键字,将数组初步排序成关键字左边的数都比关键字小,关键字右边的数都比关键字大,再对关键字的左右数组继续排序
快速排序基本算法
public class Solution{
public void kuaisu(int[] arr)
{
if(arr==null || arr.length==0) return;
sortcore(arr,0,arr.length-1);
}
public void sortcore(int[] arr,int low,int high)
{
int key = 0;
if(high<=low) return;
//找出关键字的索引位置key,key左边的都比arr[key]小,右边的都比arr[key]大
key = partition(arr,low,high);
//对左边的数组排序
sortcore(arr,low,key-1);
//对右边的数组排序
sortcore(arr,key+1,high);
}
public int partition(int[] arr,int low,int high)
{
//此处选取第一个数为关键字
//一定要先记录下arr[key]=value,否则交换时会改变值
int key = low;
int value = arr[key];
//交换令high右边的数都比value大,low左边的数都比value小
//value在high和low索引间互相交换直到两者相等
while(high>low)
{
while(high>low && arr[high]>=value)
high--;
swap(arr,low,high);
while(high>low && arr[low]<=value)
low++;
swap(arr,low,high);
}
return low;
}
public void swap(int[] arr,int p1,int p2)
{
int temp = arr[p1];
arr[p1] = arr[p2];
arr[p2] = temp;
}
}
非递归实现:
//用栈存储每次的low和high
public void kuaisu(int[] arr)
{
if(arr==null || arr.length==0) return;
ArrayDeque<Integer> stack = new ArrayDeque();
stack.push(arr.length-1);
stack.push(0);
int start = 0;
int end = 0;
int key = 0;
while(!stack.isEmpty())
{
start = stack.pop();
end = stack.pop();
if(end>start){
key = partition(arr,start,end);
stack.push(key-1);
stack.push(start);
stack.push(end);
stack.push(key+1);
}
}
}
快速排序的改进
见大话数据结构p424
1、对关键字的选择,如果第一个数离中位数偏差很远,运行效率就很低。对关键数的选择可以选择数组,start.mid,end中位于中间的数。更大的数可以选择九数选中
2、不用交换而用赋值来初步排序数组
3、在排序的过程中,对于length小的数组,直接用简单排序来排,选取简单排序中的直接插入排序的方法
4、将对右子数组进行递归变成迭代,缩减堆栈深度