概念
稳定性
内部排序和外部排序
性能
九种排序
直接插入排序
从第二个元素开始和前面一个元素比较,如果元素大小小于前面一个元素,则前面一个元素后退一格,直到前面的元素小于此元素则停止后退并插入该元素,从第二个元素开始到最后一个元素循环操作则完成排序
时间复杂度为O(n^2)
空间复杂度为O(1) 使用了一个A哨兵;
适合顺序存储和链式存储,稳定性为稳定。
//直排
void insertsort(ElemType A[],int n){
int j,i;
for(i=2;i<n;i++){
A[0]=A[i];
for(j=i-1;A[0].key<A[j].key;j--){
A[j+1]=A[j];
}
A[j+1]=A[0];
}
}
折半插入排序
思想和直接插入一样,从第二个元素开始和前面的元素比较,不过使用折半查找的方式找到该元素应该插入的位置,然后再进行后移。
时间复杂度为O(n^2)
空间复杂度为O(1) 使用了一个A哨兵;
只适合顺序存储,稳定性为稳定。
//折半排序
void Binsertsort(ElenType A[],int n){
int i,j;
int low,high,mid;
for(i=2;i<n;i++){
A[0]=A[i];
low=1;high=i-1;
while(low<=high){//折半查找
mid=(low+high)/2;
if(A[0].key<mid.key)
high=mid-1;
else
low=mid+1;
}
for(j=i-1;j>high+1;j--){
A[j+1]=A[j];
}
A[high+1]=A[0];
}
}
希尔排序
由于直排对于相对有序的序列排序速度比相对无序的序列排序速度快很多,所以将序列分块将每个块先进行排序,让序列成为一个相对有序的序列,最后再进行直排,分块规则是定义一个变量为dk,每个元素相差dk个位置的元素为一个块,如dk=3,则1,4,7,10···为一块
2,5,8,11···为一个块。dk的值没有明确的规定,但是通常dk=n/2,在排序完当前dk后,dk除以2继续排序,最后当dk为1时,就是一次直排,最后达到排序效果。
时间复杂度为O(n2)(其实是n1.3)
空间复杂度为O(1) 使用了一个A哨兵;
只适合顺序存储,稳定性为不稳定。
//希尔排序
void shellsort(ElenType A[],int n){
for(int dk=n/2;dk>0;dk/=2){//设置dk后,依次除以二,最后成为直排
for(int i=1+dk;i<=n;i++){//从第一个块的第二个元素开始比较,就像直排从第二个元素开始比较一样
if(A[i-dk].key>A[i].key){
A[0]=A[i];
for(int j=i-dk;j>0&&A[j].key<A[0].key,j-=dk;){//和同一个块的前一个元素比较
A[j+dk]=A[j];
}
A[j+dk]=A[0];
}
}
}
}
冒泡排序
冒泡排序是从最后一个元素开始通过不断和前面一个元素比较,最后可以让最小的元素排到第一个位置,然后继续从最后一个元素不断和前面一个元素比较,第二小的元素就会被排在第二个位置,最后就可以按照大小排序排序过程中设置一个flag用来标志后面的元素是否已经有序,如果已经有序就直接退出。
//冒泡排序
void bubblesort(EkenType A[],int n){
for(int i=0;i<n-1;i++){
bool flag=false;
for(int j=n;j>i;j--){
if(A[j].key<A[j+1].key){
swap(A[j],A[j-1]);//交换函数
flag=true;
}
if(flag==false)
return;
}
}
}
快速排序
快速排序,将第一个元素设置为pivot,设置两个指针分别low和high分别指向第一个元素和最后一个元素,high指向的元素和piovt比较,如果小于pivot,则将这个数赋值给low所指向的元素,如果大于pivot则将high指向前面一个数,直到指向的元素小于pivot,当high将数值赋值给low所指向的元素,后将low指向的元素与piovt进行比较,如果大于pivot则,将数值赋值给high所指向的元素,一直循环,直到low等于high时,将pivot赋值给low和high共同指向的元素,这样就可以做到pivot前面的元素都比pivot小,后面的元素都比pivot大,pivot找到了自己排序后所在的位置,通过这种方法,将这个pivot前面和后面的序列再次进行以上操作,最后所有数都可以找到自己所在的位置,完成排序。
//快排
void partition(ElemType A[],int low,int high){
ElemType pivot=A[low];
while(low<high){
while(A[high]>=pivot&&low<high){//从后遍历比pivot大的数,直到遇到比 pivot小的数,将其赋值到位置为low
high--;
}
A[low]=A[high];
while(A[low]<=pivot&&low<high){//从前遍历比pivot小的数, 直到遇到比 pivot大的数,将其赋值到位置为high
low++;
}
A[high]=A[low];
}
A[low]=pivot;//当high和low相等时,将这个位置的数置为pivot
return low;
}
void quicksort(ElemType A[],int low,int high){
if(low<high){
int pivotpos=partition(A,low,high);//找到第一个元素对应的位置
quicksort(A,low,pivotpos-1);//找到的元素左边的元素序列第一个元素对应的位置
quicksort(A,pivotpos+1,high);//找到的元素右边的元素序列第一个元素对应的位置
}//最后所有元素都能找到自己对应的位置
}
直接选择排序
把后面序列元素中最小的放在序列的前面
//直接选择排序
void selectsort(ElemType A[],int n){
for(int i=0;i<n-1;i++){
int min=i;
for (int j=i+1;j<n;j++)
if(A[j]<A[min])
min=j;
if(min!=i)
swap(A[i],A[min])
}
}
堆排序
通过构造堆(大根堆,小根堆),时期堆顶元素一直保持为最大值或者最小值,模拟树的结构,让所有的父节点都大于或者小于其孩子节点
大根堆堆初始化:时间复杂度(O(n))
//堆排序
void BuildMaxHeap(ElemType A[],int len){
for (int i = len/2; i >0 ; i--) {//循环所有父节点
AdjustDown(A,i,len);//让所有子节点都小于所对应的父节点
}
}
void AdjustDown(ElemType A[],int k,int len){
A[0]=A[k];//保存父节点
for (int i=2*k; i <=len ; i*=2) {//遍历父节点所对应的左孩子
if(i<len&&A[i]<A[i+1])//比较左孩子和右孩子大小、将i指向大的那个孩子
i++;
if(A[0]>=A[i])//如果父节点大于两个孩子节点,则结束否则交换父亲节点和大的一个孩子节点
break;
else{
A[k]=A[i];
k=i;//交换之后检查交换之后的孩子是否大于孙子
}
}
A[k]=A[0];
}
每次把最大的值输出,不停的进行初始化
void HeapSort(ElemType A[],int len){
BuildMaxHeap(A,Len);//初始化
for (int i = len; i >1; i--) {
Swap(A[i],A[1]);//输出跟节点,并与最后一个节点交换
AdjustDown(A,1,i-1);//重新使其成为大根堆
}
}
堆的插入
//堆的插入
void AdjustUp(ElemType A[],int len){
A[0]=A[K];
int i=k/2;
while (i>0 && A[i]<A[0]){
A[k]=A[i];
k=i;
i=k/2;
}
A[k]=A[0];
}
归并排序
二路归并,将序列两个两个分组,每组先排序,然后归并相邻两组排序,实行递归,就可以将整个序列排序
//归并排序
ElemType *B=(ElemType *)malloc((n+1)*sizeof(ElemType));
void Merge(ElemType A[],int low,int mid,int high){
for (int k = low; k <high ; k++) {
B[k]=A[k];
}
int i,j,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(ElemType A[],int low,int high){
if (low <high){
int mid =(low+high)/2;
MergeSort(A,low,mid);
MergeSort(A,low,mid);
Merge(A,low,mid,high);
}
}
基数排序
不通过比较的排序,通过队列比较数字各个位数的大小进行入列和出列,最终完成排序,设置位数个队列,从低位到高位开始排序,第一次排序是个位排序通过,按照个位的数字一次入对应列,如324个位为4则如Q4队列,最后按照大小进行出列,出列序列按照十位入列,后出列,最后按照百位入列后出列,完成最终排序。
总结
外部排序(内存装不下的情况)
r是初始化每个归并段的个数
t(IS)是外部排序时间
d表示读写磁盘的次数
t(IO)是读写磁盘的时间
S是归并次数
t(mg)是归并时间
访问磁盘时间是最消耗时间的,二路归并改4路归并
失败树
真题
2020:大部分元素有序的数组中,直接插入把简单选择比较次数更少,移动次数更多。
2019:n路归并排序中归并树只存在度为0和度为n的结点,度为0的结点是元素个数加补充结点,度为0的结点也是是(n-1)x度为n的结点+1,联立之后度为n的结点=(结点总数-1+补充结点)/(n-1),由于度为n的结点个数为整数,所以就能够计算出度为n的结点为整数的最小补充数。
2016:对10TB的数据文件进行排序应当使用什么排序
解:当数据超级大的时候(内存装不下的时候)用归并排序。
2015:希尔排序的组内排序采用的是直接插入排序