目录
各种排序算法的时间复杂度
说明:以下各种算法设计都按照升序排序,所有图片均为百度图片。
插入排序:
- 分为有序序列和无序序列,每次把无序序列中的一个插入有序序列,刚开始有序系列只有arr[0]
- 先把要插入的值保存在临时变量
- 然后一直往前比较,比要插入的值大就继续往前比,并把该位后移
- 直到找到比插入值小或相等的元素,然后临时变量的值插入在它的后面
- 记得加下标> = 0的条件,因为它可能会比有序序列所有数小。
public static void insertSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int temp = arr[i];
int j = i - 1;
while (j >= 0 && arr[j]>temp) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = temp;
}
}
选择排序:
- 从第一个元素开始,它与它后面所有的值相比较,记录下最小值得那个元素的下标,每一次都与当前最小值比较,找出最小值,然后跟当前要排序的位置元素交换。
- 然后第二个,第三个,......,与后面所有值相比较。
public static void chooseSort(int[] arr) {
int len = arr.length;
for (int i = 0; i < len-1; i++) {
int minIndex = i;
for (int j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j; //记录下无序数列中最小数的下标
}
}
if( minIndex != i) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
冒泡排序:
- 从第一个元素开始,与它后面一个元素比较,比它大就交换,让它在两个元素中始终后面的更大。
- 然后第二个跟第三个比较,......,到第n-1个个个与Ñ个元素比较后,最大的元素就被冒泡到了第Ñ个元素。
- 每一趟可以把一个无序序列中最大的元素放到最后,直到排序结束。
public static void bubblingSort(int[] arr) {
int len = arr.length;
for (int i = 0; i < len; i++) {
for (int j = 0; j < len-1-i; j++) {
if (arr[j + 1] < arr[j]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
哈希排序
优点:作用于数据紧凑的序列排序速度快
缺点:使用范围狭小,条件苛刻,并且需要额外的辅助空间
适用于排序数据比较紧凑,并且明确知道数据范围才能使用,循环次数 = 数组长度 * 数据范围(最大值 - 最小值)
- 把想要排序的数组中的数映射到一个新的数组,统计每个数字多少个
- 从0开始把每个数字的个数还原到要排序的数组
public static void hashSort(int[] arr) {
int[] temp = new int[101];
for (int i = 0; i < arr.length; i++) {
temp[ arr[i] ] ++;
}
int count = 0;
for (int i = 0; i < temp.length; i++) {
for(int j = 0;j<temp[i]; j++) {
arr[count] = i;
count++ ;
}
}
}
快速排序:
优点:普遍情况下速度最快,不需要额外空间
缺点: 不稳定,最坏情况下会退化为冒泡排序
- 先取一个基值,然后把所有比它小的元素放到它的左边,所有比它大的元素放到它的右边
- 然后在它的左边跟右边一直递归下去,就会得到每个元素的左边都是比它小的,右边都是比它大的,也就是最后的有序序列
/**
* 快速排序
* @param arr 要排序的数组
* @param start 开始元素的下标
* @param end 末尾元素下标+1
*/
public static void quickSort(int[] arr, int start,int end) {
if (start < end) {
int i = start, j = end-1;
int base = arr[start];
while (i < j) {
while (i < j && arr[j] >= base) {
j--;
}
if(i<j){
arr[i] = arr[j];
}
while (i < j && arr[i] <= base) {
i++;
}
if(i<j){
arr[j] = arr[i];
}
}
arr[i] = base;
quickSort(arr, start, i - 1);
quickSort(arr, i+1, end-1);
}
}
归并排序
采用分治的思想,先把序列分解为独立的数据,然后相邻的数据,组成一个有序的序列,再把各自有序的两个序列合并为有序的一个序列,一直这样合并,达到整个序列有序的结果,归并的次数 = 待排序数个数 - 1
优点: 排序速度快,时间复杂度不会根据数值的变化而变化,算法稳定
缺点:需要额外的辅助空间,会多用一倍的内存
/**
* 归并排序
* @param arr 要排序的数组
* @param left 数组起始下标
* @param right 数组结束下标
* @param temp 附加数组,长度与要排序数组相同即可
*/
public static void mergeSort(int[] arr,int left,int right,int[] temp) {
if(left < right) {
int mid = (left + right)/2;
//分左边
mergeSort(arr,left,mid,temp);
//分右边
mergeSort(arr,mid + 1,right,temp);
//合并
merge(arr,left,mid,right,temp);
}
}
//合并方法
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left ; //左边分组的起始位置
int j = mid + 1 ; //右边分组的起始位置
int t = 0;//附加数组的元素下标
//把两组数据,从小到大拷贝到附加数组
while(i<=mid && j<=right) {//两组数据各自有序,所以从小到大比较,然后拷贝
if(arr[i] < arr[j]) {
temp[t] = arr[i];
t++ ;
i++ ;
}else {
temp[t] = arr[j];
t++ ;
j++ ;
}
}
//两组数据比较并拷贝,总有一组会提前拷贝完,然后就把剩余的部分拷贝到附加数组中
while(i<=mid) {
temp[t] = arr[i];
t++ ;
i++ ;
}
while(j<=right) {
temp[t] = arr[j];
t++ ;
j++ ;
}
//把刚才拷贝到附加数组的有序序列拷贝回原始数组
t = 0;
while(left <= right) {
arr[left] = temp[t];
left++ ;
t++ ;
}
}
堆排序
基于完全二叉树的顺序存储
满二叉树:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树
完全二叉树:完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树
对于完全二叉树
- 第n个节点的左子节点下标为 (2 * n + 1)
- 第n个节点的右子节点下标为 (2 * n + 2)
- 第n个节点的父节点下标为 (n-1) / 2
- 节点总数为n的完全二叉树,非叶子节点为 n / 2 - 1
大顶堆:所有节点的子节点都不大于父节点(适用于升序排序)
小顶堆:所有节点的子节点都不小于父节点(适用于降序排序)
算法思想:
把完全二叉树调节成大顶堆,然后把根节点的数值与完全二叉树的最后一个节点交换,之前已经交换过的节点不再交换,它已经是有序的序列,然后再重新构建大顶堆,因为之前已经构建过了大顶堆,只是交换了根节点,所以只需要调节根节点以及调节根节点时被影响的子节点。每完全调节成为大顶堆之后,就会把最大的元素调节到根节点,然后交换到最后。
public static void heapSort(int[] arr) {
//一颗n个节点的完全二叉树,有n/2-1个非叶子节点,调节每一个节点为大顶堆形式,从后往前调节
for(int i=arr.length/2-1; i>=0; i--) {
getBigTopHeap(arr,i,arr.length);
}
for(int j=arr.length-1; j>0; j--) {
swap(arr,j,0); //堆顶元素与最后一个节点交换
//调节根节点为大顶堆的形式,并且会继续调整受影响的子节点
//因为之前已经时大顶堆的形式,根节点交换后,
//只有根节点不符合,只需要调节根节点与调节根节点受影响的子节点
getBigTopHeap(arr,0,j);
}
}
/**
* 调节当前节点为大顶堆的形式,也就是左右子节点都比它小,并且会继续调整受影响的子节点
* @param arr 要排序的数组
* @param index 调节当前节点为大顶堆的形式,也就是左右子节点都比它小
* @param len 要排序的长度,因为当二叉树完全就是一个大顶堆的时候,
* 最大的元素就是根节点,也就是已经排好一个数,len就会减1
*/
public static void getBigTopHeap(int[] arr,int index,int len) {
for(int j = 2*index+1; j < len ; j = j*2 + 1) {
if( (j+1) < len && arr[j] < arr[j+1] ) {//左子节点比右子节点小
j++ ; //让j指向右子节点
}//结果j总是指向数值较大的那个节点
if( arr[j] > arr[index]) { //如果子节点大于父节点
swap(arr,j,index); //交换两个元素
//指向交换数值的子节点,因为它交换数值之后,可能无法保证它比它的子节点都大,所以需要
//再把它左右节点以及它本身最大的那个元素调上来。
index = j;
}else {
return; //父节点最大时无需调整
}
}
}
/**
* 交换数组中的两个元素
* @param arr 目标数组
* @param i 元素1的下标
* @param j 元素2的下标
*/
public static void swap(int[] arr,int i,int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}