基本排序算法实现与分析

冒泡排序、选择排序、插入排序、归并排序、快速排序、堆排序代码实现与特性分析

冒泡排序

实现代码

public static void bubbleSort(int[] arr) {
	if (arr == null || arr.length < 2) {
		return;
	}
	//从0号开始比较,每次把最大的搬运到末尾,arr[i]为每次循环确定的位于最终位置的那个元素
    
    bool flag = true; //flag用来作为标记
	for (int i = arr.length -  1; i > 0 && flag; i--) {
       flag = false;
		for (int j = 0; j < i; j++) {
			if (arr[j] > arr[j + 1]) {
				swap(arr, j, j + 1);
              flag = true; 
			}
		}
	}
}

理论分析

排序过程中局部有序。
在应用了哨兵后初始序列有序情况下效率最高,为O(n)
最坏、平均时间复杂度为O(n^2),最好时间复杂度为O(n)
空间复杂度为O(1)

插入排序

实现代码

//每次循环使得[0,i]成为有序序列,元素i找到从后向前正确的插入位置
public static void insertionSort(int[] arr) {
	if (arr == null || arr.length < 2) {
		return;
	}
	for (int i = 1; i < arr.length; i++) {
		for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
			swap(arr, j, j + 1);
		}
	}
}

理论分析

时间复杂度与数据初始状况相关,最好情况下也就是初始有序的情况下,时间复杂度为O(n),逆序为最差情况,为O(n2)。平均时间复杂度O(n2)。
空间复杂度为O(1)
稳定

选择排序

实现代码

public static void selectionSort(int[] arr) {
	if (arr == null || arr.length < 2) {
		return;
	}
	//每轮循环找i到arr.length - 1中最小的,和i位置上的数交换
	for (int i = 0; i < arr.length - 1; i++) {
		int minIndex = i;
		for (int j = i + 1; j < arr.length; j++) {
			minIndex = arr[j] < arr[minIndex] ? j : minIndex;
		}
		swap(arr, i, minIndex);
	}
}

public static void swap(int[] arr, int i, int j) {
	int tmp = arr[i];
	arr[i] = arr[j];
	arr[j] = tmp;
}

理论分析

最好、最差、平均时间复杂度均为O(n^2)
空间复杂度为O(1)
不稳定
时间效率与初始序列无关

归并排序

实现代码

public static void mergeSort(int[] arr) {
	if (arr == null || arr.length < 2) {
		return;
	}
	mergeSort(arr, 0, arr.length - 1);
}

public static void mergeSort(int[] arr, int l, int r) {
	if (l == r) {
		return;
	}
	int mid = l + ((r - l) >> 1);
	mergeSort(arr, l, mid);//T(N/2)
	mergeSort(arr, mid + 1, r);//T(N/2)
	merge(arr, l, mid, r);//O(N)
	//T(N) = 2*T(2/N) + O(N)
}

public static void merge(int[] arr, int l, int m, int r) {
	int[] help = new int[r - l + 1];
	int i = 0;
	int p1 = l;
	int p2 = m + 1;
	while (p1 <= m && p2 <= r) {
		help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
	}
	while (p1 <= m) {
		help[i++] = arr[p1++];
	}
	while (p2 <= r) {
		help[i++] = arr[p2++];
	}
	for (i = 0; i < help.length; i++) {
		arr[l + i] = help[i];
	}
}

理论分析

根据master算法,T(N) = 2*T(2/N) + O(N) ,即a=2,b=2,d=1
所以,时间复杂度为O(NlogN)
归并排序的最好、最差、平均时间复杂度均为O(NlogN)
归并排序的空间复杂度为O(N)
算法效率与初始序列状态无关

快速排序

实现代码

public class Sort {
    //经典快排思路,划分时选右边界做标准,然后从左向右挤
    public static int partition(int[] arr,int l,int r){
        int less = l - 1;
        for(int i = l;i <= r;i++){
            if(arr[i] < arr[r]){
                swap(arr,++less,i);
            }
        }
        swap(arr,less + 1,r);
        return less + 1;
    }

