一、分类总结
插入排序:都要求是在已经有序的序列中,插入新的元素,直到所有的元素插入完成。
选择排序:选择一个满足条件的放入有序队列,再遍历剩下的无序列表继续选择,直到所有数据有序。
交换排序:每次保证一个有序数到达相应位置
二路归并:在保证“二路”有序的基础上进行归并
基数排序:
二、部分算法代码实现(以升序排序为例)
一、插入排序
(一)直接插入排序
思想:从初始位置遍历数组,每一次遍历都想一个关键字按照其数值的大小插入到“已经排好的、部分有序”序列的适当位置,直到所有待排关键字都被插入到有序序列中为止。
稳定性:稳定的
时间复杂度:O(n^2)
public static void ZJinsertSort(int[] nums){
System.out.print("before sort:");
for (int i=0;i<nums.length;i++){
System.out.print(nums[i]+" ");
}
for(int i=1; i<nums.length;i++){
int temp = nums[i];
int j = i-1;
//while里面必须用temp,因为nums[i]会因为nums[j]的移动而发生变化
while(j>=0&&temp<nums[j]){
nums[j+1] = nums[j];
--j;
}
nums[j+1] = temp;
}
System.out.print("\nafter sort:");
for (int i=0;i<nums.length;i++){
System.out.print(nums[i]+" ");
}
}
before sort:51 35 60 88 76 9 17 51
after sort:9 17 35 51 51 60 76 88
(二)折半插入排序
思想:从头开始遍历,i为当前要插入的元素。对下标i对应的元素在0-i的有序序列中进行折半查找,找到要插入的数据的位置index,将i-1到index的元素后移,在index的位置插入i对应的值。
稳定性:稳定的
时间复杂度:O(n^2)
下面代码偷懒了…没有按照思路来,利用了java中ArrayList中的动态插入性质插入一个元素。
public static void ZBsrot(List<Integer> nums, int insertnum){
int index = ZBserach(nums,insertnum);
//java的ArrayList中add(int,Object)方法自动向后顺延,不会覆盖
nums.add(index,insertnum);
}
//查找插入位置
public static int ZBserach(List<Integer> nums,int n){
int low = 0,high = nums.size()-1;
while(low<high){
int mid = (low+high)/2;
int jnum = nums.get(mid);
if(jnum == n)
return mid;
if(jnum<n)
low = mid+1;
else
high = mid-1;
}
//查找位置返回查找失败的位置,如果是查找元素这里应该返回-1(查找失败)
return low;
}
//测试用例
List<Integer> list = new ArrayList<>(asList(0,1,2,5,6,7));
System.out.print("\n折半插入前:");
for(int i=0;i<list.size();i++){
System.out.print(list.get(i)+" ");
}
ZBsrot(list,4);
System.out.print("\n折半插入后:");
for(int i=0;i<list.size();i++){
System.out.print(list.get(i)+" ");
}
折半插入前:0 1 2 5 6 7
折半插入后:0 1 2 4 5 6 7
(三)希尔排序
思想:将无序队列拆分成子序列,将子序列利用直接插入排序后。再合并,合并后再拆分成更小的子序列,进行序列内部排序,排序后再合并。直到子序列的个数为1.完成排序
增量选取:1.希尔:每次增量除以2向下取整(n/2,n/4…2,1)n为序列长度。时间复杂度O(n^2)
2.帕佩尔诺夫和斯塔舍维奇:(2^k-1…65,33,17,9,5,3,1)
增量序列的最后一个值一定取1,增量序列中的值尽量没有除1之外的公因子。
稳定性:不稳定的
二、交换类排序
(一)起泡排序
思想:从先到后遍历数组中的元素,前后两个元素比较,如果前面的大后面的小,就交换,否则比较后面两个元素。一次起泡排序会将一个元素放到相应位置,当一趟排序过程中没有发生关键字变化,算法结束。
稳定性:稳定的
时间复杂度:O(n^2)
public static void QPsort(int[] nums){
int flag = 0;
for(int i = 0;i<nums.length;i++){
flag = 0;
for(int j = 0;j<nums.length-1-i;j++){
if(nums[j+1]<nums[j]){
int temp = nums[j+1];
nums[j+1] = nums[j];
nums[j] = temp;
flag =1;
}
}
//一趟比较之后没有发生未知的交换,说明排序完成
if(flag==0)
break;
}
}
起泡排序前: 51 35 60 88 76 9 17 51
起泡排序后: 9 17 35 51 51 60 76 88
(二)快速排序
思想:每次选择一个关键字,将序列中小于关键字的放到前面,大于关键字的移到后面。在分别在两个子序列中选择关键字对子序列排序。
稳定性:不稳定的
时间复杂度O(nlogn)
public static void KPsort(int[] nums,int low,int high){
int temp= nums[low];
int i= low,j=high;
if(low<high){
while(i<j){
while(j>i&&nums[j]>=temp) j--;
if(i<j){
nums[i]=nums[j];
i++;
}
while(j>i&&nums[i]<=temp) i++;
if(i<j){
nums[j] = nums[i];
j--;
}
}
nums[i] =temp;
KPsort(nums,low,i-1);
KPsort(nums,i+1,high);
}
}
快排前: 51 35 60 88 76 9 17 51
快排后: 9 17 35 51 51 60 76 88
三、选择排序
(一)简单选择排序
思想:遍历一次数组从无序序列中选择一个最小值放入已经排序好的序列中。
稳定性:不稳定的
时间复杂度:O(n^2)
public static void JDXZsort(int nums[]){
for(int i=0;i<nums.length;i++){
int min = i;
for(int j=i;j<nums.length;j++){
if(nums[j]<nums[min])
min = j;
}
int temp = nums[i];
nums[i] = nums[min];
nums[min] = temp;
}
}
简单选择排序前: 51 35 60 88 76 9 17 51
简单选择排序后: 9 17 35 51 51 60 76 88
(二)堆排序
思想:将序列构造成二叉树,每次将左右子树中的最大值换到根节点。然后将根节点取出就是最大值,再构建剩下的节点
稳定性:不稳定的
时间复杂度:O(nlogn)
public static void ShiftD(int[] nums,int low,int high){
//因为数组从0开始,所以其左子树应为2*i+1
int i = low,j=2*i+1;
int temp = nums[i];
while(j<=high){
//找到左右子树中大的那一个
if(j<high&&nums[j]<nums[j+1])
j++;
//左右子树中大的那个如果大于根,就替换根
if(temp<nums[j]){
nums[i] = nums[j];
i=j;
j=2*i+1;//跟新i继续向下判断
}
else break;
}
nums[i] =temp;
}
public static void Dsort(int[]nums){
int n = nums.length-1;
int temp;
//构建树
for(int i = (n-1)/2;i>=0;i--){
ShiftD(nums,i,n);
}
//每次将最大的放到最后,继续构建剩下的节点
for (int i=n;i>=1;--i){
temp = nums[0];
nums[0] = nums[i];
nums[i] = temp;
ShiftD(nums,0,i-1);
}
}
堆排前: 51 35 60 88 76 9 17 51
堆排后: 9 17 35 51 51 60 76 88
(三)二路归并排序
思想:序列一只有一个值,两两归并。序列二含有两个值,排序后,两两归并。序列三有四个值,排序后,两两归并。直到序列数与总数相同。
稳定性:稳定的
时间复杂度:O(nlogn)
//merge未实现
public static void mergeSort(int a[],int low,int high){
if(low<high){
int mid = (low+high)/2;
mergeSort(a,low,mid);
mergeSort(a,mid+1,high);
merge(a,low,mid,high);//将a数组,low-mid,mid+1,-high进行有序合并
}
}
四、基数排序
思想:分散与收集
稳定性:稳定的
三、代码复杂度和稳定性对比总结
快速记忆法:
O(nlogn):教官来啦“快(快速)些(希尔)归(归并)队(堆)”吧
心情“不稳定”:快(快排)些(希尔)选(简单选择)堆(堆) 朋友来聊天吧
快速排序适合无序的数列,对于有序的其效率低
插入排序 | 交换排序 | 选择排序 | 基数排序 | |
---|---|---|---|---|
分类 | 直接插入排序 折半插入 希尔排序 | 起泡排序 快速排序 | 简单选择 堆排序 二路归并 | 基数排序 |
时间复杂度 | O(n^2) O(n^2) O(nlogn) | O(n^2) O(nlogn) | O(n^2) O(nlogn) O(nlogn) | O(d(rd+n)) |
空间复杂度 | O(1) O(1) O(1) | O(1) O(logn) | O(1) O(1) O(n) | O(rd) |
稳定性 | 稳定 稳定 不稳定 | 稳定 不稳定 | 不稳定 不稳定 稳定 | 稳定 |