经典排序算法
上篇文章介绍了经典排序算法中时间复杂度为O(n²)的算法,这篇文章将要继续介绍时间复杂度为O(n*㏒n)的算法
一、归并排序
原理:
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
思路:
例如现在有一个长度为n的数组,将数组元素从小到大排序,可以将数组看成区间长度为1的有序区间,然后把相邻的有序区间合并得到长度为2的有序区间,然后将相邻的有序区间合并得到长度为4的有序区间。实际上采用了分治法的思想。
代码举例:
import java.util.*;
public class MergeSort {
public void marge(int[] A,int left,int mid,int right){
//设置区间长度
int n=right-left+1;
//创建一个临时数组在合并是存放结果
int[] arr=new int[n];
//左边数组的游标
int l=left;
//右边数组的游标
int r=mid+1;
//临时数组的游标
int index=0;
while(l<=mid && r<=right){
//在左右数组中依次比较元素,选择小的放入临时数组
if(A[l]<=A[r]){
arr[index++]=A[l++];
}
else
arr[index++]=A[r++];
}
//因为每次比较的都是有序数组,所以接下来可以直接放入
while(l<=mid){
arr[index++]=A[l++];
}
while(r<=right){
arr[index++]=A[r++];
}
//将临时数组中的值放入原来的数组
for(int i=0;i<index;i++){
A[left+i]=arr[i];
}
}
//主函数
public int[] mergeSort(int[] A, int n) {
// write code here
process(A,0,n-1);
return A;
}
//分治法思想,首先将数组分解成长度为1的有序区间
public void process(int[] A,int left,int right){
if(left==right){
return;
}
//递归拆分
int mid=(left+right)/2;
process(A,left,mid);
process(A,mid+1,right);
//排序加合并
marge(A,left,mid,right);
}
}
二、快速排序
原理:
在数组中随机选择一个元素,将所有比这个元素小的元素放在它的左边,比它大的元素放在右边,然后分别对左边和右边的元素进行递归。
思路:
例如现在要将长度为n的数组从小到大排序,那么我们可以在数组中随机选择一个划分值,然后将这个划分值放到数组的最后,在数组的前面设置一个小于等于区间,一次遍历数组,如果当前元素比划分值大,则继续遍历,如果当前元素小于划分值则将当前元素与小于等于区间的下一个元素互换,同时将小于等于区间的长度加一,直到遍历到划分值时,将划分值与小于等于区间的下一个元素互换,则完成了第一次排序。以此类推递归。
代码举例:
import java.util.*;
public class QuickSort {
//主函数
public int[] quickSort(int[] A, int n) {
// write code here
quick(A,0,n-1);
return A;
}
//递归排序
private static int[] quick(int[] A, int low, int high) {
// TODO Auto-generated method stub
if (low < high) {
int mid = sort(A, low, high);
quick(A, low, mid-1);
quick(A, mid + 1, high);
}
return A;
}
//排序的具体实现
private static int sort(int[] A, int low, int high) {
// TODO Auto-generated method stub
//随机选择一个划分值
int key = A[low];
int i = low;
int j = high;
if (low < high) {
while (i < j) {
//从最后元素开始比较,如果比划分值元素大,则继续遍历
while (i < j && key <= A[j]) {
j--;
}
//否则将当前元素写入小于区间
if (i < j) {
A[i] = A[j];
}
//从前往后比较,如果当前元素比key小则保留
while (i < j && A[i] <= key) {
i++;
}
//否则将该元素放到key的右边
if (i < j) {
A[j] = A[i];
}
}
//经过上面的操作,最后在i和j重合时结束将key的值放进数组
A[i] = key;
}
return i;
}
}
三、堆排序
原理:
将长度为n的数组创建为一个大小为n的大根堆,这样堆顶元素就成为了最大数,然后将它和堆的最后元素互换,脱离堆结构,作为数组的最大元素储存起来,然后调整大小为n-1的堆为大根堆,依次之行一操作。
代码举例:
import java.util.*;
public class HeapSort {
public int[] heapSort(int[] A, int n) {
// write code here
BUILD_MAX_HEAP(A);
SORT_HEAP(A);
return A;
}
/**
* 堆排序
* @param A
*/
private void SORT_HEAP(int[] A) {
for (int i = A.length-1;i >=0;i--) {
swap(A, 0, i); // 将堆尾和堆顶交换
// 0~length(A)-1 的堆,重新建堆
MAX_HEAPIFY(A, 0, i-1);
}
}
/**
* 建堆
* @param A
*/
private void BUILD_MAX_HEAP(int[] A) {
for (int i = A.length/2-1;i >= 0;i--) {
MAX_HEAPIFY(A, i, A.length-1);
}
}
/**
* 保持堆的性质
* @param A
* @param i
* @param end
*/
private void MAX_HEAPIFY(int[] A, int i, int end) {
int l = 2*i+1;
int r = 2*i+2;
int largest;
if (l <= end && A[l] > A[i]) {
largest = l;
}else {
largest = i;
}
if (r <= end && A[r] > A[largest]) {
largest = r;
}
if (largest != i) {
swap(A, i, largest);
MAX_HEAPIFY(A, largest, end);
}
}
private void swap(int[] A, int a, int b) {
int temp = A[a];
A[a] = A[b];
A[b] = temp;
}
}
四、希尔排序
原理:
希尔排序的原理类似于经典的插入排序,但是插入排序每次是和前面的一个数比较,步长为1,而希尔排序的步长不仅仅为1.
思路:
例如使用希尔排序排序长度为n的数组,将其从小到大排列,那么我么你可以设置步长为3,那么我们首先从位置为3的元素开始比较(因为位置小于三,和三个位置前的元素比较会越界),如果该元素比它前面三个位置的元素小,则交换位置,否则向后遍历;当指针移动到第六个位置或者以后时,就不仅仅比较一次。首先跟自己前三个位置的元素比较,如果小于,则互换,然后继续跟再往前的三个数进行比较。当最后一个元素完成最终的交换时,一次排序结束,此时将步长减一,继续一轮排序。最终步长会减少到1,也就是插入排序。
代码举例:
import java.util.*;
public class ShellSort {
public int[] shellSort(int[] A, int n) {
// write code here
if(A==null ||n<2){
return A;
}
//设置步长
int feet=n/2;
while(feet>0){
for(int i=feet;i<n;i++){
while(i>=feet){
if(A[i-feet]>A[i]){
swap(A,i-feet,i);
i=i-feet;
}else{
break;
}
}
}
//缩短步长
feet=feet/2;
}
return A;
}
public void swap(int[] A,int n,int m){
int temp=A[n];
A[n]=A[m];
A[m]=temp;
}
}