    public static void quickSort(int[] arr,int l,int r){
        if(l >= r)
            return;
        int p = partition(arr,l,r);
        quickSort(arr,l,p - 1);
        quickSort(arr,p + 1,r);
    }

    public static void swap(int[] arr,int i,int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

理论分析

最好、平均时间复杂度为O(nlogn),最坏时间复杂度为O(n^2)
空间复杂度为O(1)
时间效率与初始序列有关,初始有序时效率最差为O(n^2)
不稳定

堆排序

代码实现

//排序,因为每次堆顶元素都被放在了存储堆的数组后的第一个位置,所以排序后为增序,大根堆
public static void heapSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    for (int i = 0; i < arr.length; i++) {
        heapInsert(arr, i);
    }
    int size = arr.length;
    swap(arr, 0, --size);
    while (size > 0) {
        heapify(arr, 0, size);
        swap(arr, 0, --size);
    }
}

//新的元素入堆,从下往上调整
public static void heapInsert(int[] arr, int index) {
    while (arr[index] > arr[(index - 1) / 2]) {
        swap(arr, index, (index - 1) / 2);
        index = (index - 1) / 2;
    }
}

//当顶部元素出堆,堆内最后一个元素补上堆顶元素的位置,再对该元素进行调整,从上往下调整
public static void heapify(int[] arr, int index, int size) {
    int left = index * 2 + 1;
    while (left < size) {
        int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
        largest = arr[largest] > arr[index] ? largest : index;
        if (largest == index) {
            break;
        }
        swap(arr, largest, index);
        index = largest;
        left = index * 2 + 1;
    }
}


public static void swap(int[] arr, int i, int j) {
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

理论分析

时间复杂度O(NlogN),额外空间复杂度O(1)
建立堆的时间复杂度为O(N)   (应为log1+log2+…+logn收敛于n )
大根堆:在表示堆的完全二叉树中,任何子树的最大值都是子树的根节点
小根堆:在表示堆的完全二叉树中,任何子树的最小值都是子树的根节点 
二叉树左子节点下标2*i+1,右子节点下标2*i+2
父节点下标(i- 1)/2
堆一定是完全二叉树,并且是用数组进行存储,使用下标关系进行子父节点对应

java排序函数逻辑

  1. 长度小于60用插入排序,因为常数项极少,小样本下速度快
  2. 而大样本时使用快排/归并先划分,当样本数变少后再用插入排序。类似于原来是if(L==R) return;现在是LR之间有60个元素,再返回
  3. 基本数据类型(int,float,double...)排序用快排(因为基本数据类型只要相等就一样,没必要保证稳定性)
  4. 对象排序用归并(可以保证稳定性)
    注:一个数组,要求把奇数放左边,偶数放右边,并保持奇数偶数部分内部顺序不变,空间复杂度O(1)时间复杂度O(n)——无法做到,因为要是能做到快排的划分操作就可以保证稳定性,这两个本质上都是0-1操作划分

总结

排序算法 时间复杂度 空间复杂度 稳定性 效率是否与初始序列有关
冒泡排序 最坏、平均为O(n^2),最好为O(n) O(1) 稳定 相关(哨兵机制,如不使用哨兵则无关),初始有序时最好时间复杂度为O(n)
归并排序 最好、最差、平均均为O(NlogN) O(n) 稳定 无关
插入排序 最好O(n),最差、平均为O(n^2) O(1) 稳定 相关,初始有序时最好时间复杂度为O(n),逆序时最差为O(n^2)
选择排序 最好、最差、平均均为O(n^2) O(1) 不稳定 无关
快速排序 最好、平均为O(nlogn),最坏为O(n^2) O(1) 不稳定 相关,初始有序时效率最差为O(n^2),解决方案为随机选择划分元素
堆排序 O(NlogN) O(1) 不稳定 堆排这个就不说相不相关了吧
基数排序 O(d*(r+n)),d为排序码的位数,r为排序码基数(10)d小n大时适合 O(1) 稳定
posted @ 2020-04-24 08:53  wunsiang  阅读(228)  评论(1编辑  收藏  举报