1、插入排序
public static void insertionSort(int[] array) {
int l = array.length;
int j;
for (int i = 1; i < l; i++) {
int temp = array[i];
//temp <= array[j - 1] 会因为等于多一次比较
for (j = i; j > 0 && temp < array[j - 1]; j--)
array[j] = array[j - 1];
array[j] = temp;
}
}
空间消耗 O(1) (临时保存array[i])
平均时间复杂度 O(n^2)
最好情况 O(n) (已经排序,内层for循环的检测总是立即判断不成立而终止)
最坏情况 O(n^2) (需排序的为逆序)
如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2)。
(可以使用二分查找法进行优化)
2、冒泡排序
冒泡排序(Bubble Sort,台湾译为:泡沫排序或气泡排序)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
可以是将最小的冒上去,或者最大的沉下去。
//改进后的,增加了一个判断标志
public static void bubbleSort(int[] array) {
boolean flag = true;
//若flag 为false表明剩下的序列是有序的了
for (int i = 0; i < array.length && flag; i++) {
flag = false;
for (int j = array.length - 1; j > i; j--) {
if (array[j] < array[j - 1]) {
int temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
flag = true;//表明有数据交换
}
}
}
空间消耗 O(1) (用于交换相邻数据)
平均时间复杂度 O(n^2)
最好情况 O(n) (改进后的,避免对已经有序的序列重复进行循环比较,未改进的为O(n^2) )
最坏情况 O(n^2) (需排序的为逆序)
当最好情况下,即需排序的数组本身是有序的,根据改进的代码。可以推断出有n-1次比较,没有数据交换。当最坏的时候,即需排序的数组本身是逆序的,此时需要比较n(n-1)/2次,并做等量数量级的记录移动。
3、归并排序
(1)递归实现
public static void mergeSort(int[] array) {
int[] tempArr = new int[array.length];
mergeSort(array, tempArr, 0, array.length - 1);
}
private static void mergeSort(int[] array, int[] tempArr, int left, int right) {
if (left < right) {
int center = (left + right) / 2;
//递归将左边的归并为有序
mergeSort(array, tempArr, left, center);
//递归将右边的归并为有序
mergeSort(array, tempArr, center + 1, right);
//将左右两个子序列归并到一起
merge(array, tempArr, left, center + 1, right);
}
}
private static void merge(int[] array, int[] tempArr, int leftPos, int rightPos, int rightEnd) {
int leftEnd = rightPos - 1, tmpPos = leftPos, num = rightEnd - leftPos + 1;
while (leftPos <= leftEnd && rightPos <= rightEnd) {
if (array[leftPos] < array[rightPos]) tempArr[tmpPos++] = array[leftPos++];
else tempArr[tmpPos++] = array[rightPos++];
// tempArr[tmpPos++] = array[array[leftPos] < array[rightPos] ? leftPos++ : rightPos++];
}
while (leftPos <= leftEnd)
tempArr[tmpPos++] = array[leftPos++];
while (rightPos <= rightEnd)
tempArr[tmpPos++] = array[rightPos++];
for (int i = 0; i < num; i++, rightEnd--)
array[rightEnd] = tempArr[rightEnd];
}
空间消耗 O(n+log n)
平均时间复杂度 O(n log n)
最好情况 O(n log n)
最坏情况 O(n log n)
(2)非递归实现
public static void mergeSort(int[] arr) {
int len = arr.length;
int k = 1;
while(k < len)
{
mergePass(arr, k, len);
k *= 2;
}
}
//mergePass方法负责将数组中的相邻的有k个元素的序列进行归并
private static void mergePass(int[] arr, int k, int n) {
int i = 0;
//从前往后,将2个长度为k的子序列合并为1个
//n - 2*k + 1中加 1 的原因是数组的下表是从 0 开始的
//且需要保证两两合并的序列(非落单的序列)长度为k
while(i < n - 2*k + 1)
{
merge(arr, i, i + k-1, i + 2*k - 1);
i += 2*k;
}
//这段代码保证了,将那些“落单的”长度不足两两merge的部分和前面merge起来。
if(i < n - k )
{
merge(arr, i, i+k-1, n-1);
}
}
//merge函数实际上是将两个有序数组合并成一个有序数组
private static void merge(int[] arr, int low, int mid, int high) {
//temp数组用于暂存合并的结果
int[] temp = new int[high - low + 1];
int i = low;
int j = mid+1;
int k = 0;
//将记录由小到大地放进temp数组
for(; i <= mid && j <= high; k++)
{
if(arr[i] < arr[j])
temp[k] = arr[i++];
else
temp[k] = arr[j++];
}
//接下来两个while循环是为了将剩余的(比另一边多出来的个数)放到temp数组中
while(i <= mid)
temp[k++] = arr[i++];
while(j <= high)
temp[k++] = arr[j++];
//将temp数组中的元素写入到待排数组中
for(int l = 0; l < temp.length; l++)
arr[low + l] = temp[l];
}
空间消耗 O(n)
避免了递归时需要的深度为 log n 的栈空间
4、堆排序
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
在下面的篇博客中说得挺好的,具体的实现细节可以参考,但是是用JS实现代码的:
http://bubkoo.com/2014/01/14/sort-algorithm/heap-sort/
public static void heapSort(int[] arr) {
//构建初始最大堆
for (int i = arr.length/2-1; i >=0; i--) {
buildMaxHeap(arr,i,arr.length);
}
for (int i = arr.length-1; i > 0 ; i--) {
//交换堆顶最大值和最后一个叶子结点
int tmp = arr[0];
arr[0] = arr[i];
arr[i] = tmp;
//重新构建剩下的序列的最大堆
buildMaxHeap(arr,0,i);
}
}
//构建最大堆
public static void buildMaxHeap(int[] array,int index,int heapSize) {
int iMax, iLeft, iRight;
while (true) {
iMax = index;
iLeft = 2 * index + 1;
iRight = 2 * (index + 1);
if (iLeft < heapSize && array[index] < array[iLeft]) {
iMax = iLeft;
}
if (iRight < heapSize && array[iMax] < array[iRight]) {
iMax = iRight;
}
if (iMax != index) {
int tmp = array[iMax];
array[iMax] = array[index];
array[index] = tmp;
index = iMax;
} else {
break;
}
}
}
空间消耗 O(1)
平均时间复杂度 O(n log n)
最好情况 O(n log n)
最坏情况 O(n log n)
堆排序复杂度分析:
它的运行时间主要是消耗在初始构建堆和在重建堆时的反复筛选上。
在构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较和若有必要的互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。
在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个结点到根结点的距离为⌊log2i⌋+1),并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为O(nlogn)。
所以总体来说,堆排序的时间复杂度为O(nlogn)。由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlogn)。这在性能上显然要远远好过于冒泡、简单选择、直接插入的O(n2)的时间复杂度了。
空间复杂度上,它只有一个用来交换的暂存单元,也算是非常的不错。不过由于记录的比较与交换是跳跃式进行,因此堆排序也是一种不稳定的排序方法。
另外,由于初始构建堆所需的比较次数较多,因此,它并不适合待排序序列个数较少的情况。
5、快速排序
快速排序(Quicksort)是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
参考博客:http://bubkoo.com/2014/01/12/sort-algorithm/quick-sort/
public static void quickSort(int[] arr) {
sort(arr,0,arr.length-1);
}
public static void sort(int[] array,int left,int right) {
if (left > right) {
return;
}
int storeIndex = partition(array, left, right);
sort(array, left, storeIndex - 1);
sort(array, storeIndex + 1, right);
}
private static int partition(int[] array, int left, int right) {
int storeIndex = left;
int pivot = array[right]; // 直接选最右边的元素为基准元素
for (int i = left; i < right; i++) {
if (array[i] < pivot) {
swap(array, storeIndex, i);
storeIndex++; // 交换位置后,storeIndex 自增 1,代表下一个可能要交换的位置
}
}
swap(array, right, storeIndex); // 将基准元素放置到最后的正确位置上
//之后,以基准元素为分界点,左边是小于它的,右边是大于等于它的
return storeIndex;
}
private static void swap(int[] array, int i, int k) {
int temp = array[i];
array[i] = array[k];
array[k] = temp;
}
空间消耗 O(log n)
平均时间复杂度 O(n log n)
最好情况 O(n log n)
最坏情况 O(n^2)
最坏情况发生在每次划分过程产生的两个区间分别包含n-1个元素和1个元素的时候(设输入的表有n个元素)。
最好情况为如果每次划分过程产生的区间大小都为n/2。