1. 冒泡排序
时间复杂度:最好O(n),平均和最坏情况O(n2)
空间复杂度:O(1)
稳定排序
原理:从第一个元素开始,依次比较相邻两个元素,如果前者比后者大,那么就交换者两个元素,然后处理下一组,依次类推,直到排序完成。
实现:
public void bubbleSort(int[] arr){
boolean isChanged = false;
for(int i = 0; i < arr.length - 1; i++){
isChanged = false;
for(int j = 0; j < arr.length - 1 - i; j++){
if(arr[j] > arr[j+1]){
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
isChanged = true;
}
}
if(!isChanged) break; //本次没有发生交换,说明已经排好序了
}
}
2. 选择排序
时间复杂度:最好、平均和最坏情况O(n2)
空间复杂度:O(1)
不是稳定排序
原理:从第一个元素开始,每次逐一扫描选择未排序部分的最小值,排在已排序部分后面,然后从下一个位置开始,继续进行相同的操作,直到排序完成。
实现:
/**
* 选择排序
*/
public static void sort(int[] arr){
//判断arr是否为空
if(arr == null) return;
int minIndex;
for(int i = 0; i < arr.length - 1; i++){
minIndex = i;
for (int j = i + 1; j < arr.length; j++){
if (arr[j] < arr[minIndex]){
minIndex = j;
}
}
if (minIndex != i){ //最小值不是当前值,需要交换
int tmp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = tmp;
}
}
}
3. 快速排序
时间复杂度:最好、平均情况O(nlogn),最坏情况O(n2)
空间复杂度:平均情况O(logn),最坏情况O(n)
不是稳定排序
原理:每次选择一个数,将数组按照这个数分成左右两个部分,右边的比它大,左边的比他小然后对左右两部分分别进行同样的操作,直到数组排序完成
实现:
public static void sort(int[] arr){
sortCore(arr, 0, arr.length - 1);
}
/**
* @param arr 排序的数组
* @param start 开始位置
* @param end 结束位置
*/
private static void sortCore(int[] arr, int start, int end) {
int poiv = partion(arr, start, end);
if (poiv > start){
sortCore(arr, start, poiv - 1);
}
if (poiv >= start && poiv < end){
sortCore(arr, poiv + 1, end);
}
}
private static int partion(int[] arr, int start, int end) {
int tmp = arr[start];
while (start < end){
while (start < end && arr[end] >= tmp) end--;
if (start < end) {
arr[start] = arr[end];
}
while (start < end && arr[start] < tmp) start++;
if (start < end){
arr[end] = arr[start];
}
}
arr[start] = tmp;
return start;
}
4. 归并排序
时间复杂度:最好、平均和最坏情况O(nlogn)
空间复杂度:O(n)
稳定排序
原理:首选将要排序的数组对半分,对各自部分进行排序。每部分继续进行相同的操作,直至最底层。然后合并两个相邻的部分,直到所有元素都排序完成
实现:
public static void sort(int[] arr){
//创建数组,辅助排序
int[] copy = new int[arr.length];
sortCore(arr, copy, 0, arr.length - 1);
}
/**
* 归并排序核心实现
* @param arr 排序的数组
* @param copy 辅助空间
* @param start 开始位置
* @param end 结束位置
* @param offset 索引相对于原数组的偏移
*/
private static void sortCore(int[] arr, int[] copy, int start, int end) {
if (start == end) {
copy[start] = arr[start];
return; }
int mid = (end - start) / 2 + start;
//分成两部分,递归
sortCore(arr, copy, start, mid);
sortCore(arr, copy, mid + 1, end);
//合并两个部分,将合并结果存入
int forward = mid;
int behand = end;
int last = end;
while (forward >= start && behand > mid){
if (copy[forward] > copy[behand]){
arr[last --] = copy[forward --];
}else {
arr[last --] = copy[behand --];
}
}
while (forward >= start){
arr[last --] = copy[forward --];
}
while (behand > mid){
arr[last --] = copy[behand --];
}
//拷贝到copy数组
for (int i = start; i <= end; i++){
copy[i] = arr[i];
}
}
5. 插入排序
时间复杂度:最好O(n),平均和最外情况O(n2)
空间复杂度:O(1)
稳定排序
原理:从数组第一个元素开始,依次比较前面已经排序的部分,插入合适的位置,前面排序部分比当前值大的部分向后移动一个。
实现:
public static void sort(int[] arr){
int tmp; //每次排序,存储当前的值
for(int i = 1; i < arr.length; i++){
tmp = arr[i]; //保存当前值
int j;
for (j = i; j >= fromIndex && tmp < arr[j - 1]; j --){ //遇到比当前值大的元素,则元素后移一位
arr[j] = arr[j-1];
}
arr[j] = tmp; |
}
}
6. 希尔排序
时间复杂度:平均情况O(n1.25)
空间复杂度:O(1)
不是稳定排序
原理:将无序数组分割为若干个子序列,子序列不是逐段分割的,而是相隔特定的增量的子序列,对各个子序列进行插入排序;然后再选择一个更小的增量,再将数组分割为多个子序列进行排序……最后选择增量为1,即使用直接插入排序,使最终数组成为有序。
实现:
public static void sort(int[] arr){
//checkRange(arr.length, fromIndex, toIndex);
int adder = arr.length / 2; //增量
while (adder > 0){
//从adder开始,每次排序均与前面的adder(当adder是1时就是插入排序)处的元素比较
for (int i = adder ; i < arr.length; i++){
int j;
int tmp = arr[i];
for (j = i; j >= adder && tmp < arr[j - adder]; j = j - adder){
arr[j] = arr[j - adder];
}
arr[j] = tmp;
}
adder /= 2;
}
}
7. 堆排序
时间复杂度:最好、平均和最坏情况均为O(nlogn)
空间复杂度:O(1)
不是稳定排序
原理:建立初始堆,从最后一个非叶结点开始,往前遍历,判断以该节点的开始的堆是否是符合,不符合则调整需要建立大顶堆,每次将子节点中较大地一个数往上移动,直到叶结点(堆:结点n的父节点为(n-1)/ 2,其左右子节点为2*n+1和2*n+2大根堆为根结点的值大于等于左右子结点的值),然后依次将堆顶值与为排序的最后一个值交换,然后调整前面的值为大顶堆,每次将最大的值排好序。
实现:
/**
* 堆排序
*/
public static void sort(int[] arr){
//建立初始堆,从最后一个非叶结点开始,往前遍历,判断以该节点的开始的堆是否是符合,不符合则调整
//需要建立大顶堆,每次将子节点中较大地一个数往上移动,直到叶结点
//节点n的父节点为(n-1)/ 2,其左右子节点为2*n+1和2*n+2
//大根堆为根结点的值大于等于左右子结点的值
for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--){
adjustHeap(arr, i, arr.length-1);
}
//依次将堆顶值与为排序的最后一个值交换,然后调整前面的值为大顶堆
for (int i = arr.length - 1; i >= 1; i--){
//交换
arr[0] ^= arr[i];
arr[i] ^= arr[0];
arr[0] ^= arr[i];
//调整
adjustHeap(arr, 0, i - 1);
}
}
/**
* 调整为大顶堆
* @param arr
* @param i 以i为堆的堆顶
* @param last 堆顶的最后一个结点的索引
*/
private static void adjustHeap(int[] arr, int i, int last) {
//建立以i结点为根的堆,判断子结点是否大于该节点,并将较大地值拷贝,然后继续判断
int tmp = arr[i];
for (int j = i * 2 + 1; j <= last; j = j * 2 + 1){
//获得左右子树中较大的一个数的下标
if (j < last && arr[j] < arr[j+1]) j++; //存在右子结点且右子结点较大
if (tmp >= arr[j]) break; //根结点比较大,则完成
//将较大的值作为根
arr[i] = arr[j];
i = j; //继续往下判断,j的位置的值是最初的根结点
}
arr[i] = tmp; //最后确定的位置,没有子结点或者比子结点的值大;
}