@skiery
【从王道视频总结改动整理得到,希望对大家找工作有帮助】
排序算法
排序的意义:将无序序列重新排列然后有序化。
算法稳定性:关键字相等的两个元素在排序后不改变两者的初始相对位置,则算法稳定,否则排序算法不稳定。但稳定性和排序性能无关。
1插入类排序
插入类排序:在一个有序的序列中,插入一个新的关键字,直到所有关键字都插入形成一个有序的序列。
1.1直接插入排序
首先以一个元素为有序序列,然后将后面的元素依次插入到有序的序列中合适位置直到所有元素都插入有序序列。
void InsertSort(int A[],int n){
int j ,temp;
for(int i=1;i<n;i++){
if(A[i]<A[i-1]){
temp = A[i];
for(j=i-1;A[j]>temp;j--){
A[j+1] = A[j];
}
A[j+1] = temp;
}
}
return ;
}
空间复杂度:是常数个辅助空间大小,只需要一个int位,所以空间复杂度为O(1);
时间复杂度:最坏情况下,整个数列逆序,所以时间复杂度近似高斯和公式,时间复杂度为O(n^2);最好的情况下,整个数列为顺序,时间复杂度近似O(n)。
稳定度:算法稳定(在排序条件中不加入=条件,则依次往后放)
1.2折半插入排序
先用折半查找到需要插入关键字的位置,再一次性移动元素,再插入该元素。
void HalfInsertSort(int A[],int n){
int temp,low,high,mid;
for(int i=1;i<n;i++){
temp = A[i];
low =0;high = i-1;
while(low<=high)//折半查找
{
mid = (low+high)/2;
if(A[mid]>temp){high = mid-1;}
else{low = mid+1;}
}
for(int j=high+1;j<n-1;j++){
A[j+1] = A[j];
}
A[high+1] = temp;
}
return ;
}
空间复杂度:O(4)
时间复杂度:折半插入排序相比直接插入排序只是在寻找待插入位置时比较的次数监视,每个带插入元素比较的次数大约再log2n(折半查找树),所以n个元素比较操作时间复杂对为O(nlog2n)。
但是在移动时,两者差距是一样的,所以时间复杂度还是为O(n^2)。
稳定性:和直接插入相同,稳定
1.3希尔排序
基本思想:本质还是插入排序,把待排序序列分成几个子序列,再分别对几个子序列进行直接插入排序。
分成子序列的原则:按照一定增量(一组元素中下标的差值)
stage1:(一般从n/2开始取Δ),以Δ=5为例,把对5同余的元素分在同一组,在组内进行直接插入排序,完成本轮希尔排序;
stage2:缩小增量缩小增量(Δ=第一轮Δ/2),本例中增量为2;
stage…:接着进行希尔排序直至Δ为1,结束排序。
//以10个元素为例
void ShellInsertSort(int A[],int n){
int temp,k;
for(int i=n/2;i>=1;i/=2){
for(int j=i;j<n;j++){
if(A[j]<A[j-i]){
temp = A[j];
for(k=j-i;k>=0&&A[k]>temp;k-=i){
A[k+i] = A[k];
}
A[k+i] = temp;
}
}
}
return ;
}
空间复杂度:O(2)
空间复杂度:时间复杂度约为O(n^1.3)
最坏是O(n^2)
稳定性:不稳定,受Δ选取的影响,同值元素会分离开。
2.交换类排序
交换类排序:根据序列中两个元素关键字的比较结果来交换它俩在序列中的位置
3.1冒泡排序
想象为气泡越重越下沉,越轻越上浮
过程:1)假设待排表长为n,从后往前两两比较相邻元素的值,若为逆序(即A[i-1]>A[i]),则交换他们,直到序列比较完;2)结果将最小的元素交换到代拍序列的第一个位置,下一趟冒泡是**,前一趟确定的最小元素不再参与比较**,待排序列减少一个元素,每趟冒泡的结果把序列中最小元素放在了序列的最终位置,这样进行最多n-1次冒泡就能把所有元素排好序。
void BubbleSort(int A[],int n){
int temp;
for(int i=0;i<n-1;i++){
bool flag = false; //标志位检测是否发生位置改变
for(int j=n-2;j>=i;j--){
if(A[j+1]<A[j]){
temp = A[j];
A[j]= A[j+1];
A[j+1] = temp;
flag = true;
}
}
if(flag == false)return ;
}
}
空间复杂度:O(1)
时间复杂度:最差情况,序列逆序,等差数列次操作O(n^2);
最好情况:第一次中比较发现为递增序列,所以时间复杂度为O(n);
稳定性:算法非常稳定;
3.2快速排序
思想方法:分治法;双指针
过程:每趟快排选择序列中的任何一个元素作为枢轴(通常选第一个元素),将序列中比枢轴效地元素都移到枢轴前边,大的放在后边。
int Partition(int A[],int low,int high){
int mid = A[0];
while(low<high){
while(low<high&&A[high]>=mid){//避免完全顺序的数组出现下溢
high--;
}
A[low] = A[high];
while(low<high&&A[low]<=mid){
low++;
}
A[high] = A[low];
}
A[low] = mid;
return low;
}
void FastSort(int A[],int low,int high){
if(low<high){
int mid = Partition(A,low,high);
FastSort(A,low,mid-1);
FastSort(A,mid+1,high);
}
return ;
}
时间复杂度:最好情况下(数据越复杂无规律算法效率越高):O(nlogn);
最坏情况下(待排序列非常有序):O(n^2)。
递归复杂度表达式:
T(n) = aT(n/b) + f(n) 第一部分为a个子问题复杂度,第二部分为划分问题所需时间
空间复杂度:由于快排为递归,需要借助工作栈保存调用信息,其容量与递归调用的最大深度相一致。
最好为:O(Log2(n+1))(每次Partition均匀)
最坏为:O(n),故栈的深度为O(n);
稳定性:发生元素与位置无关的交换,不稳定
4.选择类排序
方法:每趟在后面n-i+1(i=1,2,…,n-1)个待排元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到第n-1趟做完,待排序元素只剩下1个,不再选。
4.1简单选择排序
void SelectSort(int A[],int n){
int min_int;
for(int i=0;i<n-1;i++){
min_int = i;
for(int j=i+1;j<n;j++){
if(A[j]<min_int){
min_int = j;
}
}
if(min!=i){
int temp = A[i];
A[i] = A[min_int];
A[min_int] = temp;
}
}
return ;
}
空间复杂度:仅需要一个int型保存中间变量,O(1);
时间复杂度:关键操作在于交换元素操作,双循环共执行n-1~1次,所以时间复杂度为O(n^2);
稳定性: 不稳定、在交换时会打破相对顺序。
4.2堆排序
什么是堆:一颗完全二叉树,且满足任何一个非叶结点值都不大于(或不小于)其左右子结点的值。
1.每个结点的值都不小于其左右子结点的值,称为大顶堆;
2.每个结点的值都不大于其左右子结点的值,称为小顶堆。
堆排序思想:每次将无序序列调节成一个堆,然后从堆中选择堆顶元素值,将值加入有序序列,无序序列减少一个,再反复调节无序序列,直至所有关键字都加入有序序列。
stage 1
建堆,对初始序列的完全二叉树调整成一个大顶堆;二叉树由下至上由左至右(数组下标由大到小),检查每个节点是否满足大顶堆要求,不满足则调整。
stage 2
将堆顶节点和最后一个结点19交换,将最大值92移动到数组的最末,有序序列中加入了最大结点92,无序序列减少了结点92,一次排序完成。
stage 3
调堆,调整二叉树结点使得满足大顶堆要求,方法同stage 1建堆过程。
//代码实现待补充
5.归并排序
假定待排序表含有n个记录,则可以看成是n个有序的字表,1)每个子表长度为1,两两归并,得到[n/2]个长度为2或1的有序表;2)再两两归并,重复直至合成长度为n的有序表,在此称为2-路归并排序表。
5.1二路归并
int *B = (int *)malloc((n+1)*sizeof(int)); //辅助数组(动态分配内存)
void Merge(int A[],int low,int mid,int high){
for(int k=low;k<=high;k++){
B[k] = A[k];
}
for(int i=low,j=mid+1,k=i;i<=mid&&j<=high;k++)//k为归并后下标计数器
if(B[i]<=B[j]){
A[k]=B[i++];
}
else{
A[k] = B[j++];
}
while(i<=mid) A[k++] = B[i++];
while(j<=high) A[k++] = B[j++];
return ;
}
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);
}
return ;
}
空间复杂度:申请分配了动态内存,故空间复杂度为O(n)
时间复杂度:第一趟归并时间复杂度为O(n/22) = O(n);第二趟归并:O(n/44)=O(4),最终一共执行了k=log2\n次,每趟排序复杂度都是O(n),所以整个归并排序的时间复杂度为O(nlog2\n)
稳定性:稳定