数据结构——第8章排序算法
常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。知识框架如下:
插入排序
插入排序是一种简单直观的排序方法,其基本思想在于每次将一个待排序记录,按其关键字大小插入到前面已经排好序的子序列中,直到全部记录插入完成。
1.直接插入排序
直接插入排序是一种最简单的排序方法,其基本操作是将需要排序的元素插入到已排好的有序表序列中,从而得到一个完整的有序序列。
空间复杂度:O(1) 时间复杂度:O(n^2) 稳定性:稳定
算法思想
①将待排序序列分为两部分,一部分有序一部分无序。
②我们把第一个元素看作有序序列,从第二个元素到最后为无序序列。
③将无序序列中每一个元素依次插入到有序序列的合适位置–从小到大(从大到小)。
代码实现
#include <stdio.h>
void InsertSort(int A[],int n)
{
int i,j,temp;
for(i=1;i<n;i++)
{
if(A[i-1]>A[i])
{
temp=A[i];
for(j=i-1;temp<=A[j]&&j>=0;j--)
{
A[j+1]=A[j];
}
A[j+1]=temp;
}
}
printf("直接插入排序完成!\n");
}
void Print(int A[],int n)
{
for(int i=0;i<n;i++)
{
printf("%d ",A[i]);
}
printf("\n");
}
int main()
{
int A[10]={4,3,10,5,6,7,1,2,8,9};
int n = sizeof(A)/sizeof(int);
printf("初始序列:\n");
Print(A,n);
InsertSort(A,n);
printf("排序后序列:\n");
Print(A,n);
return 0;
}
运行结果
初始序列:
4 3 10 5 6 7 1 2 8 9
直接插入排序完成!
排序后序列:
1 2 3 4 5 6 7 8 9 10
--------------------------------
Process exited after 0.1367 seconds with return value 0
请按任意键继续. . .
2.折半插入排序
折半插入排序是插入排序方法中一种,相比较与直接插入排序算法,减少了排序过程中比较次数,也是一种常用的排序算法。
折半插入排序算法基本原理是将折半查找方法与直接插入排序方法相结合,也就是在每一次插入新元素时,利用折半查找方法找到其待插入的位置。
空间复杂度:O(1) 时间复杂度:O(n^2) 稳定性:稳定
执行过程
现在的序列是13,38,49,65,76,97 27 49
将要插入27,序列在数组中的情况为:
|--------------------已经排序-------------------------------------|-----未排序-----|
关键字 | 13 | 38 | 49 | 65 | 76 | 97 | 27 | 49 |
数组下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1)low=0,high=5,m=|_(0+5)/2_|=2,下标为2的关键字是49,27<49,27插入49的低半区,改变high=m-1=1,low=0;
2) low=0,high=1,m=|_(0+1)/2_|=0,下标为0的关键字是13,13<27,27插入13的高半区,改变high=1,low=m+1=1;
3) low=1,high=1,m=|_(1+1)/2_|=1,下标为1的关键字是38,27<38,27插入38的低半区,改变high=m-1=0,low=1,low>high,查找结束;
4) 依次向后移动关键字97,76,65,49,38。然后插入27,一趟折半插入结束。
代码实现
#include <stdio.h>
void InsertSort(int A[],int n)
{
int i,j,temp,low,mid,high;
for(i=1;i<n;i++)
{
low=0;
high=i-1;
temp=A[i];
while(low<=high)
{
mid=(low+high)/2;
if(A[mid]>temp)
high=mid-1;
else
low=mid+1;
}
for(j=i-1;j>=low;j--)
A[j+1]=A[j];
A[low]=temp;
}
}
void Print(int A[],int n)
{
for(int i=0;i<n;i++)
{
printf("%d ",A[i]);
}
printf("\n");
}
int main()
{
int A[10]={4,3,10,5,6,7,1,2,8,9};
int n = sizeof(A)/sizeof(int);
printf("初始序列:\n");
Print(A,n);
InsertSort(A,n);
printf("排序后序列:\n");
Print(A,n);
return 0;
}
运行结果
初始序列:
4 3 10 5 6 7 1 2 8 9
排序后序列:
1 2 3 4 5 6 7 8 9 10
--------------------------------
Process exited after 0.12 seconds with return value 0
请按任意键继续. . .
3.希尔排序
希尔排序是插入排序的一种又称 "缩小增量排序",是直接插入排序算法的一种更高效的改进版本。
基本思想:先取一个小于 n 的整数 d1 作为第一个增量,把文件的全部记录分组。所有距离为 d1 的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量 d2<d1 重复上述的分组和排序,直至所取的增量 = 1(<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
空间复杂度:O(1) 时间复杂度:O(n^2) 稳定性:不稳定
代码实现
#include <stdio.h>
void ShellSort(int A[],int n)
{
int d,i,j,temp;
for(d=n/2;d>=1;d=d/2)
{
for(i=d;i<n;i++)
{
temp=A[i];
for(j=i-d;j>=0&&temp<A[j];j=j-d)
A[j+d]=A[j];
A[j+d]=temp;
}
}
}
void Print(int A[],int n)
{
for(int i=0;i<n;i++)
{
printf("%d ",A[i]);
}
printf("\n");
}
int main()
{
int A[10]={4,3,10,5,6,7,1,2,8,9};
int n = sizeof(A)/sizeof(int);
printf("初始序列:\n");
Print(A,n);
ShellSort(A,n);
printf("排序后序列:\n");
Print(A,n);
return 0;
}
运行结果
初始序列:
4 3 10 5 6 7 1 2 8 9
排序后序列:
1 2 3 4 5 6 7 8 9 10
--------------------------------
Process exited after 0.1217 seconds with return value 0
请按任意键继续. . .
交换排序
交换排序就是根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置。常用的交换排序算法有冒泡和快排。交换类的排序,其趟数和原始序列状态有关。
4.冒泡排序
冒泡排序的原理是:从左到右,相邻元素进行比较。每次比较一轮,就会找到序列中最大的一个或最小的一个。这个数就会从序列的最右边冒出来。
算法思想
以从小到大排序为例,第一轮比较后,所有数中最大的那个数就会浮到最右边;第二轮比较后,所有数中第二大的那个数就会浮到倒数第二个位置……就这样一轮一轮地比较,最后实现从小到大排序。
空间复杂度:O(1) 时间复杂度:O(n^2) 稳定性:稳定
代码实现
#include <stdio.h>
void BubbleSort(int A[],int n)
{
int i,j,temp;
for(i=0;i<n-1;i++)
{
for(j=0;j<n-1-i;j++)
{
if(A[j]>A[j+1])
{
temp=A[j+1];
A[j+1]=A[j];
A[j]=temp;
}
}
}
}
void Print(int A[],int n)
{
for(int i=0;i<n;i++)
{
printf("%d ",A[i]);
}
printf("\n");
}
int main()
{
int A[10]={4,3,10,5,6,7,1,2,8,9};
int n = sizeof(A)/sizeof(int);
printf("初始序列:\n");
Print(A,n);
BubbleSort(A,n);
printf("排序后序列:\n");
Print(A,n);
return 0;
}
运行结果
初始序列:
4 3 10 5 6 7 1 2 8 9
排序后序列:
1 2 3 4 5 6 7 8 9 10
--------------------------------
Process exited after 0.1185 seconds with return value 0
请按任意键继续. . .
5.快速排序
快排算法是基于分治策略的排序算法,其基本思想是,对于输入的数组 a[low, high],按以下三个步骤进行排序。
(1) 分解:以 a[p] 为基准将a[low: high]划分为三段 a[low:p-1],a[p] 和 a[p+1:high],使得 a[low:p-1] 中任何一个元素小于等于 a[p], 而 a[p+1: high] 中任何一个元素大于等于 a[p]。
(2) 递归求解:通过递归调用快速排序算法分别对 a[low:p-1] 和 a[p+1:high] 进行排序。
(3) 合并:由于对 a[low:p-1] 和 a[p+1:high] 的排序是就地进行的,所以在 a[low:p-1] 和 a[p+1:high] 都已排好序后,不需要执行任何计算,a[low:high] 就已经排好序了。
空间复杂度:O(logn) 时间复杂度:O(nlogn) 稳定性:不稳定
快排动图(网上找的动图,其中有一个基准为 6 的标识错误。虽然基准选择方法不一样,但排序过程还是一样的):
代码实现
#include <stdio.h>
int Partition(int A[],int low,int high)
{
int pivot=A[low];
while(low<high)
{
while(low<high&&A[high]>=pivot)
--high;
A[low]=A[high];
while(low<high&&A[low]<=pivot)
++low;
A[high]=A[low];
}
A[low]=pivot;
return low;
}
void QuickSort(int 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 Print(int A[],int n)
{
for(int i=0;i<n;i++)
{
printf("%d ",A[i]);
}
printf("\n");
}
int main()
{
int A[10]={4,3,10,5,6,7,1,2,8,9};
int n = sizeof(A)/sizeof(int);
int low=0,high=n-1;
printf("初始序列:\n");
Print(A,n);
QuickSort(A,low,high);
printf("排序后序列:\n");
Print(A,n);
return 0;
}
运行结果
初始序列:
4 3 10 5 6 7 1 2 8 9
排序后序列:
1 2 3 4 5 6 7 8 9 10
--------------------------------
Process exited after 0.1134 seconds with return value 0
请按任意键继续. . .
选择排序
选择排序是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
6.简单选择排序
空间复杂度:O(1) 时间复杂度:O(n^2) 稳定性:不稳定
代码实现
#include <stdio.h>
void SelectSort(int 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)
{
int temp=A[i];
A[i]=A[min];
A[min]=temp;
}
}
}
void Print(int A[],int n)
{
for(int i=0;i<n;i++)
{
printf("%d ",A[i]);
}
printf("\n");
}
int main()
{
int A[10]={4,3,10,5,6,7,1,2,8,9};
int n = sizeof(A)/sizeof(int);
printf("初始序列:\n");
Print(A,n);
SelectSort(A,n);
printf("排序后序列:\n");
Print(A,n);
return 0;
}
运行结果
初始序列:
4 3 10 5 6 7 1 2 8 9
排序后序列:
1 2 3 4 5 6 7 8 9 10
--------------------------------
Process exited after 0.1304 seconds with return value 0
请按任意键继续. . .
7.堆排序
堆排序是指利用堆这种数据结构所设计的一种排序算法。分为两种方法:大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列。建堆的时间复杂度为O(n),堆排序的时间复杂度为O(nlog2^n)
空间复杂度:O(1) 时间复杂度:O(nlogn) 稳定性:不稳定
代码实现
#include <stdio.h>
//将以k为根的子树调整为大根堆
void HeadAdjust(int A[],int k,int len)
{
int temp=0;
temp=A[k];
for(int i=2*k+1;i<len;i=i*2+1)
{
if(i+1<len&&A[i]<A[i+1])
i++;
if(temp>=A[i])
break;
else
{
A[k]=A[i];
k=i;
}
}
A[k]=temp;
}
//建立大根堆
void BuildMaxHeap(int A[],int len)
{
for(int i=len/2-1;i>=0;i--)
HeadAdjust(A,i,len);
}
//堆排序的完整逻辑
void HeapSort(int A[],int len)
{
BuildMaxHeap(A,len);
for(int i=len-1;i>0;i--)
{
int temp=A[i];
A[i]=A[0];
A[0]=temp;
HeadAdjust(A,0,i);
}
}
void Print(int A[],int n)
{
for(int i=0;i<n;i++)
{
printf("%d ",A[i]);
}
printf("\n");
}
int main()
{
int A[7]={6,9,1,5,8,4,7};
int len = sizeof(A)/sizeof(int);
printf("初始序列:\n");
Print(A,len);
printf("排序后序列:\n");
HeapSort(A,len);
Print(A,len);
return 0;
}
运行结果
初始序列:
6 9 1 5 8 4 7
排序后序列:
1 4 5 6 7 8 9
--------------------------------
Process exited after 0.1126 seconds with return value 0
请按任意键继续. . .
8.归并排序
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。
注意:一般而言,对于 N 个元素进行 k-路 归并排序时,排序的趟数 m 满足 k^m = N,从而 m = logk(N)向上取整。
空间复杂度:O(n) 时间复杂度:O(nlogn) 稳定性:稳定
代码实现
#include <stdio.h>
void Merge(int A[],int B[],int low,int mid,int high)
{
int i,j,k;
for(k=low;k<=high;k++)
{
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 B[],int low,int high)
{
if(low<high)
{
int mid=(low+high)/2;
MergeSort(A,B,low,mid);
MergeSort(A,B,mid+1,high);
Merge(A,B,low,mid,high);
}
}
void Print(int A[],int n)
{
for(int i=0;i<n;i++)
{
printf("%d ",A[i]);
}
printf("\n");
}
int main()
{
int A[10]={4,3,10,5,6,7,1,2,8,9};
int n = sizeof(A)/sizeof(int);
int B[10];
int low=0;
int high=n-1;
printf("初始序列:\n");
Print(A,n);
MergeSort(A,B,low,high);
printf("排序后序列:\n");
Print(A,n);
return 0;
}
运行结果
初始序列:
4 3 10 5 6 7 1 2 8 9
排序后序列:
1 2 3 4 5 6 7 8 9 10
--------------------------------
Process exited after 0.1115 seconds with return value 0
请按任意键继续. . .
常用排序算法复杂度和稳定性总结