常见排序算法的实现
(1)直接插入排序-insertSort
算法思想简介:
直接插入排序是一种简单的插入排序法,其基本思想是:
当插入第i(i>=1)个元素时,前面的array[0],array[1]...array[i-1]已经排好序,此时用array[i]与前面已排好序的数据进行比较,找到合适位置将其插入,原数据及以后数据顺序后移。
算法特性:
当待排序序列越趋近于有序时,排序速度越快!
代码实现:
/**
* 直接插入排序
时间复杂度:
最坏的情况下:O(N^2)
最好的情况下:O(N)--》有序情况下
空间复杂度:
O(1)
稳定性:
稳定的*/
public static void insertSort(int[] array) {
for(int i = 1; i < array.length; i++) {
int j = i-1;
int tmp = array[i];
while(j>=0) {
if(array[j]>tmp) {
array[j+1] = array[j];
} else {
break;
}
j--;
}
array[j+1] = tmp;
}
}
直接插入排序基本思想:
(2)希尔排序-ShellSort
算法思想简介:
希尔排序是直接插入排序的一种优化,其在排序前通过多次分组预排序使得数据趋于有序,最后一轮对整组数据进行直接插入排序,由于此时数据趋于有序,故速度大大提升。
希尔排序又称之为缩小增量法。
希尔排序法的基本思想是:先选定一个整数gap用于表示同一组中元素之间的间距,对按照距离分的每一组分别进行排序。重复前文操作,gap不断变小,最后为1,当n为1时即是对整组数据进行排序
算法特性:
希尔排序会随着gap每次分组的不同而影响其时间复杂度
代码实现:
/**
* 希尔排序
* 时间复杂度:
* 按照此种gap函数,时间复杂度介于O(N^1.3)~O(N^1.6)之中
* 空间复杂度:
* O(1)
* 稳定性:
* 不稳定*/
public static void shellSort(int[] array) {
int gap = array.length;
while(gap>1) {
gap/=2;
shell(array,gap);
}
}
private static void shell(int[] array, int gap) {
for(int i = 1; i < array.length; i+=gap) {
int j = i-gap;
int tmp = array[i];
while(j>=0) {
if(array[j]>tmp) {
array[j+gap] = array[j];
} else {
break;
}
j-=gap;
}
array[j+gap] = tmp;
}
}
希尔排序基本思想:
(3)直接选择排序-selectSort
算法思想简介:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完
算法特性:
每次循环找出待排序序列中的最大/最小值进行插入,如此一来时间复杂度较高,故不常用
代码实现:
/**
* 直接选择排序
* 时间复杂度:
* O(N^2)
* 空间复杂度:
* O(1)
* 稳定性:
* 不稳定 */
public static void selectSort(int[] array) {
int left = 0;
int right = array.length-1;
while(left<right) {
int maxValue = left;
int minValue = left;
for(int i = left; i <= right; i++) {
if(array[i]<array[minValue]) {
minValue = i;
}
if(array[i]>array[maxValue]) {
maxValue = i;
}
}
swap(array,left,minValue);
if(left==maxValue) {
maxValue = minValue;
}
swap(array,right,maxValue);
left++;
right--;
}
}
直接选择排序基本思想:
(4)堆排序-heapSort
算法思想简介:
堆排序是利用堆积树这种数据结构所设计的一种排序算法,它是选择排序的一种,其通过堆来进行选择数据。需要注意的是,排升序建大根堆,排降序建立小根堆。
代码实现:
/**
* 堆排序
* 时间复杂度:
* O(N*logN)
* 空间复杂度:
* O(1)
* 稳定性:
* 不稳定
* */
public static void heapSort(int[] array) {
createHeap(array);
for(int end = array.length-1; end>=0 ;end--) {
swap(array,0,end);
siftDown(array,0,end);
}
}
//建堆
private static void createHeap(int[] array) {
int parent = (array.length-1-1)/2;
while(parent>=0) {
siftDown(array,parent,array.length);
parent--;
}
}
//向下调整
private static void siftDown(int[] array, int parent,int len) {
int child = parent*2+1;
while(child<len) {
if((child+1)<len && array[child+1]>array[child]) {
child++;
}
if(array[parent]<array[child]) {
swap(array,child,parent);
parent = child;
child = parent*2+1;
} else {
break;
}
}
}
堆排序基本思想:
(5)冒泡排序-bubbleSort
算法思想简介:
每次循环将待排序序列中的最大值放置在待排序序列最后,此时最后一个元素有序,下次循环则不对该元素进行排序。以此类推,直至整个序列有序。
代码实现:
/**
* 冒泡排序
* 时间复杂度:
* O(N^2)
* 空间复杂度:
* O(1)
* 稳定性:
* 稳定*/
public static void bubbleSort(int[] array) {
for(int i = 0; i < array.length-1; i++) {
for(int j = 0; j < array.length-1-i; j++) {
if(array[j]>array[j+1]) {
swap(array,j,j+1);
}
}
}
}
冒泡排序基本思想:
(6)快速排序-quickSort
算法思想简介:
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序序列中的某个元素作为基准值,按照该值将待排序序列分割为两个子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后左右子序列重复该过程,直到所有元素都排列在对应位置为止
代码实现-递归:
/**
* 快速排序-递归实现
* 时间复杂度:
* 最好的情况下 O(N*logN) 基准值每次都将待排序序列均等分成两份
* 最坏的情况下 O(N^2) 顺序 或 逆序
* 空间复杂度:
* 最好的情况下 O(logN)
* 最坏的情况下 O(N)
* 稳定性:
* 不稳定
* */
public static void quickSort(int[] array) {
quick(array,0,array.length-1);
}
//为了保持接口的统一性,quick方法用于递归
private static void quick(int[] array, int left, int right) {
if(left>=right) {
return;
}
int pivot = partition(array,left,right);
quick(array,left,pivot-1);
quick(array,pivot+1,right);
}
//分割函数
private static int partition(int[] array, int left, int right) {
int index = left;
int num = array[index];
while(left<right) {
while(left<right && array[right]>=num) {
right--;
}
while(left<right && array[left]<=num) {
left++;
}
swap(array,left,right);
}
swap(array,left,index);
return left;
}
快速排序递归基本思想:
代码实现-迭代:
/**
* 快速排序-迭代实现*/
public static void quickSortIteration(int[] array) {
int left = 0;
int right = array.length-1;
Stack<Integer> stack = new Stack<>();
stack.push(left);
stack.push(right);
while(!stack.empty()) {
right = stack.pop();
left = stack.pop();
int pivot = partition(array,left,right);
//保证左右两边至少存在两个待排序元素
if(right-1>pivot) {
stack.push(pivot+1);
stack.push(right);
}
if(left+1<pivot) {
stack.push(left);
stack.push(pivot-1);
}
}
}
//分割函数
private static int partition(int[] array, int left, int right) {
int index = left;
int num = array[index];
while(left<right) {
while(left<right && array[right]>=num) {
right--;
}
while(left<right && array[left]<=num) {
left++;
}
swap(array,left,right);
}
swap(array,left,index);
return left;
}
快速排序迭代基本思想:
优化:
1,三数取中法挑选基准值
快速排序的时间复杂度会随着每次基准值分割序列均衡度的变化而改变,当左右子序列相差越多时,树的深度越深,时间复杂度越高,在待排序数列为顺序/逆序的情况下,时间复杂度达到最高O(N^2)。而三数取中法挑选基准值则可以避免出现最坏情况,并对一般情况也有较大改善。
其原理就是分别取待排序序列头下标、尾下标、中间下标的值进行对比,取这三个数的中间数与left交换,从而达到修改基准值的目的。
代码实现:
//快速排序-三数取中法优化
private static int middleNumber(int[] array, int left, int right) {
int mid = (left+right)/2;
if(array[left]<array[right]) {
if(array[mid]<array[left]) {
return left;
} else if(array[mid]>array[right]) {
return right;
} else {
return mid;
}
} else {
if(array[mid]<array[right]) {
return right;
} else if(array[mid]>array[left]) {
return left;
} else {
return mid;
}
}
}
代码优化思想解析(以递归实现为例):
2,当递归到指定区间时使用直接插入排序
快速排序递归实现每次按照基准值分割待排序序列时我们可以将其看作是一颗二叉树的延展过程。而在二叉树这种结构中,最后一层的节点个数大约为整棵树节点个数的 1/2。
同理,我们若是想在快速排序递归实现中有效减少递归个数,则可以设定当待排序序列的范围小于指定范围时,直接使用直接插入排序,此优化当范围设置合理时,理论上至少可以减少一半的递归量。
代码实现:
//在指定范围内进行直接插入排序
private static void insertSortRange(int[] array, int left, int right) {
for(int i = left; i <= right; i++) {
int tmp = array[i];
int j = i-1;
while(j>=left) {
if(array[j]>tmp) {
array[j+1] = array[j];
} else {
break;
}
j--;
}
array[j+1] = tmp;
}
}
代码优化思想解析:
(7)归并排序-mergeSort
算法思想简介:
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的案例。将已有序的子序列合并,得到完全有序的序列;即先使子序列有序,再将两个有序表合并成一个有序表,称为二路归并。
算法特性:
归并排序的缺点就是需要O(N)的时间复杂度,其更多是用于解决外部排序。
代码实现-递归
/**
* 归并排序
* 时间复杂度:
* O(N*logN)
* 空间复杂度:
* O(N)
* 稳定性:
* 稳定*/
public static void mergeSort(int[] array) {
mergeSortFunction(array,0,array.length-1);
}
private static void mergeSortFunction(int[] array, int left, int right) {
if(left>=right) {
return;
}
int mid = (left+right)/2;
mergeSortFunction(array,left,mid);
mergeSortFunction(array,mid+1,right);
merge(array,left,right,mid);
}
private static void merge(int[] array, int left, int right, int mid) {
int[] tmpArray = new int[right-left+1];
int record = left;
int k = 0;
int rightFront = mid+1;
while(left<=mid && rightFront<=right) {
if(array[left]<array[rightFront]) {
tmpArray[k++] = array[left++];
} else {
tmpArray[k++] = array[rightFront++];
}
}
while(left<=mid) {
tmpArray[k++] = array[left++];
}
while(rightFront<=right) {
tmpArray[k++] = array[rightFront++];
}
for(int i = 0; i < tmpArray.length; i++) {
array[i+record] = tmpArray[i];
}
}
归并排序递归基本思想:
归并排序-迭代
/**
* 归并排序-迭代实现*/
public static void mergeSortIteration(int[] array) {
//归并排序需要每次两组进行排序,而gap代表每组的元素个数
int gap = 1;
while(gap<array.length) {
for(int i = 0; i < array.length; i+=2*gap) {
int left = i;
int mid = left+gap-1;
int right = mid+gap;
if(mid>=array.length) {
mid = array.length-1;
}
if(right>=array.length) {
right = array.length-1;
}
merge(array,left,right,mid);
}
gap*=2;
}
}
归并排序迭代基本思想:
4,海量数据的排序问题
前提:运行内存仅有1G,但是待排序的数据有10G
解决方法:
因为内存中无法将所有的数据全部放下,所以需要外部排序,而归并排序就是最常用的外部排序
1:先将文件切分成200份,每个512M
2:分别对512M排序,因为内存已经放得下了,故任意排序方法都可以
3:对200份有序文件进行二路归并即可
5,其它非基于比较排序
(1)计数排序-countSort
算法思想简介:
统计相同元素出现的次数,再根据统计的结果将数据放回原来的序列中。
代码实现:
/**
* 计数排序
* 时间复杂度:
* O(MAX(N,范围))
* 空间复杂度:
* O(范围)
* 稳定性:
* 稳定(但是我这种实现是不稳定的)*/
public static void countSort(int[] array) {
//求最大值
int max = array[0];
int min = array[0];
for(int i = 1; i < array.length; i++) {
if(array[i]>max) {
max = array[i];
}
if(array[i]<min) {
min = array[i];
}
}
//创建计数数组
int[] tmp = new int[max-min+1];
for(int i = 0; i < array.length; i++) {
tmp[array[i]-min]++;
}
//将已排序的数值拷贝☞array数组
int k = 0;
for(int i = 0; i < tmp.length; i++) {
while(tmp[i]!=0) {
array[k++] = min+i;
tmp[i]--;
}
}
}
计数排序基本思想:
(2)基数排序-radixSort
算法思想简介:
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
代码实现:
/**
* 基数排序
* 时间复杂度:
* O(N^2)
* 空间复杂度:
* O(N)
* 稳定性:
* 不稳定
* */
public static void radixSort(int[] array) {
//创建一个桶
Queue<Integer>[] bucket = new Queue[10];
for(int i = 0; i < bucket.length; i++) {
bucket[i] = new LinkedList<Integer>();
}
//求最大值
int max = array[0];
for(int i = 1; i < array.length; i++) {
if(array[i]>max) {
max = array[i];
}
}
int div = 10;
while(max*10 !=0) {
for(int i = 0; i < array.length; i++) {
int index = array[i]%div;
while(index>10) {
index/=10;
}
bucket[index].offer(array[i]);
}
int k = 0;
for(int i = 0; i < bucket.length; i++) {
while(!bucket[i].isEmpty()) {
array[k++] = (int)bucket[i].poll();
}
}
max/=10;
div*=10;
}
}
基数排序基本思想:
动图演示: