各个算法复杂度及稳定性
算法类别 | 排序 算法 | 时间复杂度 | 空间复杂度 | 稳定性 | 备注 | ||
最好情况 | 最坏情况 | 平均情况 | |||||
插入排序 | 直接插入排序 | O(n) | O() | O() | O(1) | 稳定 | 适用于链表 |
希尔排序 | O(n) | O() | O() | O(1) | 不稳定 | 只适用于顺序表,不适用于链表。 | |
交换排序 | 冒泡排序 | O(n) | O() | O() | O(1) | 稳定 | 适用于链表 |
快速排序 | O(nlogn) | O() | O(nlogn) | O(nlogn)(最好) O(n)(最坏) | 不稳定 | 算法表现主要取决于递归深度。应用中广泛 | |
选择排序 | 简单选择排序 | O() | O() | O() | O(1) | 不稳定 | 顺序表和链表都适用。 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 | 大根堆---递增序列 小根堆---递减序列 | |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 | ||
基数排序 (桶排序) | O(d+m) | O(d*(n+m)) | O(d*(n+m)) | O(n+m) | 稳定 | d为待排序的维数(个十百)。m为基数的个数 |
直接插入排序
算法思想:每次将一个待排序的记录按其关键字大小插入到前面已经排好的子序列中,直到全部记录插入完成。
代码实现
// 直接插入排序
void InsertSort(int A[],int n){
int i,j,temp;
for (i = 1; i < n; i++){
if(A[i] < A[i-1]){ // 将各元素插入已排好序的序列中
temp = A[i]; // 若A[i]关键字小于前驱
for(j = i-1; j >= 0 && A[j] > temp; --j){ // 检查所有前面已排好序的元素
A[j+1] = A[j]; // 所有大于temp的元素都向后挪位
}
A[j+1] = temp; // 复制到插入位置
}
}
}
希尔排序
先将待排序表分成若干形如 L[i , i+d , i+2d ,... , i+kd]的特殊子表,对各个子表分别进行直接插入排序(从每个子表的表头部分开始)。缩小增量d,重复上述过程,直到d=1为止。如果数组为奇数个数,则d取整数(向下取整)。
代码实现
// 希尔排序
void ShellSort(int a[],int n){
int d,i,j;
// a[0]只是暂存单元,不是哨兵,当j<=0时,插入位置已到
for(d = n/2; d>=1; d=d/2) // 步长变化
for(i = d+1; i<=n; ++i)
if(a[i]<a[i-d]){ // 需将a[i]插入有序增量子表
a[0] = a[i]; // 暂存在a[0]
for(j = i-d; j>0 && a[0]<a[j]; j-=d)
a[j+d] = a[j]; // 记录后移,查找插入的位置
a[j+d] = a[0]; // 插入
} // if的”}“
}
冒泡排序
从后往前(或从前往后)两两比较相邻元素的值,若为逆序(即a[i-1]>a[i]),则交换它们,直到序列比较完。称这样过程为”一趟“冒泡排序。
代码实现
// 冒泡排序
void BubbleSort(int A[],int n){
for(int i=0;i < n-1; i++){
bool flag = false; //表示本趟冒泡是否发生交换的标志
for(int j=n-1; j>i; j--){ //一趟冒泡过程
if(A[j - 1]>A[j]){ // 若为逆序
swap(A[j-1],A[j]); // 交换
flag = true;
}
}
if(flag == false){
return; // 本趟遍历后没有发生交换,说明表已经有序
}
}
}
// 交换
void swap(int &a,int &b){
int temp = a;
a = b;
b = temp;
}
快速排序
在待排序表L[1...n]中任取一个元素pivot作为枢轴(或基准,通常取首元素),通过一趟排序将待排表划分为独立的两部分L[1...k-1]和L[k+1...n],使得L[1...k-1]中的所有元素小于pivot,L[k+1...n]中的所有元素大于等于pivot,则pivot放在了其最终位置L(k)上,这个过程称为一次”划分“。然后分别递归地对两个子表重复上述过程,直至每部分只有一个元素或空为止,即所有元素放在了其最终位置上。
代码实现
// 用一个元素将待排序序列划分为左右两个部分
int Patrition(int A[],int low, int high) {
int pivot = A[low]; // 第一个元素作为枢轴
while (low < high) { // 用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 SelectSort(int A[] , int n){
for (int i = 0; i < n-1; i++) { // 一共进行n-1趟
int min = i; // 记录最小元素位置
for (int j = i+1; j < n; j++) { // 在A[i...n-1]中选择最小的元素
if(A[j]<A[min]){
min = j; // 更新最小元素位置
}
}
if(min != i){
swap(A[i],A[min]); // 封装的swap()函数共移动3次
}
}
}
// 交换
void swap(int &a,int &b){
int temp = a;
a = b;
b = temp;
}
堆排序
若n个关键字序列L[1...n]满足下面某一条性质,则称为堆(Heap):
(1)若满足:L(i)>=L(2i)且L(i)>=L(2i+1) (1<=i<=n/2) ------大根堆(大顶堆):也就是完全二叉树中:根>=左,右
(2)若满足:L(i)<=L(2i)且L(i)<=L(2i+1) (1<=i<=n/2) ------小根堆(小顶堆):也就是完全二叉树中:根<=左,右
若元素互换破坏了下一级的堆,则采用相同的方法继续往下调整。
思路:把所有非终端节点都检查一遍,是否满足大根堆的要求,如果不满足,则进行调整。检查当前节点是否满足根>=左,右,若不满足,将当前节点与更大的一个孩子互换。
每一趟将堆顶元素加入有序子序列(与待排序序列中的最后一个元素交换)。
代码实现
// 建立大根堆
void BuildMaxHeap(int A[],int len){
for (int i = len/2; i > 0 ; i--) { // 从后往前调整所有非终端节点
HeadAdjust(A,i,len);
}
}
// 将以k为根的子树调整为大根堆
void HeadAdjust(int A[],int k,int len){
A[0] = A[k]; //A[0]暂存子树的根节点
for(int i=2*k;i <= len; i *= 2){ //沿key较大的子结点向下筛选
if(i < len && A[i] < A[i+1])
i++; // 取key较大的子结点的下标
if(A[0]>=A[i]) // 筛选结束
break;
else{
A[k] = A[i]; // 将A[i]调整到双亲结点上
k = i; //修改k值,以便继续向下筛选
}
}
A[k] = A[0]; //被筛选结点的值放入最终位置
}
// 堆排序的完整逻辑
void HeapSoft(int A[],int len){
BuildMaxHeap(A,len); //初始建堆
for(int i = len; i > 1;i--){ //n-1趟的交换和建堆过程
swap(A[i],A[1]); //堆顶元素和堆底元素交换,swap函数如上
HeadAdjust(A,1,i-1); // 把剩余的待排序元素整理成堆
}
}
归并排序
把两个或多个有序的子序列合并为一个。
代码实现
int *B = (int *)malloc(n*sizeof(int)); // 辅助数组B
// A[low...mid]和A[mid+1 ... high]各自有序,将两个部分归并
void Merge(int A[],int low,int mid,int high){
int i,j,k;
for(k=low;k <= high; k++)
B[k] = A[k]; // 将A中所有元素复制到B中
for(i = low,j = mid+1,k = i;i <= mid && j <= high;k++){
if(B[i] <= B[j])
A[k] = B[i++]; // 将较小值复制到A中
else
A[k] = B[j++];
}
while(j <= mid)
A[k++] = B[i++];
while(j <= high)
A[k++] = B[i++];
}
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); // 归并
}
}
基数排序
第一趟:以个位进行分配,第一趟收集结束,得到按个位递减排序的序列。
第二趟:以十位进行分配,第二趟收集结束,得到按十位递减排序的序列,十位相同的按个位递减排序。
.......
假设长度为n的线性表中每个结点aj的关键字由d元组(,,,...,,)组成
其中,0<=<=r-1(0<=j<n,0<=i<=d-1),r称为基数。
基数排序得到递减序列的过程如下,
初始化:设置r个空队列,,...,
按照各个关键字位权重递增的次序(个、十、百),对d个关键字位分别做”分配“和”收集“。
分配:顺序扫描各个元素,若当前处理的关键字位=x,则将元素插入Qx队尾。
收集:把,,...,各个队列中的结点依次出队并链接。
适用范围
(1)数据元素的关键字可以方便的拆分为d组,且d较小。
(2)每组关键字的取值范围不大,即r较小。
(3)数据元素个数n较大。
代码实现
typedef struct LinkNode{
ElemType data;
struct LinkNode *next;
}LinkNode, *LinkList;
typedef struct{ //链式队列
LinkNode *front , *rear; //队列的队头和队尾指针
}LinkQueue;