选择排序
一种最简单的算法:
首先找到数组的最小的元素跟一个元素交换位置,其次在接下来的元素中找到最小的元素与数组的第二个元素交换,如此往复。
特点:
- 运行时间与输入无关:需要N^2/2次比较
- 数据移动是最少的:只需N次交换
- 不稳定,例如:(2,2,5,4,7,8,1)
import java.util.*;
public class Selection
{
public static void main(String[] args){
int[] arr = new int[]{1,6,43,2,3,65,98,54,2,8,0,9};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr){
for(int i = 0; i < arr.length;i++){
int min = i;
for(int j = i;j<arr.length;j++){
if(arr[j] < arr[min]){
min = j;
}
}
exch(arr,i,min);
}
}
public static void exch(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
冒泡排序
从左到右不断交换相邻逆序的元素,在一轮的循环之后,可以让未排序的最大元素上浮到右侧。
在一轮循环中,如果没有发生交换,就说明数组已经是有序的,此时可以直接退出。
- 稳定的排序,时间复杂度O(N^2)
import java.util.*;
public class Bubble{
public static void main(String[] args){
int[] arr = new int[]{1,6,43,2,3,65,98,54,2,8,0,9};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public void sort(int[] arr){
int N = arr.length;
for(int i = N-1;i>=0;i--){
for(int j = 0;j<i;j++){
if(arr[j] > arr[j+1]){
swap(arr,j,j+1);
}
}
}
}
public static void exch(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
插入排序
通过将每个数插入已经排序好的数组中的适当位置,同时其余元素需要右移一位。
特点:
- 最坏需要N^2/2次比较跟N^2/2次交换,最好的情况需要N-1次比较跟0次交换;平均需要N^2/4次比较跟N^2/4次交换。
- 稳定
import java.util.*;
public class Insertion
{
public static void main(String[] args){
int[] arr = new int[]{1,6,43,2,3,65,98,54,2,8,0,9};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr){
for(int i = 0 ; i < arr.length;i++){
for(int j = i;j>0 && arr[j] < arr[j-1] ;j--){
exch(arr,j,j-1);
}
}
}
public static void exch(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
希尔排序
思想:
- 使数组中任意间隔为h的元素都是有序的
- 希尔排序为了加快速度改进了插入排序,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序
-
不稳定
import java.util.*; public class Shell { public static void main(String[] args){ int[] arr = new int[]{1,6,43,2,3,65,98,54,2,8,0,9}; sort(arr); System.out.println(Arrays.toString(arr)); } public static void sort(int[] arr){ int h = 1; int N = arr.length; while( h < N/3) h = 3*h + 1; while(h >=1){ for(int i = h; i<N;i++){ for(int j = i;j>=h && arr[j] < arr[j-h];j -= h){ exch(arr,j,j-h); } } h = h/3; } } public static void exch(int[] arr,int i,int j){ int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
堆排序
堆(大根堆):每个节点都大于它的两个字节点
堆排序是通过堆这种数据结构而设计的排序算法,踏实一种选择排序,
它最好、最坏、平均的时间复杂度都为 O(NlogN)、
思想:
- 将待排序的序列构成一个大顶堆,此时,整个序列的最大值就是根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余N-1的元素重新构造成一个堆,这样就会得到N个元素的最小值。如此反复,便能得到一个有序序列。
- 不稳定
import java.util.*;
public class Heap
{
public static void main(String[] args){
int[] arr = new int[]{1,6,43,2,3,65,98,54,2,8,0,9};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr){
int N = arr.length - 1;
//将数组构建成堆
for(int i = (N+1)/2 ;i >= 0 ;i--){
sink(arr,i,N);
}
while(N > 0){
exch(arr,0,N--);
sink(arr,0,N);
}
}
//由上至下的调整堆
public static void sink(int[] arr,int k,int N){
while(2 * k <= N){
int j = 2 * k;
if(j < N && arr[j] < arr[j+1]) //如果右子数大于左子树,那么将指针j指向右子树
j++;
if(arr[k] < arr[j]){ //如果父节点小于子节点的值,则将父节点的值和子节点互换
exch(arr,k,j);
k = j;
}else{
break;
}
}
}
public static void exch(int[] arr,int i ,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
归并排序
归并排序是递归地将数组分为两半进行分别排序,然后将结果归并起来。
特点:
- 时间为:O(NlogN),
- 拆分之后的层数为logN,且每层合并都是N,故时间复杂度为O(NlogN);拆分可以认为是O(1),
- 时间复杂度:总运算次数表达式受n变化影响最大的那一项且不包含系数. 故(N+1)logN = NlogN。
- 空间为O(N),需要通过一个辅助数组进行对数据的存储
- 稳定
将两个有序的数组排序成一个数组:
通过两个i,j两个指针分别指向两个数组,比较指针所执行的值,将小的那个值赋值给新数组,同时该指针自增。
import java.util.*;
public class Merge
{
public static void main(String[] args){
int[] arr = new int[]{1,6,43,2,3,65,98,54,2,8,0,9};
int[] temp = new int[arr.length];
sort(arr,0,arr.length-1,temp);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr,int left,int right,int[] temp){
if(left < right){
int mid = (left + right) / 2;
sort(arr,left,mid,temp);
sort(arr,mid + 1,right,temp);
merge(arr,left,mid,right,temp);
}
}
public static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left , j = mid + 1;
for(int k = left;k<=right;k++){
temp[k] = arr[k]; //将数组arr[left .. right]复制到temp[left .. right]
}
for(int k = left;k <= right;k++){
if(i > mid)
arr[k] = temp[j++];
else if(j > right)
arr[k] = temp[i++];
else if(temp[i] < temp[j])
arr[k] = temp[i++];
else
arr[k] = temp[j++];
}
}
}
快速排序:
快速排序是一种分治的排序算法,将一个数组分成两个子数组,将两部分独立地排序。
跟归并排序的区别:
- 归并排序是将数组分成两个子数组分别排序,并将所用的子数组归并以将整个数组排序。
- 而快速排序是当两个子数组都有序时整个数组也就有序了
特点:
- 原地排序(只需要一个很小的辅助栈)
- 时间复杂度为 O(NlogN)
- 不稳定,比如序列( 5 3 3 4 3 8 9 10 11)
import java.util.*;
public class Quick
{
public static void main(String[] args){
int[] arr = new int[]{1,6,43,2,3,65,98,54,2,8,0,9};
sort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr,int left,int right){
merge(arr,left,right);
}
public static void merge(int[] arr,int left,int right){
if(left < right){
int i = left , j = right , t = arr[i]; //以最左边(left)的数为基准
while( i < j){
while(arr[j] >= t && i < j) //从右到左,找到第一个小于t的数
j--;
arr[i] = arr[j];
while(arr[i] <= t && i < j){ //从左到右,找到第一个大于t的数
i++;
}
arr[j] = arr[i];
}
//最后将(基准)t的值赋值给i,此时i左边的数都小于t,i右边的数都大于t
arr[i] = t;
merge(arr,left,i-1);
merge(arr,i+1,right);
}
}
}
总结
稳定的算法:
- 冒泡排序,插入排序,归并排序,其他为不稳定
时间复杂度:
- 冒泡、选择、插入:O(N^2)
- 快速、堆、归并:O(NlogN),其中归并需要额外的空间O(N)
快排的基准的三种选取
- 固定位置:选择第一个或最后一个,这种选取方式当遇到有序序列时,效率很低,为O(N^2)
- 随机位置:选取待排序列中任意一个数作为基准值。
- 三数取中法:取第一个值,最后一个值,第N/2的值即中间数为基准值,这种为三种中效率最高的。
快排的优化:
- 当排序数组序列的长度分割到一定大小后,试用插入排序
优化原因:对于待排序的序列长度很小或是基本趋于有序时,快排的效率还是插排好。
- 三向切分:对于有大量重复元素的数组,可以将数组切分为三部分,分别对应小于、等于和大于切分元素。三向切分快速排序对于只有若干不同主键的随机数组可以在线性时间内完成排序。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。