时间复杂度为:O(N*logN)的排序算法
注:图来自于网络
归并排序(Merge Sort)
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and
Conquer)的一个非常典型的应用。 作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);自下而上的迭代;
算法步骤
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列; 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置; 重复步骤 3 直到某一指针达到序列尾;
将另一序列剩下的所有元素直接复制到合并序列尾。
/**
* 归并排序是建立在归并操作上的一种有效的排序算法。
* 该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
* 将已有序的子序列合并,得到完全有序的序列;
* 即先使每个子序列有序,再使子序列段间有序。
* 若将两个有序表合并成一个有序表,称为2-路归并
* @param nums
* @return
*/
public static int[] mergeSort(int [] nums){
if(nums.length<2|| nums==null)return nums;
process(nums,0,nums.length-1);
return nums;
}
/**
* 递归过程
* @param arr
* @param L
* @param R
*/
private static void process(int[] arr ,int L,int R){
if(L==R){
return;
}
int mid=L+((R-L)>>1); //求他们的中间值
process(arr,L,mid);
process(arr,mid+1,R);
merge(arr,L,mid,R);
}
/**
* merge过程
* @param arr
* @param l
* @param m
* @param r
*/
private static void merge(int[] arr, int l, int m, int r) {
int[] help=new int[r-l+1];
int i=0;
int p1=l;
int p2=m+1;
while (p1<=m&&p2<=r){
help[i++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
}
while (p1<=m){
help[i++]=arr[p1++];
}
while (p2<=r){
help[i++]=arr[p2++];
}
for (int j = 0; j < help.length; j++) {
arr[l+j]=help[j];
}
}
递归的应用
问题描述:求在数组arr[L…R]上的最大值(核心算法)
/**
* 递归应用
* 求在数组arr[L...R]上的最大值
* 1>将[L ... R]分成左右两半。左[L ... Mid] 右[Mid+1 ... R]
* 2>左部分求最大值,有部分求最大值
* 3>[L ... R]范围上的最大值,是max(左部分求最大值,有部分求最大值)
* 注意:2>是个递归过程,当范围上只有一个数,就可以不用再递归了
* @param arr 该数组
* @param L 起始位置
* @param R 结束位置
* @return
*/
public static int processMax(int[] arr ,int L,int R){
if(L==R){
return arr[L];
}
int mid=L+((R-L)>>1); //求他们的中间值
int leftMax=processMax(arr,L,mid);
int rightMax=processMax(arr,mid+1,R);
return Math.max(leftMax,rightMax);
}
快速排序(Quick Sort)
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。
在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn)
算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治法(Divide and conquer)
策略来把一个串行(list)分为两个子串行(sub-lists)
快速排序又是一种分而治之思想在排序算法上的典型应用。
本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法
算法步骤
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
/**
* 快速排序
* 快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。
* 在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn)
* 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
* 快速排序使用分治法(Divide and conquer)
* 策略来把一个串行(list)分为两个子串行(sub-lists)
* 快速排序又是一种分而治之思想在排序算法上的典型应用。
* 本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法
* @param nums
* @return
*/
public static int[] quickSort(int [] nums){
return quickSort(nums, 0, nums.length - 1);
}
/**
*
* @param arr
* @param left 起始位置
* @param right 结束位置
* @return
*/
private static int[] quickSort(int[] arr, int left, int right) {
if (left < right) {
int partitionIndex = partition(arr, left, right);
quickSort(arr, left, partitionIndex - 1);
quickSort(arr, partitionIndex + 1, right);
}
return arr;
}
private static int partition(int[] arr, int left, int right) {
// 设定基准值(pivot)
int pivot = left;
int index = pivot + 1;
for (int i = index; i <= right; i++) {
if (arr[i] < arr[pivot]) {
swap(arr, i, index);
index++;
}
}
swap(arr, pivot, index - 1);
return index - 1;
}
堆排序(Heap Sort)
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列; 堆排序的平均时间复杂度为 Ο(nlogn)。
算法步骤
创建一个堆 H[0……n-1];
把堆首(最大值)和堆尾互换;
把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
重复步骤 2,直到堆的尺寸为 1。
/**
* 堆排序
* 堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。
* 堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:
* 即子结点的键值或索引总是小于(或者大于)它的父节点。
* 堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
* 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
* 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
* 堆排序的平均时间复杂度为 Ο(nlogn)。
* @param nums
* @return
*/
public static int[] heapSort(int [] nums){
if(nums==null||nums.length<2)return nums;
for (int i = 0; i < nums.length; i++) { //o(n)
heapInsert(nums,i); //o(log(n)))
}
int heapSize=nums.length;
swap(nums,0,--heapSize);
while (heapSize>0){
heapIfy(nums,0,heapSize);
swap(nums,0,--heapSize);
}
return nums;
}
/**
* 把一个处于index位置上的数,往上继续移动
* 典型:大根堆
* @param arr
* @param index
*/
private static void heapInsert(int[] arr, int index) {
/**
* (index-1)/2:它父位置下标
*/
while (arr[index]>arr[(index-1)/2]){
swap(arr,index,(index-1)/2);
index=(index-1)/2;
}
}
/**
*
* @param arr
* @param i 从那个位置开始往下
* @param len 通过这个来约束左右孩子,防止越界
*/
private static void heapIfy(int[] arr, int i, int len) {
int left = 2 * i + 1; //左孩子下标
int right = 2 * i + 2;//右孩子下标
//写法一
// int largest = i;
// if (left < len && arr[left] > arr[largest]) {
// largest = left;
// }
// if (right < len && arr[right] > arr[largest]) {
// largest = right;
// }
// if (largest != i) {
// swap(arr, i, largest);
// heapIfy(arr, largest, len);
// }
//写法二
while (left<len){ //下方还有孩子的时候
//两孩子谁的值大,把下标给largest
int largest=right<len&&arr[right]>arr[left]?right:left;
//父和孩子谁的值大,把下标给largest
largest=arr[largest]>arr[i]?largest:i;
if(largest==i){
break;
}
swap(arr,largest,i);
i=largest;
left=i*2+1;
}
}
swap
补充:上篇文章中,swap方法这块是存在一个陷阱的。
当i==j时,就行相当与一样的数在疑惑,异或的结果为0
public static void swap(int[] arr,int i,int j){
/**
* 陷阱:当i==j时,就行相当与一样的数在疑惑,异或的结果为0
* eg:当i=2;j=2;
* i=i^j=2^2=0
*/
if(i==j)return; //用于判断i和j的判断
arr[i]=arr[i]^arr[j];
arr[j]=arr[i]^arr[j];
arr[i]=arr[i]^arr[j];
}