快排基于分治思想,几乎是最快的排序方式,被评为20世纪是十大算法之一。
//快排
public void quickSort(int data[],int start,int end){
if(startint partition=partition(data,start,end);
quickSort(data,0, partition-1);
quickSort(data,partition+1, end);
}
}
//确定pivot
public int partition(int data[],int start,int end){
int pivot=data[start];
int left=start+1;
int right=end;
while(left<=right){
while(left<=right&&data[left]<=pivot){
left++;
}
while(left<=right&&data[right]>pivot){
right--;
}
if(leftreturn right;
}
//数据交换
public void swap(int[] data, int x, int y) {
int temp=data[x];
data[x]=data[y];
data[y]=temp;
}
平均时间– O(nlog2n)
快排在平均情况下的时间复杂度比较难算,记住就行了。
最好时间– O(nlog2n)
最好情况下,每次找到的pivot点应该是 N2 的位置。
所以:
最坏时间– O(n2)
首先明确快排的最坏情况是:原序列有序。
空间复杂度–平均条件下– O(logn)
同时间复杂度一样,求解过程较为麻烦,记住结论就好了
空间复杂度–最好条件下– O(logn)
最好条件下,每次取中值递归,递归树深度为 log2n 。其空间复杂度也就为 O(logn)
归并排序也是基于分治法的思想,速度和快排差不多,不过空间占用稍大,具有稳定性。
//归并排序
public void mergeSort(int[] data,int start,int end){
if(startint mid=(start+end)/2;
mergeSort(data, start, mid);
mergeSort(data, mid+1, end);
mergeChild(data, start, mid, end);
}
}
//合并左右孩子
public void mergeChild(int []data,int start,int mid,int end){
int len=end-start+1;
int []temp=new int[len];
int left=start,right=mid+1;
for(int i=0;iif(left>mid){
temp[i]=data[right++];
continue;
}
if(right>end){
temp[i]=data[left++];
continue;
}
if(data[left]<=data[right]){
temp[i]=data[left++];
}else{
temp[i]=data[right++];
}
}
for(int i=0;i
平均时间– O(nlog2n)
归并排序中,每一次的mid点都是 end−start2 ,所以:
最好时间– O(nlog2n)
最好情况和平均情况一样,每次的mid点都是 end−start2 ,所以,最好情况和平均情况一样,都是 O(nlog2n)
最坏时间– O(nlog2n)
最坏情况和平均情况一样,每次的mid点都是 end−start2 ,所以,最坏情况和平均情况一样,都是 O(nlog2n)
空间复杂度–平均条件下– O(n)
关于平均条件下的空间复杂度,书上说的是 O(n) ,但是它把mergeChild函数里申请的临时变量int []temp也算进去了,应该是在递归外层申请了一个大小为n的数组,这样一来,空间复杂度就变成了 n+log2n ,也就是 O(n) 。但是我上面的代码,把这个临时变量写到了mergeChild中,函数执行完毕自动回收,也就不算是空间占用了,此时的空间复杂度和快排的一样,都是 O(logn) 。不过我看网上都是说的 O(n) ,我这里也写成 O(n) 了。也许我的理解有误,还请知道的指点一下,谢谢。
空间复杂度–最好条件下– O(n)
同上,是 O(n) 。
冒泡排序理解起来较为简单。具有稳定性。时间复杂度较高,空间复杂度较低。了解一下吧。
public void bubbing(int[] data){
int len=data.length;
for(int i=0;iint tag=0;
for(int j=0;j1;j++){
if(data[j]>data[j+1]){
tag=1;
int temp=data[j];
data[j]=data[j+1];
data[j+1]=temp;
}
}
if(tag==0)return;
}
}
平均时间– O(n2)
乱序排列的复杂度。
最好时间– O(n)
顺序排列的复杂度。
空间复杂度–平均条件下– O(1)
空间复杂度–最好条件下– O(1)
空间复杂度–最坏条件下– O(1)
堆排序利用最大(小)堆的性质进行排序。使用最大堆时,先构建一棵最大堆,然后不断取顶部节点,得到的序列就是有序的。
最大堆是一棵所有根节点都大于子节点的完全二叉树;最大堆的最大元素是顶部根节点,第二大是根节点的两个子节点的其中的一个,第三大不确定,有可能是根节点的子节点的另外一个,也可能是子节点中较大的节点的子节点中的一个。最小堆同理。
//堆排序
public void heapSort(int[] data) {
if(data==null||data.length==0)return;
int n=data.length;
buildMaxHeap(data);
for(int i=0;i0, n-i-1);
shiftDown(data, 0, n-i-1);
}
}
//构建最大堆
public void buildMaxHeap(int[] data){
int n=data.length;
int x=n/2-1;
for(int i=x;i>=0;i--){
shiftDown(data, i, n);
}
}
//下筛
public void shiftDown(int []data,int x,int n){
int l=2*x+1,r=2*x+2;
int max=x;
if(ldata[max])max=l;
if(rdata[max])max=r;
if(max!=x){
swap(data, max, x);
shiftDown(data, max, n);
}
}
//数据交换
public void swap(int[] data,int x,int y){
int temp=data[x];
data[x]=data[y];
data[y]=temp;
}
最大堆构建的时间复杂度为 O(n) ,下筛的时候,总共n-1次循环,一次下筛的复杂度为 O(log2n) (具有n个节点的二叉树最高为 log2n+1 )。所以总的复杂度为 O(nlogn)
最好时间– O(nlogn)
最坏时间– O(nlogn)
空间复杂度–平均条件下– O(1)
只需要一个临时变量,所以为 O(1)
空间复杂度–最好条件下– O(1)
空间复杂度–最坏条件下– O(1)
希尔排序是直接插入排序的改进,复杂度可以降到O(n^1.3).
主要流程是:
1. 初始化增量d=n/2
2. 分别对d个数列进行插入排序
3. d=d/2,然后执行步骤2.
public void shellSort(int[]data){
if(data==null||data.length==0)return;
int n=data.length;
int d=n/2;
while(d>=1){
for(int k=0;kfor(int i=k+d;iint temp=data[i];
int j=i-d;
while(j>=k&&data[j]>temp){
data[j+d]=data[j];
j-=d;
}
data[j+d]=temp;
}
}
d=d/2;
}
}
最坏时间– O(n1.3)
空间复杂度–平均条件下– O(1)
空间复杂度–最好条件下– O(1)
空间复杂度–最坏条件下– O(1)
最后,总结一下各个排序方法的复杂度和稳定性:
排序算法 | 最好情形(时间 ~ 空间) | 平均情形(时间 ~ 空间) | 最坏情形(时间 ~ 空间) | 稳定性 |
---|---|---|---|---|
快速排序 | O(nlogn) ~ O(logn) | O(nlogn) ~ O(logn) | O(n2) ~ O(n) | 不稳定 |
归并排序 | O(nlogn) ~ O(logn) | O(nlogn) ~ O(logn) | O(nlogn) ~ O(n) | 稳定 |
冒泡排序 | O(n2) ~ O(1) | O(n2) ~ O(1) | O(n) ~ O(1) | 稳定 |
堆排序 | O(nlogn) ~ O(1) | O(nlogn) ~ O(1) | O(nlogn) ~ O(1) | 稳定 |
希尔排序 | O(n1.3) ~ O(1) | O(n1.3) ~ O(1) | O(n1.3) ~ O(1) | 不稳定 |
KMP算法是字符串匹配的高效算法,朴素的匹配算法的时间复杂度为 O(n∗m) ,KMP匹配算法的时间复杂度可以达到 O(n+m) ,当m很小n很大时,这个复杂度可以逼近 O(n) ,是一个很巧妙的算法。
//根据计算得到的next数组进行模式字符串的查找。
public int matcher(String t,String p){
if(t==null||p==null)return -1;
int [] next = next(p);
int tLen=t.length();
int pLen=p.length();
for(int i=0,j=0;iwhile (j>0&&t.charAt(i)!=p.charAt(j)){
j=next[j-1];
}
if(t.charAt(i)==p.charAt(j)){
j++;
}
if(j==pLen){
return i-j+1;
}
}
return -1;
}
//计算next数组
public int[] next(String p){
int n=p.length();
int [] next=new int[n];
next[0]=0;
for(int i=1;iint k=next[i-1];
while (k>0&&p.charAt(k)!=p.charAt(i)){
k=next[k-1];
}
if(p.charAt(k)==p.charAt(i)){
k++;
}
next[i]=k;
}
return next;
}
KMP匹配算法的时间复杂度可以达到 O(n+m) ,当m很小n很大时,这个复杂度可以逼近 O(n) ,是一个很巧妙的算法。