用 Java 写的几个排序算法:插入排序,选择排序,冒泡排序,快速排序(双边扫描),归并排序,堆排序。
归并算法
参考了用Java写算法之四:归并排序,
MergeSort(归并排序)算法Java实现
归并算法的主要思想是 divide and conquer。将数组从中间分开,直到数组分组中只剩一个数据,此时单个数据可以很轻松的比较大小了。比较完大小后,再两两合并。
归并效率很高,由于递归划分为子序列只需要logN复杂度,而合并每两个子序列需要大约2n次赋值,为O(n)复杂度,因此,只需要简单相乘即可得到归并排序的时间复杂度O(nlog(n))且算法固定,不受输入数据的影响。
但缺点是空间复杂度O(n),需要额外空间。所以当输入数据很大的时候不适合。
堆排序
参考这里堆与堆排序,
HEAPSORT 堆排序算法详解(JAVA实现) 这两个讲的都很清楚。
堆是一种类似于树的数据结构。 不是堆栈中的堆
二叉堆分为最大堆和最小堆。最大堆的性质是每个父元素都不小于子元素。最小堆是每个父元素不大于子元素。
利用最大堆可以完成排序。
public class Sort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int [] arr = {49,11,90,11,120,34,12,7,43,8,23};
int len = arr.length;
/*Quick quick = new Quick();
quick.quick_sort(arr, 0, len-1);*/
MergeSort ms = new MergeSort();
ms.mergeSort(arr);
//这里还隐含了 数组的是引用的值的传递。调用排序算法后,再输出数组顺序已排好
for(int i =0 ;i<len;i++){
System.out.print(arr[i]+", ");
}
}
}
//插入排序:向一个有序数列里面插入,默认第一个数是有序的
class InsertSort{
public void sort(int arr[]){
//循环里计算arr.length ,浪费时间
int n=arr.length;
//默认第一个数是有序的
for(int i=1;i<n;i++){
//准备插入的数
int insertVal=arr[i];
//insertVal准备与前一个数比较,所以index=i-1
int index=i-1;
while(index>=0&&insertVal<arr[index]){
//要插入的数比前一个数小,则indexVal的位置还没有找好,index应该-1,继续比较。
arr[index+1]=arr[index];
index--;
}
//将insertVal插入适当位置.判断插入的位置,用两个数1,2判断下即可
//整个排序也可以用两个数检验
arr[index+1]=insertVal;
}
/*for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+"; ");
}
System.out.println();*/
}
}
//选择:认为每次循环的第一个数是最小数(最大数)
class Select{
public void sort(int arr[]){
int temp=0;
for(int j=0;j<arr.length-1;j++){
//开始认为第一个数就是最小
int min=arr[j];
//记录最小数下标
int minIndex=j;
for(int k=j+1;k<arr.length;k++){
if(min>arr[k]){
min=arr[k];
minIndex=k;
}
}
//当退出该次循环时就找到了该次的最小值
temp=arr[j];
arr[j]=arr[minIndex];
arr[minIndex]=temp;
}
/*for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+"; ");
}
System.out.println();*/
}
}
//冒泡:两两相比较(刚接触的那个两层循环排序),冒泡,小的下沉,第一轮最小的在最后。
class Bubble{
public void sort(int arr[]){
int i,j,temp;
for(i=0;i<arr.length-1;i++){
for (j=0;j<arr.length-i-1;j++){
if(arr[j]>arr[j+1]){
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
/*for(i=0;i<arr.length;i++){
System.out.print(arr[i]+"; ");
}
System.out.println();*/
}
}
//快速排序:对冒泡排序的改进,前面的排序都是单进程。
//1,必须先从右边开始,然后进行左边
//2,写分治代码的时候需要加if(l<r)进行判断
//3,每个地方的交换位置
class Quick{
int AdjustArray(int s[],int l,int r){
int i=l,j=r;
int x=s[i];//第一个坑
while(i<j){
//从右向左找小于X 的数填s[i]
while(i<j&&s[j]>=x)
j--;
if(i<j){
s[i]=s[j];
}
while(i<j&&s[i]<=x)
i++;
if(i<j){
//i j 交换位置,最后 i 与key 交换位置
s[j]=s[i];
}
}
//退出时,i等于j,将 X 填到这个坑中去
s[i]=x;
return i;
}
//再写分治法的代码
void quick_sort(int s[],int l,int r){
if(l<r){
int i = AdjustArray(s, l, r);//先成挖坑填数法调整s[]
quick_sort(s, l, i - 1); // 递归调用
quick_sort(s, i + 1, r);
}
}
}
//归并排序
//divide and conquer思想
class MergeSort{
public void mergeSort(int[] arr){
//统一只创建一个临时数组
int[] temp = new int[arr.length];
internalMergeSort(arr,temp,0,arr.length-1);
}
public void internalMergeSort(int[] arr,int[] temp,int l,int r){
if(r-l<1) return;
int mid = (l+r)/2;
internalMergeSort(arr,temp,l,mid);
internalMergeSort(arr,temp,mid+1,r);
Sort(arr,temp,l,mid,r);
}
public void Sort(int [] arr,int[] temp,int l,int mid,int r){
int i = l;
int j = mid+1;
int k =l;//记录temp的数组下标,可以设置为0 ,也可以设置为 left
//将两个数组按照顺序存入 temp 数组中
while(i<=mid&&j<=r){
if(arr[i]<=arr[j]){
temp[k]=arr[i];
k++;
i++;
}else{
temp[k]=arr[j];
k++;
j++;
}
}
while(i<=mid){
temp[k++]=arr[i++];
}
while(j<=r){
temp[k++]=arr[j++];
}
//复制回原来的数组,因为是递归,所以只要将合并后的数组存数其原来的那段位置
//后面再根据递归,将有序的数组复原出来
for(int m=l;m<=r;m++){
//注意,K++ 后没有满足要求,所以temp的最后一个数下标为 K-1 所以为 m<k
arr[m]=temp[m];
System.out.print(arr[m]+" ");
}
System.out.println();
}
}
class HeapSort{
/*
* 错误输出:1, 43, 5, 32, 12, 12, 23, 71, 98,
*
* 将maxheapnify 中左右比较的if if 换成 if...else
* 依旧错误:1, 5, 23, 12, 12, 32, 43, 71, 98,
*
*
* */
//堆排序本质上是选择排序。每次选择当前无序区的最大放入有序区。步骤:
//1,保持最大堆性质MaxHeapify 2,构建最大堆 BuildMaxHeap 3,排序HeapSort(依次取出根节点)
protected int left(int i){
return (2*i+1);
}
protected int right(int i){
return(2*i+2);
}
//注意,堆越往下,下标 i 越大!!
protected int parent(int i){
return (i+1)/2-1;
}
//这里假设 i 的左右孩子都是最大堆(因为在buildheap的时候是在最大的i,即从下向上build的)
public void MaxHeapnify(int[]arr,int index,int heapSize){
int left = left(index);
int right = right(index);
//注意,堆是一个数组,表示堆的数组A 有两个属性:
//length(数组元素的个数);heap-size 存在数组中的堆元素表个数(导论)
//所以不应该用arr.length 来表示堆的大小
//int len = arr.length;
//largest 记录下标,后面再调整最大堆属性时也需要用到此下标(递归MaxHeapnify(arr, largest, heapSize);)
int largest = index;
if(left<=heapSize && arr[left]>arr[index]){
largest=left;
}
/*
* ~~(>_<)~~ 这里出错了,应该是比较parent与left哪个大
* parent变成max(parent ,left),再将parent与right比较
* parent 变成max(parent ,left,right)
*
* */
//!!right<=heapSize 递归出口!! 当不满足时,递归结束、
if(right<=heapSize && arr[right]>arr[largest]){
largest=right;
}
if(largest!=index){
//交换
int temp = arr[largest];
arr[largest]=arr[index];
arr[index]=temp;
//如果index位置没有交换,则说明index已经找到自己的位置。
//假设中左右是最大堆,则index为根的树符合了最大堆要求。
//否则需要继续对该index进程最大堆化,直到largest超出了堆的大小!!
MaxHeapnify(arr, largest, heapSize);
}
}
//构造最大堆。由于前面的假设,左右子树均为最大堆,则需要由下向上构造
public void BuildMaxHeap(int [] arr,int heapSize){
//for(int i=heapSize-1;i>=0;i--){ 注意这里不是在heapSize
//处开始的,因为叶节点没有左右节点!!!
for(int i=parent(heapSize);i>=0;i--){
MaxHeapnify(arr, i, heapSize);
}
}
public void heapSort(int [] arr){
int len = arr.length;
int heapSize = len-1;
BuildMaxHeap(arr, heapSize);
for(int j =0 ;j<len;j++){
System.out.print(arr[j]+", ");
}
System.out.println();
//取出堆中的元素。
for(int i=heapSize;i>=1;--i){
int temp = arr[0];
arr[0] = arr[i];
arr[i]=temp;
heapSize--;
for(int j =0 ;j<len;j++){
System.out.print(arr[j]+", ");
}
System.out.println("Step: ");
MaxHeapnify(arr, 0, heapSize);
}
}
}
关于堆排序:
堆可以视为一颗完全二叉树
假设一个数组{3,7,2,11,3,4,9,2,18,0}
与堆的关系:
构建最大堆的步骤:
- 首先将堆中元素进行MAX-Heapnify 来构建最大堆
- 将堆首元素依次取出,排序完成
这里对元素MAX-Heapnify 时,是从第一个有左右子树的节点开始的。即上图中第4个节点开始,依次向上4,3,2,1,0节点进行MAX-Heapnify 。
图中从左向右: