前言
算法对于我们普通的工程师来说可算得上陌生又熟悉,因为在平时的业务代码中可能见到他的身影比较少,但在底层的代码中我们可能会经常发现排序算法的影子,如数据库索引,操作系统的进程调度。因此,掌握这种算法中的思想还是致关重要,今天大猿就带大家来实战一下基础的排序算法,加强记忆。
常见的排序分类
直接插入排序
先直接上代码:
java
public static void insertSort2(int [] arr){
for (int i = 1; i < arr.length; i++) {
int key = arr[i];
int j=i-1;
for ( ; j >= 0 ; j--) {
if(key < arr[j]){
arr[j + 1] = arr[j];
}else {
break;
}
}
arr[j + 1] = key;
}
StringBuffer stringBuffer = new StringBuffer();
for (int j : arr) {
stringBuffer.append(",").append(j);
}
System.out.println(stringBuffer);
}
public static void main(String[] args) {
int [] arr = {345,23,79,54,67,666,23,78,2,22,985};
// insertSort(arr);
insertSort2(arr);
}
运行结果
复杂度
时间复杂度
运行复杂度,当整个序列基本有序的情况下此时插入排序将出现最好情况 O(n) , 这个也不难想象,当整个排序序列基本有效时,比较只需要n次,内部循环几乎可以不执行。
对于一般情况时间复杂度是O(n^2);
空间复杂度
因为在程序执行过程中,我们只用一个一个临时存储变量,所以空间复杂度为O(1);
稳定性
该算法是稳定算法,如何理解稳定性呢?稳定性最直白的理解就是如果出现相同元素的时候,看原来的序列是否遭到破坏。
希尔排序
上代码,
java
public static void main(String[] args) {
int[] arr = new int[100];
for (int i = 0; i < 100; i++) {
arr[i] = (int) (Math.random() *100);
}
shellSort(arr);
}
public static void shellSort(int [] arr){
for (int gap = arr.length /2; gap>0; gap /=2){
for(int i = gap; i < arr.length ; i ++){
int temp = arr[i];
int j=i - gap;
for( ; j >= 0; j -= gap){
if(arr[j] < temp){
arr[j+gap]=arr[j];
}else {
break;
}
}
arr[j+gap] = temp;
}
}
StringBuffer stringBuffer = new StringBuffer();
for (int j : arr) {
stringBuffer.append(",").append(j);
}
System.out.println(stringBuffer);
}
运行结果
复杂度
时间复杂度
希尔排序可以理解为特殊的插入排序,最好和最坏时间复杂度基本与插入排序保持一致。软设上给出的平均复杂度为O(n^1.3) 有兴趣的同学可以去研究一下理论。
空间复杂度
空间复杂度基本与插入排序是一致的。
稳定性
该算法为非稳定算法。
计数排序
代码
java
public static int[] countSort(int[] array) {
//1.得到数列的最大值
int max = array[0];
for(int i=1; i<array.length; i++){
if(array[i] > max){
max = array[i];
}
}
//2.根据数列最大值确定统计数组的长度
int[] countArray = new int[max+1];
//3.遍历数列,填充统计数组
for(int i=0; i<array.length; i++){
countArray[array[i]]++;
}
//4.遍历统计数组,输出结果
int index = 0;
int[] sortedArray = new int[array.length];
for(int i=0; i<countArray.length; i++){
for(int j=0; j<countArray[i]; j++){
sortedArray[index++] = i;
}
}
return sortedArray;
}
public static int[] countSortV2(int[] array) {
//1.得到数列的最大值和最小值,并算出差值d
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 d = max - min;
//2.创建统计数组并统计对应元素个数
int[] countArray = new int[d+1];
for(int i=0; i<array.length; i++) {
countArray[array[i]-min]++;
}
//3.统计数组做变形,后面的元素等于前面的元素之和
for(int i=1;i<countArray.length;i++) {
countArray[i] += countArray[i-1];
}
//4.倒序遍历原始数列,从统计数组找到正确位置,输出到结果数组
int[] sortedArray = new int[array.length];
for(int i=array.length-1;i>=0;i--) {
sortedArray[countArray[array[i]-min]-1]=array[i];
countArray[array[i]-min]--;
}
return sortedArray;
}
public static void main(String[] args) {
int[] array = new int[] {4,4,6,5,3,2,8,1,7,5,6,0,10};
int[] sortedArray = countSort(array);
System.out.println(Arrays.toString(sortedArray));
array = new int[] {95,94,91,98,99,90,99,93,91,92};
sortedArray = countSortV2(array);
System.out.println(Arrays.toString(sortedArray));
}
运行结果
时间复杂度
计数排序的时间复杂度为O(n+k),其中n是待排序序列的长度,k是待排序序列中的最大值,注意计算时间复杂度时只是考虑了构造统计数组的最大长度k和
空间复杂度
计数排序的空间复杂度为O(k)。
稳定性
该算法是一个稳定算法,对于数的范围不大,但是算量很多的情况非常实用。
选择排序
继续先上代码
java
public static void selectSort(int [] arr){
int maxIndex =0;
for (int i = 0; i < arr.length-1; i++) {
for (int j = i; j <arr.length ; j++) {
if(arr[maxIndex]<arr[j]){
maxIndex =j;
}
}
if(maxIndex != i){
int temp =0;
temp = arr[maxIndex];
arr[maxIndex] = arr[i];
arr[i] = temp;
}
}
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i <arr.length; i++) {
stringBuffer.append(",").append(arr[i]);
}
System.out.println(stringBuffer.toString());
}
public static void main(String[] args) {
int [] arr = {345,23,79,54,67,666,23,78,2,22,985};
selectSort(arr);
}
运行结果
时间和空间复杂度
选择排序的时间复杂度为O(n^2) 空间复杂度为O(1)
稳定性
该算法为不稳定算法。
堆排序
堆排序稍微复杂点,先上个截图,至于具体理论的话请同学们自己去找教材或者视频,下面给出算法及代码
java
public static void main(String[] args) {
int arr [] ={1,10,3,2,6,5,7,9,8,0,4};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
/**
*
* @param array
*/
private static void heapSort(int [] array){
//构建堆
for (int i = (array.length-2)/2; i >=0; i--) {
downAdjust(array,i,array.length);
}
System.out.println("-----------------------");
System.out.println(Arrays.toString(array));
//调整堆
for (int i = array.length-1; i >0; i--) {
int temp = array[i];
array[i] = array[0];
array[0] = temp;
downAdjust(array,0,i);
/* if("UP".equals(cmd)){
upAdjust(array);
}*/
}
}
/***
* 节点下沉
* @param arr
* @param parentIndex
* @param length
*/
private static void downAdjust(int [] arr,int parentIndex , int length){
int temp = arr[parentIndex];
int childIndex = 2* parentIndex +1;
while (childIndex < length){
//定位到右孩子
if(childIndex+1 < length && arr[childIndex+1]>arr[childIndex]){
childIndex++;
}
if(temp > arr[childIndex]){
break;
}
arr[parentIndex] = arr[childIndex];
parentIndex = childIndex;
childIndex = 2* childIndex+1;
}
arr[parentIndex] = temp;
// System.out.println(Arrays.toString(arr));
}
运行结果
复杂度及稳定性
堆排序是不稳定排序,其时间复杂度时O(nlog(2)n),空间复杂度为n+1;此外
该算法是非稳定算法。
冒泡排序
代码如下:
java
public static void main(String[] args) {
int [] arr = {345,23,79,54,67,666,23,78,2,22,985};
bubbleSort(arr);
}
public static void bubbleSort(int [] arr){
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
int temp = 0;
if (arr[j] < arr[j+1]) {
temp = arr[j];
arr[j]=arr[j+1];
arr[j+1] = temp;
}
}
}
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i <arr.length; i++) {
stringBuffer.append(",").append(arr[i]);
}
System.out.println(stringBuffer.toString());
}
运行结果
复杂度分析及稳定性分析
冒泡算法的复杂度为O(n^2) 空间复杂度为O(1),该算法为稳定的排序算法。
快速排序
主算法
java
public static void main(String[] args) {
int [] arr = {345,23,79,54,67,666,23,78,2,22,985};
quickSort(arr,0,arr.length-1);
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i <arr.length; i++) {
stringBuffer.append(",").append(arr[i]);
}
System.out.println(stringBuffer.toString());
}
public static void quickSort(int [] arr, int start,int end){
if(start > end){
return;
}
int midIndex = partition(arr, start, end);
quickSort(arr,start,midIndex-1);
quickSort(arr,midIndex+1,end);
}
private static int partition(int[] arr, int start, int end) {
int right = end;
int left = start;
int midValue = arr[left];
while (left != right){
while (left<right && midValue < arr[right]){
right --;
}
// arr[left] = arr[right];
while (left<right && midValue >= arr[left]){
left ++;
}
// arr[right] = arr[left];
if(left < right){
int temp =0;
temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
}
arr[start] = arr[right];
arr[right] = midValue;
return right;
}
运行结果
复杂度分析
时间复杂度
快速排序的一般时间复杂度为 O(nlog(2)n) ,最坏的时间复杂度为O(n^2);
空间复杂度
递归算法的空间复杂度 = 每次递归的空间复杂度O(1) * 递归深度, 因为堆栈的深度就是快速排序的空间复杂度位O(log(2)n),最坏也就是O(n)
稳定性
快速排序为不稳定算法
归并排序
先上代码
java
public static void main(String[] args) {
int [] arr = {345,23,79,54,67,666,23,78,2,22,985};
mergeSort(arr, 0, arr.length - 1);
for (int i : arr) {
System.out.print(i + " ");
}
}
public static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
public static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
for (int p = 0; p < temp.length; p++) {
arr[left + p] = temp[p];
}
}
运行结果
时间复杂度
从上面的代码来看,其归并的深度为log(2)n 而每次合并的时间复杂度都为n,所以综合下来其时间复杂度为 n*log(2)n;
空间复杂度
因为归并需要额外的空间数组,其大小n
稳定性
该算法为稳定的排序算法,相同元素不改变原来的位置。
桶排序
桶排序的基本思想可以说是与计数排序有异曲同工之妙,由于篇幅原因,需要了解算法的具体实现的小伙伴可以自己去翻阅资料。
总结
本文带大家实战了常见的排序算法,并给出了运行结果,对于不同场景我们可以采用不同的排序算法,从时间复杂度来看,快排,归并,堆排序是响应速度最快的三种常见排序方法,但所占空间都比较高;从空间复杂度来看,冒泡,插入,简单选择排序是应用空间最小的排序方法,但响应时间有点慢;复杂为线性的排序算法只有基数排序和桶排序,但该算法并不适合所有的应用场景,所以在实际应用大各位小伙伴要懂得权衡利弊,妥善处置。