本文总结的排序算法有简单选择排序、直接插入排序、冒泡排序、希尔排序、快速排序、堆排序、归并排序,桶排序,基数排序。
简单选择排序
private void selectSort(int[] nums){
for(int i = 0; i < nums.length-1; i++){
int index = i;
for(int j = i+1; j < nums.length; j++){
if(nums[j] < nums[index]){
index = j;
}
}
if(index != i){
int tmp = nums[i];
nums[i] = nums[index];
nums[index] = tmp;
}
}
}
插入排序
private void insertSort(int[] nums){
for(int i = 0; i < nums.length-1; i++){
int index = i+1;
while(index > 0 && nums[index] < nums[index-1] ){
int tmp = nums[index];
nums[index] = nums[index-1];
nums[index-1] = tmp;
index--;
}
}
}
冒泡排序
void bobbleSort(int[] nums){
for(int i = 0; i < nums.length; i++){
for(int j = 0; j < nums.length - i -1; j++){
if(nums[j] > nums[j+1]){
int tmp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = tmp;
}
}
}
}
归并排序
private void mergeSort(int[] nums, int low, int high){
if(low < high){
int mid = (low + high)/2;
mergeSort(nums, low, mid);
mergeSort(nums, mid+1, high);
merge(nums, low, mid, high);
}
}
private void merge(int[] nums, int low, int mid, int high){
int[] tmp = new int[high-low+1];
int i = low, j = mid+1;
int index = 0;
while(i <= mid && j <= high){
if(nums[i] > nums[j]){
tmp[index++] = nums[j++];
}else{
tmp[index++] = nums[i++];
}
}
for(; i <= mid;){
tmp[index++] = nums[i++];
}
for(; j <= high;){
tmp[index++] = nums[j++];
}
for(int k = 0; k < tmp.length; k++){
nums[low+k] = tmp[k];
}
}
快速排序
partition函数
快排里面的partition函数用来解决这样的问题:给定一个数组arr[]和数组中任意一个元素a,重排数组使得a左边都小于它,右边都不小于它。
public class Partition {
public static void main(String[] args) {
// TODO Auto-generated method stub
Partition p = new Partition();
int[] a = {1,4,3,6,7,9,2};
System.out.println(p.partition(a,0,6,2));
for(int i : a){
System.out.print(i + " ");
}
}
// arr[]为数组,start、end分别为数组第一个元素和最后一个元素的索引
// povitIndex为数组中任意选中的数的索引
int partition(int arr[], int start, int end, int pivotIndex)
{
int pivot = arr[pivotIndex];
int tmp = arr[pivotIndex];
arr[pivotIndex] = arr[end];
arr[end] = tmp;
int storeIndex = start;
for(int i = start; i < end; ++i) {
if(arr[i] < pivot) {
tmp = arr[i];
arr[i] = arr[storeIndex];
arr[storeIndex] = tmp;
++storeIndex;
}
}
tmp = arr[storeIndex];
arr[storeIndex] = arr[end];
arr[end] = tmp;
return storeIndex;
}
}
以上代码作为思想,改进后的快排代码:
void sort(int nums){
quickSort(nums, 0, nums.length - 1);
}
void quickSort(int[] nums, int low, int high){
if(low >= high){
return;
}
int tmp = nums[low];
int i = low;
int j = high;
while(i < j){
while(i < j && nums[j] >= tmp){
j--;
}
if(i < j){
nums[i++] = nums[j];
}
while(i < j && nums[i] < tmp){
i++;
}
if(i < j){
nums[j--] = nums[i];
}
}
nums[i] = tmp;
quickSort(nums, low, i-1);
quickSort(nums, i+1, high);
}
希尔排序
比较清晰的思路,作为算法思想的参考,可以直接跳过这个:
private void shellSort1(int[] nums)//Simple implementation
{
int i , j ,gap;
int n = nums.length;
for(gap = n/2; gap > 0; gap /= 2){
for(i = 0; i < gap; i++){
for(j = i + gap; j < n; j += gap){
if(nums[j] < nums[j-gap]){
int temp = nums[j];
int k = j - gap;
while(k >= 0 && nums[k] > temp){
nums[k + gap] = nums[k];
k -= gap;
}
nums[k + gap] = temp;
}
}
}
}
}
比较简洁的代码,还是要用三重循环实现:
private void shellSort2(int[] nums){
int gap, i, j;
for(gap = nums.length/2; gap > 0; gap /= 2){
for(i = gap; i < nums.length; i++){
int temp = nums[i];
for(j = i - gap; j >= 0; j -= gap){
if(temp < nums[j]){
nums[j + gap] = nums[j];
}else{
break;
}
}
nums[j + gap] = temp;
}
}
}
堆排序
用数组来存储堆,下标为i的结点,父节点的编号为(i-1)/2,子结点的编号为2i+1, 2i+2。
**建立堆:**每次插入一个元素并调整堆的过程。
**插入一个元素:**插入到数组最后,更新堆。
**删除一个元素:**删除发生在nums[0],将最后一个元素调整到nums[0]处,更新堆。
**堆排序:**堆排序主要包括两步,一是构建堆,二是交换堆顶元素与最后一个元素的位置。
private void minHeapSort(int[] nums) {
int i;
int len = nums.length;
for(i = len/2-1; i >= 0; i--){
adjustMinHeap(nums, i, len -1);
}
for(i = len-1; i >= 0; i--){
int tmp = nums[0];
nums[0] = nums[i];
nums[i] = tmp;
adjustMinHeap(nums, 0, i - 1);
}
}
private void adjustMinHeap(int[] nums, int pos, int len){
int temp;
int child;
for(temp = nums[pos]; 2 * pos + 1 <= len; pos = child){
child = pos * 2 + 1;
if(child < len && nums[child] > nums[child + 1]){
child++;
}
if(nums[child] < temp){
nums[pos] = nums[child];
}else{
break;
}
}
nums[pos] = temp;
}
基数排序
参考http://blog.youkuaiyun.com/lg1259156776/article/details/48783753
以10进制排序为例,
思想:分配和收集。新建10个链表(或数组或其他容器),迭代n次(n为最大数的位数),对迭代i,将数字分配从右到左的第i位数字的桶号,最后按照顺序收集。
用链表来收集(n个),并增加指向链表的尾指针,尾指针2r(基,此处为10),增加总的空间(n+2r)。
一般分配代价O(n),收集代价O®,总代价O(d(r+n))
时间复杂度,平均O(d(r+n)),最坏O(d(n+rd)),最好O(d(r+n))
空间复杂度,O(rd+n)(r代表关键字的基数,d代表长度,n代表关键字的个数)
稳定。
桶排序
建立桶,将一个数据表分到各个桶(每个桶按照分区来接收数据),对非空桶各自排序再按顺序合并。(分治)
算法性能比较
排序算法 | 最好时间 | 平均时间 | 最坏时间 | 辅助空间 | 稳定性 | 备注 |
---|---|---|---|---|---|---|
简单选择排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 不稳定 | n小时较好 |
直接插入排序 | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 稳定 | 大部分已有序时较好 |
冒泡排序 | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 稳定 | n小时较好 |
希尔排序 | O ( n ) O(n) O(n) | O ( n l o g n ) O(nlog n) O(nlogn) | O ( n s ) O(n^s) O(ns) 1 < s < 2 1<s<2 1<s<2 | O(1) | 不稳定 | s是所选分组 |
快速排序 | O ( n l o g n ) O(nlog n) O(nlogn) | O ( n l o g n ) O(nlog n) O(nlogn) | O ( n 2 ) O(n^2) O(n2) | O ( l o g n ) O(log n) O(logn) | 不稳定 | n大时较好 |
堆排序 | O ( n l o g n ) O(nlog n) O(nlogn) | O ( n l o g n ) O(nlog n) O(nlogn) | O ( n l o g n ) O(nlog n) O(nlogn) | O ( 1 ) O(1) O(1) | 不稳定 | n大时较好 |
归并排序 | O ( n l o g n ) O(nlog n) O(nlogn) | O ( n l o g n ) O(nlog n) O(nlogn) | O ( n l o g n ) O(nlog n) O(nlogn) | O ( n ) O(n) O(n) | 稳定 | n大时较好 |
后来又添加的一些排序算法比较:
可参考常用排序算法时间复杂度和空间复杂度。
在这个表格中,n是要被排序的纪录数量以及k是不同键值的数量。
稳定的排序
冒泡排序(bubble sort)— O(n2)
鸡尾酒排序(cocktail sort)—O(n2)
插入排序(insertion sort)—O(n2)
桶排序(bucket sort)—O(n);需要O(k)额外空间
计数排序(counting sort)—O(n+k);需要O(n+k)额外空间
归并排序(merge sort)—O(n log n);需要O(n)额外空间
原地归并排序— O(n2)
二叉排序树排序(binary tree sort)— O(n log n)期望时间; O(n2)最坏时间;需要O(n)额外空间
鸽巢排序(pigeonhole sort)—O(n+k);需要O(k)额外空间
基数排序(radix sort)—O(n·k);需要O(n)额外空间
侏儒排序(gnome sort)— O(n2)
图书馆排序(library sort)— 时间复杂度通常是O(n log n),需要(1+ε)n额外空间
不稳定的排序
选择排序(selection sort)—O(n2)
希尔排序(shell sort)—O(n log2 n)如果使用最佳的现在版本
Clover排序算法(Clover sort)—O(n)期望时间,O(n^2/2)最坏情况
梳排序— O(n log n)
堆排序(heap sort)—O(n log n)
平滑排序(smooth sort)— O(n log n)
快速排序(quick sort)—O(n log n)期望时间, O(n2)最坏情况;对于大的、乱数列表一般相信是最快的已知排序
内省排序(introsort)—O(n log n)
耐心排序(patience sort)—O(n log n + k)最坏情况时间,需要额外的O(n + k)空间,也需要找到最长的递增子序列(longest increasing subsequence)
不实用的排序
Bogo排序— O(n × n!),最坏的情况下期望时间为无穷。
Stupid排序—O(n3);递归版本需要O(n2)额外存储器
珠排序(bead sort)— O(n) or O(√n),但需要特别的硬件
煎饼排序—O(n),但需要特别的硬件
臭皮匠排序(stooge sort)算法简单,但需要约n^2.7的时间
平均时间复杂度由高到低为:
冒泡排序O(n2)
选择排序O(n2)
插入排序O(n2)
希尔排序O(n1.25)
堆排序O(n log n)
归并排序O(n log n)
快速排序O(n log n)
基数排序O(n)
说明:虽然完全逆序的情况下,快速排序会降到选择排序的速度,不过从概率角度来说(参考信息学理论,和概率学),不对算法做编程上优化时,快速排序的平均速度比堆排序要快一些。