排序算法的时间复杂度与稳定性
稳定指相同值是否会被打乱。
1.关于稳定性:
不稳定:快选堆希(快速排序、选择排序、堆排序、希尔排序)
稳 定:插冒归计基(简单插入排序、冒泡排序、归并排序、计数排序、基数排序)
2.关于移动次数和关键字顺序无关的排序
顺口溜:一堆(堆排序)海龟(归并排序)选(选择排序)基(基数排序)友
冒泡排序(时间:O(n2), 空间:O(1))
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
时间复杂度:最好O(n),最坏O(n2)。
package Algorithm.sortex;
public class Maopao1 {
public static void sort(int a[]){
if(a.length<=1){
return;
}
for(int i =0;i<a.length-1;i++) {
for(int j=0;j<a.length-i-1;j++) {//-1为了防止溢出
if(a[j]>a[j+1]) {
int temp = a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
}
}
public static void main(String[] args) {
int a[]={5,8,4,1,5};
sort(a);
}
}
选择排序(时间O(n2),空间O(1))
选择i位置后最小的元素与i位置交换。
找最小元素时,只要记录下标,避免了重复的值交换,比冒泡要快。
package Algorithm.sortex;
import java.util.Arrays;
public class StraightSelectSorting {
public static void sort(int a[]){
if(a.length<=1){
return;
}
for(int i=0;i<a.length;i++){
int minindex=i;//无序区的最小数据数组下标
for(int j=i+1;j<a.length;j++){//在无序区中找到最小数据并保存其数组下标
if(a[j]<a[minindex]){
minindex=j;
}
}
//将最小元素放到本次循环的前端
int temp=a[i];
a[i]=a[minindex];
a[minindex]=temp;
}
}
public static void main(String[] args){
int a[]={5,8,4,1,5};
sort(a);
System.out.println(Arrays.toString(a));
}
}
插入排序(时间O(n2),空间O(1))
直接插入排序
其过程如插扑克。
package Algorithm.sortex;
public class InsertSort {//插入排序
public static void sort(int[] a){
for(int i=1;i<a.length;i++){
for(int j=i;j>0;j--){
if(a[j]<a[j-1]){
int temp=a[j];
a[j]=a[j-1];
a[j-1]=temp;
}else{
break;
}
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int a[]={3,1,4,2,7,8,6,5};
sort(a);
}
}
二分插入排序
链表插入排序
希尔排序
归并排序(时间:O(n log n) 空间:O(n))
速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列
思想可用于小和,逆序对。
package Algorithm.sortex;
public class MergeSort {//该思想可用于解小和问题,逆序对问题
/-归并排序-/
public static void gb(int a[]){
if(a.length<2||a==null){
return;
}
gb(a,0,a.length-1);//要注意边界
}
public static void gb(int a[], int L, int R) {
if(R==L) return;//递归一定要判断返回,不然出错
int mid = L + ((R -L) >> 1);//安全,不溢出
gb(a, L, mid);
gb(a, mid + 1, R);
merge(a, L, R, mid);
}
public static void merge(int a[], int L, int R, int mid) {
int help[] = new int[R - L + 1];//引入help数组
int p1 = L;
int p2 = mid + 1;
int i = 0;
while ((p1 <= mid) && (p2 <= R)) {
help[i++] = a[p1] < a[p2] ? a[p1++] : a[p2++];
}
while (p2 <= R) {
help[i++] = a[p2++];
}
while (p1 <= mid) {
help[i++] = a[p1++];
}
for (int j = 0; j < help.length; j++) {
a[L + j] = help[j];
}
}
//-小和问题-///
public static int xh(int a[]){
if(a.length<2||a==null){
return 0;
}
return xh(a,0,a.length-1);//要注意边界
}
public static int xh(int a[], int L, int R) {
if(R==L) return 0;
int mid = L + ((R -L) >> 1);
return xh(a, L, mid)+xh(a, mid + 1, R)+Xiaohe(a, L, R, mid);
}
public static int Xiaohe(int a[], int L, int R, int mid) {
int help[] = new int[R - L + 1];
int p1 = L;
int p2 = mid + 1;
int i = 0;
int res=0;
while ((p1 <= mid) && (p2 <= R)) {
res+=a[p1]<a[p2]?a[p1]*(R-p2+1):0;
help[i++] = a[p1] > a[p2] ? a[p1++] : a[p2++];
}
while (p2 <= R) {
help[i++] = a[p2++];
}
while (p1 <= mid) {
help[i++] = a[p1++];
}
for (int j = 0; j < help.length; j++) {
a[L + j] = help[j];
}
return res;
}
public static void main(String[] args) {
int res=0;
int a[]=new int[]{4,2,7,3,1};
System.out.println(xh(a));
gb(a);// TODO Auto-generated method stub
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
}
}
快速排序(时间:O(nlogn),最好为O(nlogn),最差为O(logn2),空间:O(logN)?)
选取最后一个,第一个,或者随机一个数作为分割点。
随机选取数时,可以和最后一个数交换。
可用递归实现。
空间复杂度:
首先就地快速排序使用的空间是O(1)的,也就是个常数级;而真正消耗空间的就是递归调用了,因为每次递归就要保持一些数据;
最优的情况下空间复杂度为:O(logn) ;每一次都平分数组的情况
最差的情况下空间复杂度为:O( n ) ;退化为冒泡排序的情况
package Algorithm.sortex;
public class Quiksort {
public static void sort(int a[]){
if(a.length<2||a==null){
return;
}
sort(a, 0, a.length-1);
}
public static void sort(int a[],int L,int R){
if(L>=R) return;
int rI=(int)(Math.random()*(R-L+1));//随机选取一个数
swap(a,L+rI,R);
//System.out.println(rI);
int p[]=partition(a,L,R);
sort(a,L,p[0]);
sort(a,p[1],R);
}
public static int[] partition(int a[], int L, int R) {
int less = L - 1;//表示所有小于比较位a[r]的最后一位
int more = R;//表示所有大于比较位a[r]的最前一位
int index = L;//表示需要比较的位置
int p[]=new int[2];
while (index < more) {
if (a[index] == a[R]) {//a[R]为用于比较的数
index++;//若等于,则不动
}
else if (a[index] < a[R]) {//若小于,则当前数与最后的小于数的后一位(该数等于or就是当前数)交换
swap(a, ++less, index++);
}
else if (a[index] > a[R]) {//若大于,当前数与more前一个数交换,当前比较数下表不变(换回来的是没判断过的数)
swap(a, --more, index);
}
}
swap(a,more,R);
p[0]=less;
p[1]=more+1;//把R排出去
return p;
}
public static void swap(int a[], int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public static void main(String[] args) {
int a[]=new int[]{2,5,3,6,1,4,6,9,2,3};
//partition(a,0,9,4);// TODO Auto-generated method stub
sort(a);
for(int i=0;i<a.length;i++){
System.out.print(a[i]);
}
}
}
堆排序
https://www.cnblogs.com/chengxiao/p/6129630.html
- 把数组变为大根堆(任何一棵子树最大值都为子树根)
转换为大根堆:
2.堆顶和最后一个位置做交换
3.前n个数做heapify
4.逐步排序(重复1,2,3)
Heapify:结点值变小了,找到孩子中最大的与之交换
HeapInsert:新节点加入到堆,同时往上调整的过程
package Algorithm.sortex;
public class Heapsort {
public static void sort(int a[]){
if((a==null)||(a.length<2)){
return;
}
for(int i=0;i<a.length;i++){
HeapInsert(a,i);//生成大根堆
}
for(int i=a.length-1;i>0;i--){
swap(a,i,0);//最后叶节点与堆顶交换
Heapify(a,0,i);//数组大小为i
}
}
public static void HeapInsert(int a[], int index) {//新节点加入到堆,同时往上调整的过程
while ((a[index] > a[(index - 1) / 2]) && (index > 0)) {//父节点=(子节点-1)/2
swap(a, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
public static void Heapify(int a[], int index, int size) {//节点值变小了,找到孩子中最大的与之交换
int left = 1;
while (left < size - 1) {
int temp = ((a[left] > a[left + 1]) || (left + 1 == size - 1)) ? left : left + 1;
if (a[temp] <= a[index])
break;
swap(a, index, temp);
index = temp;
left = index * 2 + 1;
}
}
public static void swap(int a[], int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public static void print(int a[]) {
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
}
public static void main(String[] args) {
int a[] = new int[]{7,4,5,2,6,10,8,3,1,9};// TODO Auto-generated method stub
sort(a);
print(a);
}
}
堆排复杂度计算
堆排序的时间复杂度,主要在初始化堆过程和每次选取最大数后重新建堆的过程;
初始化建堆过程时间:O(n)
推算过程:
首先要理解怎么计算这个堆化过程所消耗的时间,可以直接画图去理解;
假设高度为k,则从倒数第二层右边的节点开始,这一层的节点都要执行子节点比较然后交换(如果顺序是对的就不用交换);倒数第三层呢,则会选择其子节点进行比较和交换,如果没交换就可以不用再执行下去了。如果交换了,那么又要选择一支子树进行比较和交换;
那么总的时间计算为:s = 2^( i - 1 ) * ( k - i );其中 i 表示第几层,2^( i - 1) 表示该层上有多少个元素,( k - i) 表示子树上要比较的次数,如果在最差的条件下,就是比较次数后还要交换;因为这个是常数,所以提出来后可以忽略;
S = 2^(k-2) * 1 + 2^(k-3)*2.....+2*(k-2)+2^(0)*(k-1) ===> 因为叶子层不用交换,所以i从 k-1 开始到 1;
这个等式求解,我想高中已经会了:等式左右乘上2,然后和原来的等式相减,就变成了:
S = 2^(k - 1) + 2^(k - 2) + 2^(k - 3) ..... + 2 - (k-1)
除最后一项外,就是一个等比数列了,直接用求和公式:S = { a1[ 1- (q^n) ] } / (1-q);
S = 2^k -k -1;又因为k为完全二叉树的深度,所以 (2^k) <= n < (2^k -1 ),总之可以认为:k = logn (实际计算得到应该是 log(n+1) < k <= logn );
综上所述得到:S = n - longn -1,所以时间复杂度为:O(n)
更改堆元素后重建堆时间:O(nlogn)
推算过程:
1、循环 n -1 次,每次都是从根节点往下循环查找,所以每一次时间是 logn,总时间:logn(n-1) = nlogn - logn ;
综上所述:堆排序的时间复杂度为:O(nlogn)
计数排序,基数排序,桶排序
计数排序
基数排序(O (nlog(r)m),其中r为所采取的基数,而m为堆数)
桶排序
基本思想
桶排序的基本思想是将一个数据表分割成许多buckets,然后每个bucket各自排序,或用不同的排序算法,或者递归的使用bucket sort算法。也是典型的divide-and-conquer分而治之的策略。它是一个分布式的排序,介于MSD基数排序和LSD基数排序之间。
建立一堆buckets;
遍历原始数组,并将数据放入到各自的buckets当中;
对非空的buckets进行排序;
按照顺序遍历这些buckets并放回到原始数组中即可构成排序后的数组。
桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。当然桶排序的空间复杂度为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。