插入排序
直接插入排序
//直接插入排序
void InsertSort(Elemtype A[],int n)
{
int j;
for(int i=2; i<=n; i++) //i=0处用来放哨兵
{
A[0]=A[i];
for(j=i-1; A[0]<A[j]; j--) //不按下标来控制,只看元素大小
{
A[j+1]=A[j];
}
A[j+1]=A[0];
}
}
折半插入排序
//折半插入排序
void HalfInsertSort(Elemtype A[],int n)
{
int i,j,low,mid,high;
for(int i=2; i<=n; i++) //i=0处用来放哨兵
{
A[0]=A[i];
low=1;
high=i-1;
while(low<=high)
{
mid=(low+high)/2;
if(A[mid]>A[0])
high=mid-1;
else
low=mid+1;
}
//如果没有找到相等的元素,那么最后high会到low的左边或者low跑到high的右边,high的右边都是更大的元素,low的左边都是更小的元素
//如果找到了相等的元素,由于这里没有return,只会发生low跑到high的右边才会退出循环,此时high指向相等的元素,只有high+1右移,稳定性也能得到保证
for(j=i-1; j>=high+1; j--) //high+1非常妙
{
A[j+1]=A[j];
}
A[j+1]=A[0];
}
}
希尔排序
//希尔排序
//先写出插入排序,然后外面套一个缩减步长的循环,并且把所有的步长1换成dk
void ShellSort(Elemtype A[],int n)
{
int j; //必须在外面定义,因为最后一步需要把哨兵放回j+dk位置
for(int dk=n/2; dk>=1; dk/=2) //dk为步长 //步长为1时退化为插入排序
{
for(int i=dk+1; i<=n; i++) //以dk为步长进行插入排序
{
A[0]=A[i]; //设置哨兵
for(j=i-dk; j>0&&A[0]<A[j]; j-=dk) //步长较大时,比较时可能会直接越界
A[j+dk]=A[j];
A[j+dk]=A[0];
}
}
}
交换排序
冒泡排序
//冒泡排序
//可以从后开始把小的元素前移,也可以从前开始把大的元素后移
void BubbleSort(Elemtype A[],int n)
{
bool flag; //用flag来观察一趟排序中有没有交换元素,如果没有,说明序列已经有序
for(int i=0; i<n-1; i++) //第i轮表示已经有i个元素已经冒泡了,进行n-1轮,最后一个元素不用排了
{
flag=false; //使用flag可以在序列有序时(在某一轮中,没有进行交换),提前结束程序
for(int j=n-1; j>i; j--) //i=j不能取,因为后面会访问A[j-1],取了就越界了。代入i=0看一下就知道了(j-1最终冒到最上面=0,那么j最小=1)
if(A[j-1]>A[j])
{
swap(A[j-1],A[j]);
flag=true;
}
if(flag==false)
return; //某一轮中,没有一次交换,说明序列已经有序,可以提前结束
}
}
快速排序
//挖坑法分割,只能low和high交替填坑(不能一边连续找两个大于或小于pivot的元素)
int Partition1(Elemtype A[],int low,int high)
{
Elemtype pivot=A[low];
while(low<high)
{
while(low<high&&A[high]>pivot) //因为默认low为pivot,所以从high开始找元素填空
high--;
A[low]=A[high];
while(low<high&&A[low]<pivot) //为什么内层要重复判断low<high,因为在内层循环过程中,随时可能提前结束
low++;
A[high]=A[low];
}
A[low]=pivot; //此时high=low,用哪个下标都行
return low; //把已经定位的元素小标传出来,用来划分下一轮子区间
}
//遍历法分割,比较简单(以最右边元素为pivot)
int Partition2(Elemtype A[],int low,int high)
{
int left=low; //只用来指向小于pivot的元素
for(int i=low; i<high; i++) //遍历pivot后面所有元素
{
if(A[i]<A[high]) //i始终比left走的快,所以不会产生left和i指向两个小于pivot的元素,导致小的元素遗留在右边
{
swap(A[i],A[left]); //每找到一个小于pivot的元素,与left处交换(可以保证left前面所有元素都是小于pivot的,left和i之间所有元素都是大于pivot的)
left++;
}
}
swap(A[left],A[high]); //最后,把pivot放到应该在的位置
return left;
}
//快速排序主函数
void QuickSort(Elemtype A[],int low,int high)
{
if(low<high) //分割到子区间只有一个元素为止
{
Elemtype pivot=Partition2(A,low,high);
QuickSort(A,low,pivot-1);
QuickSort(A,pivot+1,high);
}
}
选择排序
简单选择排序
//简单选择排序
void SelectSort(Elemtype A[],int n)
{
for(int i=0; i<n-1; i++) //执行完n-1轮,最大的元素已经在最后一个位置
{
int min=A[i]; //假设第i个元素为当前轮的最小元素
int index=i;
for(int j=i+1; j<n; j++)
{
if(A[j]<min)
{
min=A[j];
index=j;
}
}
if(A[i]!=A[index])
swap(A[i],A[index]);
}
}
堆排序
//调整一个元素为根的子树为大根堆
void HeadAdjust(int A[],int k,int n)
{
A[0]=A[k]; //暂存根节点
for(int i=2*k; i<=n; i*=2) //寻找子树中最大的节点
{
if(i<n&&A[i]<A[i+1]) //如果当前节点有右孩子,且右孩子比左孩子大,选择右孩子向上
i++;
if(A[0]>=A[i]) //当前节点是最大的,不需要调整
break;
else //当前节点不是最大的,A[i]向上调整
{
A[k]=A[i];
k=i; //为了进入下一轮循环,检查被调整上来的节点是否满足大根堆要求
}
}
A[k]=A[0]; //最后的下坠位置
}
//建立大根堆
void BuildMaxHeap(int A[],int n) //时间复杂度O(n)
{
for(int i=n/2; i>0; i--) //想下往上处理非叶子节点
HeadAdjust(A,i,n); //而对于每个子树,小元素从上往下下坠
}
//堆排序主函数,每趟把堆顶元素与待排序序列最后一个元素交换
void HeapSort(int A[],int n)
{
BuildMaxHeap(A,n);
for(int i=n; i>1; i--)
{
swap(A[i],A[1]); //把堆顶元素和堆底元素交换(逻辑上把当前最大的元素排除在堆外)
HeadAdjust(A,1,i-1); //恢复剩余元素为大根堆,(1号元素为根,i-1个元素)
}
}
归并排序
//二路归并过程
int *B=new Elemtype[50]; //辅助空间
void Merge(Elemtype A[],int low,int mid,int high) //[low,mid]和[mid+1,high]内部是有序的,把这两个区间进行二路归并
{
int i,j,k;
for(k=low; k<=high; k++) //把A数组复制一份到B
B[k]=A[k];
for(i=low,j=mid+1,k=i; i<=mid&&j<=high; 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++];
}
//归并排序主函数
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); //起到排序作用的是Merge函数
}
}
基数排序
外部排序
算法性能比较和其他
算法 | 最好 | 平均 | 最差 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
直接插入排序 | O(n) | O(n2) | O(n2) | O(1) | 稳定 |
冒泡排序 | O(n) | O(n2) | O(n2) | O(1) | 稳定 |
简单选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
希尔排序 | O(n1.3) -O(n2) | O(n2) | O(1) | 不稳定 | |
快速排序 | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O( n 2 n^2 n2) | O( l o g 2 n log_2n log2n) | 不稳定 |
堆排序 | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O(1) | 不稳定 |
二路归并排序 | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O(n) | 稳定 |
基数排序 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | O( r ) | 稳定 |
Python中封装的接口是归并
C++封装的接口是快拍
Java封装的接口是快排&&堆排
完整代码
#include<bits/stdc++.h>
using namespace std;
typedef int Elemtype;
typedef struct SSTable
{
Elemtype *elem; //线性表起始位置
int TableLen; //元素个数
} SSTable;
//随机生成线性表用来测试
void ST_init(SSTable &ST,int len)
{
ST.TableLen=len;
ST.elem=(Elemtype*)malloc(sizeof(Elemtype)*ST.TableLen);
srand(time(NULL)); //随机生成0-99的元素
for(int i=0; i<ST.TableLen; i++)
ST.elem[i]=rand()%100;
}
//打印下标从0开始的数组
void ST_print(SSTable ST)
{
for(int i=0; i<ST.TableLen; i++)
cout<<ST.elem[i]<<" ";
cout<<endl;
}
//打印带哨兵的数组(从下标1开始)
void ST_print2(SSTable ST)
{
for(int i=1; i<=ST.TableLen; i++)
cout<<ST.elem[i]<<" ";
cout<<endl;
}
//交换两个元素
void swap(Elemtype &a,Elemtype &b)
{
Elemtype temp;
temp=a;
a=b;
b=temp;
}
//直接插入排序
void InsertSort(Elemtype A[],int n)
{
int j;
for(int i=2; i<=n; i++) //i=0处用来放哨兵
{
A[0]=A[i];
for(j=i-1; A[0]<A[j]; j--) //不按下标来控制,只看元素大小
{
A[j+1]=A[j];
}
A[j+1]=A[0];
}
}
//折半插入排序
void HalfInsertSort(Elemtype A[],int n)
{
int i,j,low,mid,high;
for(int i=2; i<=n; i++) //i=0处用来放哨兵
{
A[0]=A[i];
low=1;
high=i-1;
while(low<=high)
{
mid=(low+high)/2;
if(A[mid]>A[0])
high=mid-1;
else
low=mid+1;
}
//如果没有找到相等的元素,那么最后high会到low的左边或者low跑到high的右边,high的右边都是更大的元素,low的左边都是更小的元素
//如果找到了相等的元素,由于这里没有return,只会发生low跑到high的右边才会退出循环,此时high指向相等的元素,只有high+1右移,稳定性也能得到保证
for(j=i-1; j>=high+1; j--) //high+1非常妙
{
A[j+1]=A[j];
}
A[j+1]=A[0];
}
}
//希尔排序
//先写出插入排序,然后外面套一个缩减步长的循环,并且把所有的步长1换成dk
void ShellSort(Elemtype A[],int n)
{
int j; //必须在外面定义,因为最后一步需要把哨兵放回j+dk位置
for(int dk=n/2; dk>=1; dk/=2) //dk为步长 //步长为1时退化为插入排序
{
for(int i=dk+1; i<=n; i++) //以dk为步长进行插入排序
{
A[0]=A[i]; //设置哨兵
for(j=i-dk; j>0&&A[0]<A[j]; j-=dk) //步长较大时,比较时可能会直接越界
A[j+dk]=A[j];
A[j+dk]=A[0];
}
}
}
//冒泡排序
//可以从后开始把小的元素前移,也可以从前开始把大的元素后移
void BubbleSort(Elemtype A[],int n)
{
bool flag; //用flag来观察一趟排序中有没有交换元素,如果没有,说明序列已经有序
for(int i=0; i<n-1; i++) //第i轮表示已经有i个元素已经冒泡了,进行n-1轮,最后一个元素不用排了
{
flag=false; //使用flag可以在序列有序时(在某一轮中,没有进行交换),提前结束程序
for(int j=n-1; j>i; j--) //i=j不能取,因为后面会访问A[j-1],取了就越界了。代入i=0看一下就知道了(j-1最终冒到最上面=0,那么j最小=1)
if(A[j-1]>A[j])
{
swap(A[j-1],A[j]);
flag=true;
}
if(flag==false)
return; //某一轮中,没有一次交换,说明序列已经有序,可以提前结束
}
}
//以下三个函数均为快速排序的组成部分
//挖坑法分割,只能low和high交替填坑(不能一边连续找两个大于或小于pivot的元素)
int Partition1(Elemtype A[],int low,int high)
{
Elemtype pivot=A[low];
while(low<high)
{
while(low<high&&A[high]>pivot) //因为默认low为pivot,所以从high开始找元素填空
high--;
A[low]=A[high];
while(low<high&&A[low]<pivot) //为什么内层要重复判断low<high,因为在内层循环过程中,随时可能提前结束
low++;
A[high]=A[low];
}
A[low]=pivot; //此时high=low,用哪个下标都行
return low; //把已经定位的元素小标传出来,用来划分下一轮子区间
}
//遍历法分割,比较简单(以最右边元素为pivot)
int Partition2(Elemtype A[],int low,int high)
{
int left=low; //只用来指向小于pivot的元素
for(int i=low; i<high; i++) //遍历pivot后面所有元素
{
if(A[i]<A[high]) //i始终比left走的快,所以不会产生left和i指向两个小于pivot的元素,导致小的元素遗留在右边
{
swap(A[i],A[left]); //每找到一个小于pivot的元素,与left处交换(可以保证left前面所有元素都是小于pivot的,left和i之间所有元素都是大于pivot的)
left++;
}
}
swap(A[left],A[high]); //最后,把pivot放到应该在的位置
return left;
}
//快速排序主函数
void QuickSort(Elemtype A[],int low,int high)
{
if(low<high) //分割到子区间只有一个元素为止
{
Elemtype pivot=Partition2(A,low,high);
QuickSort(A,low,pivot-1);
QuickSort(A,pivot+1,high);
}
}
//简单选择排序
void SelectSort(Elemtype A[],int n)
{
for(int i=0; i<n-1; i++) //执行完n-1轮,最大的元素已经在最后一个位置
{
int min=A[i]; //假设第i个元素为当前轮的最小元素
int index=i;
for(int j=i+1; j<n; j++)
{
if(A[j]<min)
{
min=A[j];
index=j;
}
}
if(A[i]!=A[index])
swap(A[i],A[index]);
}
}
//以下三个函数为堆排序的组成部分
//调整一个元素为根的子树为大根堆
void HeadAdjust(int A[],int k,int n)
{
A[0]=A[k]; //暂存根节点
for(int i=2*k; i<=n; i*=2) //寻找子树中最大的节点
{
if(i<n&&A[i]<A[i+1]) //如果当前节点有右孩子,且右孩子比左孩子大,选择右孩子向上
i++;
if(A[0]>=A[i]) //当前节点是最大的,不需要调整
break;
else //当前节点不是最大的,A[i]向上调整
{
A[k]=A[i];
k=i; //为了进入下一轮循环,检查被调整上来的节点是否满足大根堆要求
}
}
A[k]=A[0]; //最后的下坠位置
}
//建立大根堆
void BuildMaxHeap(int A[],int n) //时间复杂度O(n)
{
for(int i=n/2; i>0; i--) //想下往上处理非叶子节点
HeadAdjust(A,i,n); //而对于每个子树,小元素从上往下下坠
}
//堆排序主函数,每趟把堆顶元素与待排序序列最后一个元素交换
void HeapSort(int A[],int n)
{
BuildMaxHeap(A,n);
for(int i=n; i>1; i--)
{
swap(A[i],A[1]); //把堆顶元素和堆底元素交换(逻辑上把当前最大的元素排除在堆外)
HeadAdjust(A,1,i-1); //恢复剩余元素为大根堆,(1号元素为根,i-1个元素)
}
}
//以下两个函数为归并排序的组成部分
//二路归并过程
int *B=new Elemtype[50]; //辅助空间
void Merge(Elemtype A[],int low,int mid,int high) //[low,mid]和[mid+1,high]内部是有序的,把这两个区间进行二路归并
{
int i,j,k;
for(k=low; k<=high; k++) //把A数组复制一份到B
B[k]=A[k];
for(i=low,j=mid+1,k=i; i<=mid&&j<=high; 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++];
}
//归并排序
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); //起到排序作用的是Merge函数
}
}
int main()
{
SSTable ST;
Elemtype A[10]= {64,94,95,79,69,84,18,22,12,78}; //使用固定数据
Elemtype B[11]= {0,64,94,95,79,69,84,18,22,12,78}; //使用固定数据(有哨兵的情况使用)
memcpy(ST.elem,A,sizeof(A)); //整型和浮点型不能用strcpy,strcpy遇0会结束,而memcpy可以按内存大小来复制
ST.TableLen=10;
//ST_init(ST,10); //使用随机生成数据
cout<<"原始序列:"<<endl;
ST_print(ST);
cout<<"冒泡排序结果:"<<endl;
BubbleSort(A,10);
memcpy(ST.elem,A,sizeof(A));
ST_print(ST);
cout<<"快速排序结果:"<<endl;
QuickSort(A,0,9);
memcpy(ST.elem,A,sizeof(A));
ST_print(ST);
cout<<"插入排序结果:"<<endl;
InsertSort(B,10);
memcpy(ST.elem,B,sizeof(B));
ST_print2(ST);
cout<<"折半插入排序结果:"<<endl;
HalfInsertSort(B,10);
memcpy(ST.elem,B,sizeof(B));
ST_print2(ST);
cout<<"希尔排序结果:"<<endl;
ShellSort(B,10);
memcpy(ST.elem,B,sizeof(B));
ST_print2(ST);
cout<<"简单选择排序结果:"<<endl;
SelectSort(A,10);
memcpy(ST.elem,A,sizeof(A));
ST_print(ST);
cout<<"堆排序结果:"<<endl;
HeapSort(B,10);
memcpy(ST.elem,B,sizeof(B));
ST_print2(ST);
cout<<"归并序结果:"<<endl;
MergeSort(A,0,9);
memcpy(ST.elem,A,sizeof(A));
ST_print(ST);
return 0;
}