之前学习C语言时学过各类基础排序,现在用Java来再回顾一遍。这一篇主要是写一下规范的代码。
一、排序相关的基本概念
排序其实是一个相当大的概念,主要分为两类:内部排序和外部排序。而我们通常所说的各种排序算法其实指的是内部排序算法。内部排序是基于内存的,整个排序过程都是在内存中完成的,而外部排序指的是由于数据量太大,内存不能完全容纳,排序的时候需要借助外存才能完成(常常是某一部分已经计算过的数据移出内存让另一部分未被计算的数据进入内存)。而本篇文章将主要介绍内部排序中的几种常用排序算法:
为了测试方便,我定义了生成随机数组的Const类和打印输出的print函数。Const类如下:
public class Const {
public int a[] = new int[50];
public Const(){
Random random = new Random();
for( int i=0; i<a.length; i++ ){
a[i] = random.nextInt(100);
}
}
}
二、插入类排序
1.直接插入
插入类排序算法的核心思想是,在一个有序的集合中,我们将当前值插入到适合位置上,使得插入结束之后整个集合依然是有序的。代码如下:
/**
* 插入排序
* @param array
*/
public static void InsertSort(int[] array){
int i, j, key;
for( i=1; i<array.length; i++ ){
key = array[i];
j = i - 1;
//取当前位置值,与已经排序好的那一部分由大到小进行比较,将其挪到正确的位置
while ( j>=0 && key<array[j] ){
array[j+1] = array[j];
j--;
}
array[j+1] = key;//在正确的位置插入key值
}
print(array);
}
2、二分插入
原理是二分查找到合适位置再进行插入。
/**
* 二分插入排序
* @param array
*/
public static void halfInsertSort(int[] array){
for( int i=1; i<array.length; i++ ){
int mid, low, high, key;
low = 0; high = i-1;
key = array[i];
//这个循环之后,要插入的地方为low
while ( low<=high ){
mid = (low+high)/2;
if( key==array[mid] ){
low = mid;
break;
}else if( key>array[mid] ){
low = mid + 1;
}else{
high = mid - 1;
}
}
//将已排序部分low之后的都向后移动一位
for( int k=i-1; k>=low; k-- ){
array[k+1] = array[k];
}
array[low] = key;
}
print(array);
}
3、希尔排序
希尔排序算法使用一个距离增量来切分子序列,使每个子序列都有序。当距离增量变小的时候,序列的个数也会变少,但是这些子序列的内部都基本有序,当对他们进行直接插入排序的时候会使得效率变高。一旦距离增量减少为1,那么子序列的个数也将减少为1,也就是我们的原序列,而此时的序列内部基本有序,最后执行一次直接插入排序完成整个排序操作。
/**
* 希尔排序
* @param array
*/
public static void shellSort(int[] array){
int len = array.length;
int step = len/2;//步长
while( step>0 ){
for( int i=step; i<len; i++ ){
int j = i;
//while循环 --对由步长分成的各数组进行直接插入排序
while ( j>=step && array[j]<array[j-step] ){
int temp = array[j];
array[j] = array[j-step];
array[j-step] = temp;
j -= step;
}
}
step /= 2;
}
print(array);
}
三、交换类排序
1、冒泡排序
冒泡排序通过两两比较,每次将最大或者最小的元素移动到整个序列的一端。
/**
* 冒泡排序
* @param array
*/
public static void bubbleSort(int[] array){
int temp = 0;
//每次将最大的放到最后,需要进行length-1次
for( int i=0; i<array.length-1; i++ ){
for( int j=1; j<array.length-1-i; j++ ){
if( array[j]<array[j-1] ){
temp = array[j];
array[j] = array[j-1];
array[j-1] = temp;
}
}
}
print(array);
}
2、快速排序
快速排序的基本思想是,从序列中任选一个元素,但通常会直接选择序列的第一个元素作为一个标准,所有比该元素值小的元素全部移动到他的左边,比他大的都移动到他的右边。该排序算法是目前为止,内部排序中效率最高的排序算法。
tips:
1、n越大排序效率越高。快速排序比较占用内存,内存随n的增大而增大,是效率高但不稳定的排序算法。
2、最坏情况:划分一次之后一边是1个,一边是n-1个,每次只排好一个元素,即整体 asc/desc。这种极端情况的时间复杂度就是O(N^2),退化到了冒泡排序。
3、最好情况:是每次都能均匀的划分序列,为O(N*log2N)
4、空间复杂度:每一次排都只需要O(1)的辅助存储,但是每次递归都会保持一些数据,所以根据情况是O(log2n)~O(n)的空间复杂度。
/**
* 快速排序
* @param array
*/
public static void quickSort(int[] array){
int low = 0;
int high = array.length-1;
qSort(array, low, high);
print(array);
}
private static void qSort(int[] array, int low, int high){
if( low<high ){
int pos = oneQuickSort(array, low, high);
qSort(array, low, pos-1);
qSort(array, pos+1, high);
}
}
private static int oneQuickSort(int[] array, int low, int high){
int key = array[low];
while ( low<high ){
while ( low<high && array[high]>=key ){
high--;
}
array[low] = array[high];
while ( low<high && array[low]<=key ){
low++;
}
array[high] = array[low];
}
array[low] = key;
return low;
}
四、选择类排序
选择类排序的基本思想是,每一趟会在n个元素中比较n-1次,选择出最大或者最小的一个元素放在整个序列的端点处。
1.直接选择排序
/**
* 直接选择排序
* @param array
*/
public static void selectionSort(int[] array){
for( int i=0; i<array.length; i++ ){
int minIdx = i; //记录最小值的位置
for( int j=i+1; j<array.length; j++ ){
if( array[j]<array[minIdx] ){
minIdx = j;
}
}
if( minIdx!=i ){
int temp = array[i];
array[i] = array[minIdx];
array[minIdx] = temp;
}
}
print(array);
}
2. 堆排序
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。
堆
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
代码如下:
/**
* 堆排序
* @param array
*/
public static void heapSort(int[] array){
int len = array.length;
//建大顶堆,注意len/2-1的位置是从下往上第一个非叶子节点
for( int i=len/2-1; i>=0; i-- ){
adjustHeap(array, i, len-1);
}
//交换堆顶元素和末尾元素,完了之后使剩下的元素仍是一个大顶堆
for( int i=len-1; i>=0; i-- ){
swap(array, 0, i);
adjustHeap(array, 0, i);
}
print(array);
}
private static void swap(int[] array, int a, int b){
int temp = array[a];
array[a] = array[b];
array[b] = temp;
}
//让i节点和它的子树满足大顶堆,即让i的子节点都小于等于i节点
private static void adjustHeap(int[] array, int i, int size){
int data = array[i];
for( int k=2*i+1; k<=size; k=k*2+1 ){
if( k+1<=size && array[k]<array[k+1] ){
k += 1;
}
//在子节点中找到比父节点大的,就让这个大的覆盖父节点,不用交换。
//之所以没找到就直接break,不再往下一层找,是因为无论是建堆,还是交换堆顶元素和末尾元素,
//都是从自下而上,从右往左的。
//等于说是大顶堆是从叶子节点开始建立子树的大顶堆,然后逐渐扩展为整个树的大顶堆。
if( array[k]>data ){
//此处直接覆盖原值,不用交换。只用记录i到了哪里,出循环再赋上初值即可。
array[i] = array[k];
i = k;
} else {
break;
}
}
array[i] = data;
}
五、归并类排序算法
算法思路:
该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
1、把长度为n的输入序列分成两个长度为n/2的子序列;
2、对这两个子序列分别采用归并排序;
3、将两个排序好的子序列合并成一个最终的排序序列。
时间:O(nlogn)
空间:O(n)
占空间,但是效率高,且稳定
/**
* 归并排序
* @param array
*/
public static void mergeSort(int[] array){
int len = array.length;
int[] arrayTemp = new int[len];
mSort(array, 0, len-1, arrayTemp);
print(array);
}
private static void mSort(int[] array, int low, int high, int[] arrayTemp){
if( low < high ) {
int mid = low + (high-low)/2; //分界
mSort(array, low, mid, arrayTemp);
mSort(array, mid+1, high, arrayTemp);
mergeTwoArray(array, low, mid, high, arrayTemp);
}
}
private static void mergeTwoArray(int[] array, int low, int mid, int high, int[] arrayTemp){
int i=low, j=mid+1;
int current = 0;
while ( i<=mid && j<=high ){
//每次比较两数组的第一个(最小的),取两个中最小的接在arrayTemp后,退出条件是某一数组到末尾
if( array[i]<array[j] ){
arrayTemp[current++] = array[i++];
}else{
arrayTemp[current++] = array[j++];
}
}
//若右边已至末尾,则将左边的全部接在arrayTemp后
while( i<=mid ){
arrayTemp[current++] = array[i++];
}
//若左边已至末尾,...
while ( j<=high ){
arrayTemp[current++] = array[j++];
}
System.arraycopy(arrayTemp,0,array,low,current);
}
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/19380.html