排序名称 | 时间复杂度 | 空间复杂度 | 稳定性 | |
插入排序 | 直接插入排序 | O(n2) | O(1) | 是 |
二分插入排序 | O(nlogn) | O(n) | 是 | |
希尔排序 | O(n(logn) 2) | O(1) | 否 | |
交换排序 | 冒泡排序 | O(n2) | O(1) | 是 |
快速排序 | O(nlogn) | O(logn) | 否 | |
选择排序 | 直接选择排序 | O(n2) | O(1) | 否 |
堆排序 | O(nlogn) | O(1) | 否 | |
归并排序 |
| O(nlogn) | O(n) | 是 |
基数排序 |
| O(n+k) | O(n+k) | 是 |
所需辅助空间最多:归并排序;
所需辅助空间最少:堆排序;
平均速度最快:快速排序;
不稳定的排序:希尔排序,快速排序,选择排序,堆排序
一、插入排序:
(1)直接插入排序:
思想:每次将一个待排序的数据,插入到前面已经有序的数列中的适当位置,使数列依然有序,直到待排序数据元素全部插入完毕。
public class InsertSort{
/**
* 排序算法的实现,对数组中指定的元素进行排序
* @param a 待排序的数组
* @param low 从哪里开始排序
* @param high 排到哪里
*/
public void sort(int[] a, int low, int high){
for(int i=low+1; i<=high; i++){
int tmp = a[i];
int j=i;
for(; j>low && tmp<a[j-1]; j--){ //与已有序的元素进行比较寻找插入位置
a[j] = a[j-1]; //在比较时移动元素位置
}
a[j] = tmp;
}
}
}
(2)二分(折半)插入排序:
思想:每次将一个待排序的数据,插入到前面已经有序的数列中的适当位置(使用折半查找法查找插入位置),使数列依然有序,直到待排序数据元素全部插入完毕。
public class BinInsertSort {
public void sort(int[] a, int low, int high){
for(int i=low+1; i<high; i++){
int lo = low, hi = i-1;
int tmp = a[i];
while(lo<=hi){ //折半寻找插入位置
int mid = (lo+hi)/2;
if(tmp < a[mid]){
hi = mid-1;
}else{
lo = mid+1;
}
}
for(int j=i-1; j>hi; j--){ //移动元素
a[j+1] = a[j];
}
a[hi+1] = tmp;
}
}
}
(3)希尔排序:
思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成(n除以d1)个组。所有的距离d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后取第二个增量d2<d1,重复上述分组和排序,直到所取增量dt=1,即所有记录放在同一组中进行直接插入排序为止。
public class ShellSort {
/**
* 排序算法的实现,对数组中指定的元素进行排序
* @param a 待排序的数组
* @param low 从哪里开始排序
* @param high 排到哪里
* @param delta 希尔排序的步长
*/
public void sort(int[] a, int low, int high, int[] delta){
for(int k=0; k<delta.length; k++){
partition(a, low, high, delta[k]);
}
}
private void partition(int[] a, int low, int high, int delta){
for(int i=low+delta; i<high; i++){
if(a[i]<a[i-delta]){
int tmp = a[i];
int j = i-delta;
for(; j>low&&tmp<a[j]; j=j-delta) //在已排好的有序表中寻找插入位置
a[j+delta] = a[j]; //在比较时移动元素位置
a[j+delta] = tmp;
}
}
}
}
二、交换排序:
(1)冒泡排序:
思想:每次将一个待排序的数据,插入到前面已经有序的数列中的适当位置,使数列依然有序,直到待排序数据元素全部插入完毕。
public class BubbleSort{
public void sort(int[] a, int low, int high){
int n = high - low + 1;
for(int i=low; i<n; i++){
for(int j=i; j<high-i; j++){
if(a[j]>a[j+1]){
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
}
}
}
}
}
(2)快速排序:
思想:在数组中任意取一数据作为基准数据,然后将所有比它小的数据放到它的前面,所有比它大的数据放到它的后面,这个过程称为一趟快速排序。然后对这两部分数据分别进行快速排序,依次递归直到所有数据有序。
public class QuickSort {
//一次快速排序,返回基准的位置,此位置把数组分成两个部分
private int partition(int[] r, int low, int high){
int pivot = r[low];
while(low<high){
while(low<high && r[high]>=pivot) high--;
r[low] = r[high];
while(low<high && r[low]<=pivot) low++;
r[high] = r[low];
}
r[low] = pivot;
return low;
}
public void sort(int[] r, int low, int high){
if(low<high){
//递归进行排序
int pa = partition(r, low, high);
sort(r, low, pa-1);
sort(r, pa+1, high);
}
}
}
二、选择排序:
(1)直接选择排序:
思想:第一次从所有元素中选择最小的元素与R[0]交换位置,接着从剩下的R[1]...R[n]中选择最小的元素与R[1]交换位置,依次类推,直到所有元素被选择完毕。
public class SelectSort {
public void sort(int[] a, int low, int high){
for(int i=low; i<high-1; i++){
int min = i;
for(int j=i+1; j<high; j++){ //选择关键字最小的元素,返回其在数组中的位置
if(a[min]>a[j]) min = j;
}
if(i!=min){ //关键字最小的元素与i位置的元素交换
int tmp = a[i];
a[i] = a[min];
a[min] = tmp;
}
}
}
}
(2)堆排序:
堆包括大顶堆和小顶堆:根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆,又称最小堆。根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆。
思想:将N个关键字的序列按关键字进行建堆(大顶堆或者小顶堆),输出堆顶元素,调整剩余的N-1个元素成为一个新堆,重复上述过程,直到所有元素都输出。
初始建堆:由初始序列得到完全二叉树,按层次从上到下第一个非叶子节点开始对其调整,然后对下一个非叶子节点进行调整,直到根节点为止。
设有M个元素的堆,输出堆顶元素后,剩下M-1个元素进行调整,输出元素后调整堆:
首先,将堆底元素送入堆顶,此时堆被破坏,其原因是根节点不满足堆的性质,而根节点的左右子树仍然是堆;
然后,将根结点左右子女中较大(较小)的进行交换。若与左孩子交换,则左子树谁被破坏,且仅左子树的根节点不满足堆的性质;若与右孩子交换,则与右子树交换,且仅右子树的根节点不满足堆的性质;
继续对不满足的子树进行上述交换操作,直到叶子节点,则堆被重建。
public class HeapSort {
private void heapAjust(int[] r, int low, int high){
int tmp = r[low];
for(int j=2*low; j<=high; j=j*2){ //沿关键字较大的元素向下进行筛选
//j指向关键字较大的元素
if(j<high&&r[j]<r[j+1]) j++;
//若tmp比其孩子都大,则插入到low所指位置
if(tmp>=r[j]) break;
r[low] = r[j]; low = j;
}
r[low] = tmp;
}
public void sort(int[] r){
int n = r.length-1;
for(int i=n/2; i>=1; i--) //初始化建堆
heapAjust(r, i, n);
for(int i=n; i>1; i--){ //不断输出堆顶元素并调整r[1...i-1]为新堆
int tmp = r[1]; //交换堆顶与堆底元素
r[1] = r[i];
r[i] = tmp;
heapAjust(r,1,i-1); //调整r[1...i-1]为新堆
}
}
}