内排序
插入排序
直接插入排序
- 将数组A中元素A[i]插入到已有序子序列A[0] , A[1] , … A[i-1]中
- 1.比较 查找A[i]在A[0] , A[1] , … A[i-1]中的位置pos
- 2.移位 将A[pos] , A[pos+1] , … A[i-1]全部后移一位
- 3.将A[i]复制到A[pos]
#include<stdio.h>
//n个元素 A[0]为哨兵
void insert_sort(int * A,int n){
int i,j;
for(i=2; i<=n; i++){
A[0] = A[i];
for(j = i -1; A[0]<A[j]; --j ) //边比较 边移动
A[j+1] = A[j];
A[j+1] = A[0]; //j+1为查找出来的pos
}
}
//时间复杂度为O(n*n) 空间复杂度为O(1) 稳定 线性表和链表均可
void main(){
int A[]={12345/*哨兵*/,5,9,8,1,2,9,8,0,4};
printf("before inser_sort:\n");
for(int i=1; i<=9; ++i)
printf("%d ",A[i]);
insert_sort(A,9);
printf("\nafter insert_sort:\n");
for(i=1; i<=9; ++i)
printf("%d ",A[i]);
getchar();
}
折半插入排序
- 折半查找 确定待插入位置后 统一后移元素
#include<stdio.h>
void half_insert_sort(int * A,int n){
int i,j,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[mid] > A[0]) high = mid -1;
else low = mid +1; //游标high指向A[i]所要插入的位置
}
/*相比直接插入排序 减少了比较次数 比较O(n*log2(n))*/
//移位
for(j=i-1; j>=high+1; --j)
A[j+1] = A[j];
/*移动次数未变 移位操作的时间复杂度仍然为O(n*n)*/
//插入
A[high+1] = A[0];
}
}
//算法时间复杂度O(n*n) 空间复杂度O(1) 稳定 顺序表
void main(){
int A[]={12345/*哨兵*/,5,9,8,1,2,9,8,0,4};
printf("before half_inser_sort:\n");
for(int i=1; i<=9; ++i)
printf("%d ",A[i]);
half_insert_sort(A,9);
printf("\nafter half_insert_sort:\n");
for(i=1; i<=9; ++i)
printf("%d ",A[i]);
getchar();
}
希尔(Shell)排序 「缩小增量排序」
- 1.分割子表
- 2.对子表进行直接插入排序
- 3.基本有序后,全体进行一次直接插入排序
- 经过多个分组的直接插入排序,最后一次完整的插入排序前数据表几乎已经是排好序了,因此shell sort充分利用了直接插入排序在数据表几乎已经有序的条件下工作高效的特点
#include<stdio.h>
void shell_sort(int* A,int n){
int i,j,dk;
for(dk = n/2; dk >= 1; dk/=2)
for(i = dk +1; i < n; ++i )
if(A[i] < A[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];
}
}
//时间复杂度 特定情况下O(n^1.3) 最坏O(n*n) 空间复杂度O(1)
//不稳定 仅适用于顺序存储的线性表
void main(){
int A[]={12345/*哨兵*/,5,9,8,1,2,9,8,0,4};
printf("before shell_sort:\n");
for(int i=1; i<=9; ++i)
printf("%d ",A[i]);
shell_sort(A,9);
printf("\nafter shell_sort:\n");
for(i=1; i<=9; ++i)
printf("%d ",A[i]);
getchar();
}
交换排序
冒泡排序
- 从后往前两两比较 逆序交换
#include<stdio.h>
void bubble_sort(int* A, int n){
int i,j,temp;
bool flag;
for(i= 0 ; i<n-1; ++i)
flag= false;
//从后往两两比较
for(j = n-1; j>i; --j)
if(A[j-1]>A[j]){
//swap
temp = A[j-1];
A[j-1] = A[j];
A[j] = temp;
flag = true;
}
if(flag==false) //一趟后flag仍为false没有元素交换
return; //说明表已经有序 使最好情况下时间复杂度为O(n)
}
//时间复杂度O(n*n) 空间复杂度O(1)
//稳定 每一趟排序都有一个元素在其最终位置上
//双起冒泡
void bubble_sort2(int* A,int n){
int i,low=0,high=n-1,temp;
bool flag=true;
while(low<high && flag){
flag = false;
//奇数趟 从前到后
for(i = low; i<high; ++i)
if(A[i]>A[i+1]){
temp = A[i];
A[i] = A[i+1];
A[i+1] = temp;
flag = true;
}
high--;
//偶数趟 从后到前
for(i = high; i>low; --i)
if(A[i]<A[i-1]){
temp = A[i];
A[i] = A[i-1];
A[i-1] = temp;
flag = true;
}
low++;
}
}
void main(){
int A[]={5,9,8,1,2,9,8,0,4};
printf("before bubble_sort:\n");
for(int i=0; i<9; ++i)
printf("%d ",A[i]);
bubble_sort2(A,9);
printf("\nafter bubble_sort:\n");
for(i=0; i<9; ++i)
printf("%d ",A[i]);
getchar();
}
快速排序
- 1.任选一个元素a作为pivot,比a小的放在a前面.反之,后面
- 2.一趟快排分为A[1], A[2], … A[pos-1]和A[pos+1], A[pos+2], … A[n]两部分,a放在最终位置pos上
- 3.左右分别递归,过程如下图
#include<stdio.h>
//一趟快速排序划分过程 返回pivot的最终位置
int partition(int* A, int low, int high){
//采用严蔚敏版教材的划分算法,将第一个元素设为pivot
int pivot = A[low];
while(low<high){
while(low<high && A[high]>=pivot) --high;
A[low] = A[high]; //将比pivot小的移到左端
while(low<high && A[low]<=pivot) ++low;
A[high] = A[low]; //将比pivot大的移到右端
}
A[low] = pivot; //最后low即为pivot的最终位置
return low;
}
void quick_sort(int* A, int low,int high){
if(low<high){ //递归跳出条件
int pivotpos = partition(A,low,high); //关键在划分算法
quick_sort(A,low,pivotpos-1);
quick_sort(A,pivotpos+1,high);
}
}
//算法递归 需要递归工作栈保存每一层递归的必要信息
//空间复杂度与递归调用的最大深度一致 最好log2(n+1) 最差n-1次递归
//空间复杂度 最坏O(n) 平均O(log2(n))
//时间复杂度 最坏O(n*n) 最好O(n*log2(n))
//不稳定
void main(){
int A[]={5,9,8,1,2,9,8,0,4};
printf("before quick_sort:\n");
for(int i=0; i<9; ++i)
printf("%d ",A[i]);
quick_sort(A,0,8);
printf("\nafter quick_sort:\n");
for(i=0; i<9; ++i)
printf("%d ",A[i]);
getchar();
}
选择排序
简单选择排序
- 每一趟(假设i趟)选出后面n-i+1序列中最小的元素位置
- 将该元素与第i个位置元素交换
#include<stdio.h>
void select_sort(int* A,int n){
int i,j,min,temp;
for(i=0; i<n-1; ++i){
min = i;
for(j= i+1; j<n; ++j)
if(A[j]<A[min])
min = j; //j的位置就是最小元素min所在位置
if(min!=j){
temp = A[min];
A[min] = A[i];
A[i] = temp;
}
}
}
//时间复杂度 O(n*n) 空间复杂度O(1)
//不稳定
void main(){
int A[]={5,9,8,1,2,9,8,0,4};
printf("before select_sort:\n");
for(int i=0; i<9; ++i)
printf("%d ",A[i]);
select_sort(A,9);
printf("\nafter select_sort:\n");
for(i=0; i<9; ++i)
printf("%d ",A[i]);
getchar();
}
堆排序
以大根堆为例:
- 1.构造初始大根堆
- 2.输出堆顶元素,将堆底元素送入堆顶
- 3.将堆顶元素向下调整使其保持大根堆性质
- 4.跳到步骤2,重复,直到剩下一个元素
堆排序属于选择排序,与简单选择排序的区别,在于选出最大或最小数的方式不同,减少了比较次数,关键在于调整堆的算法
#include<stdio.h>
void adjust_down(int* A,int k, int n){
//将元素k向下调整,形成大根堆
A[0] = A[k];
//对于结点k,结点2k为其左子结点
for(int i = 2*k ; i <n; i*=2){
if(A[i]<A[i+1])
++i;
if(A[0]>A[i])
break;
else{
A[k] = A[i]; //i=2k o r2k+1 把子结点的值赋给其双亲结点k
k = i; //修改k 令之后的循环可以继续向下筛选
}
}
A[k] = A[0];
}
void build_max_heap(int* A,int n){
//从i = [n/2]到1,调整堆
for(int i = n/2; i>0; --i)
//向下调整 算法关键
adjust_down(A,i,n);
}
//A[0]为哨兵 这里以大根堆为例
void heap_sort(int* A,int n){
int temp;
//建立初始堆 建堆时间复杂度O(n)
build_max_heap(A,n);
//递归调用 有n-1次向下调整,每次下调时间复杂度O(h),即O(log2(n))
for(int i = n; i>1; --i){
temp = A[1];
A[1] = A[i];
A[i] = temp;
adjust_down(A,1,i-1);
}
}
//时间复杂度O(n*log2(n)) 空间复杂度O(1) 不稳定
void main(){
int A[]={12345/*哨兵*/,5,9,8,1,2,9,8,0,4};
printf("before heap_sort:\n");
for(int i=1; i<=9; ++i)
printf("%d ",A[i]);
heap_sort(A,9);
printf("\nafter heap_sort:\n");
for(i=1; i<=9; ++i)
printf("%d ",A[i]);
getchar();
}
//=========================拓展==分割线==可以不看================================
//NOTE1:
//堆排序支持删除操作: 删除堆顶元素时,将堆底元素换到堆顶,从根节点向下调整建堆
int* delete_head_and_sort(int* A, int n){
int i,temp;
A[1] = A[n];//删除
adjust_down(A,1,n-1);//建堆 下调时间复杂度O(h),即O(log2(n))
for(i = n-1; i>1; --i){
temp = A[1];
A[1] = A[i];
A[i] = temp;
adjust_down(A,1,i-1);
}
return A;//A的长度并没改变
}
//NOTE2:
//堆排序支持插入操作:将新结点放在堆尾,对这个新结点做向上调整操作建堆
//向上调整函数 时间复杂度O(h),即O(log2(n))
void adjust_up(int* A, int k){
//k为上调的结点,假设新结点已经加入到堆尾了
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];
}
int* insert_and_sort(int* A,int n,int newPoint){
int i,temp;
int* B = new int[n+2];
for(i = 0; i<n+1; ++i)
B[i] = A[i];
B[n+1] = newPoint;
adjust_up(B,n+1);
for(i = n+1; i>1; --i){
temp = A[1];
A[1] = A[i];
A[i] = temp;
adjust_down(A,1,i-1);
}
return B;
}
归并排序
归并排序
- 将两个或者两个以上有序表组合成一个新的有序表,以2-路归并排序为例:
- 1.分解成2个子表,递归进行排序
- 2.合并
#include<stdio.h>
void merge(int* A,int low, int mid, int high){
//A[low],A[low+1],...A[mid]有序 A[mid+1],A[mid+2],...A[high]有序
int i,j,k;
int* C = new int[high-low+1]; //辅助空间n个单元
for(k = low; k<=high; ++k) //copy A to temp C
C[k] = A[k];
for(i = low,j = mid+1 , k=i; i<=mid && j <= high; ++k){
if(C[i]<C[j])
A[k] = C[i++];
else
A[k] = C[j++];
}
while(i <= mid) A[k++] = C[i++]; //前半部分未检测完
while(j <= high) A[k++] = C[j++]; //后半部分
}
void merge_sort(int* A, int low, int high){
//这里为2-路并归排序 所以要进行log2(n)趟
if(low<high){
int mid = (low + high)/2;
//分治low--mid mid+1--high 对子表递归进行排序
merge_sort(A,low,mid);
merge_sort(A,mid+1,high);
//合并
merge(A,low,mid,high); //一趟merge的时间复杂度为O(n)
}
}
//时间复杂度 O(n*log2(n)) 空间复杂度O(n) 稳定
void main(){
int A[]={5,9,8,1,2,9,8,0,4};
printf("before merge_sort:\n");
for(int i=0; i<9; ++i)
printf("%d ",A[i]);
merge_sort(A,0,9);
printf("\nafter merge_sort:\n");
for(i=0; i<9; ++i)
printf("%d ",A[i]);
getchar();
}
分布排序
基数排序
- 不基于比较排序 借助「分配」和「收集」
基数排序分为LSD (least significant digit) 和MSD (most significant digit)排序:LSD的排序方式由键值的最右边开始,MSD则相反,由键值的最左边开始
#include<stdio.h>
// 获取x这个数的d位数上的数字
// 比如获取123的1位数,结果返回3
int get_digit(int x,int d){
int a[]={1,10,100,1000};
return ( (x/a[d]) %10 );
}
void radix_sort_LSD(int* A, int low,int high,int digit){
//digit:输入数据的最高位数
int radix = 10; //基数 一趟下来需要radix个桶,空间复杂度为O(radix)
int i,j,d;
int* count = new int[radix];
int* bucket = new int[high-low+1];
//从低位到高位执行排序过程LSD,经历d趟分配和收集
for(d=1; d<=digit; ++d){
//置空各个桶的数据个数
for(i=0; i<radix; ++i)
count[i]=0;
//统计各个桶要装入的数据个数
for(i=low; i<=high; ++i){
j = get_digit(A[i],d);
count[j]++;
}
//count[i]表示第i个桶的右边界索引
for(i=1; i<radix; ++i)
count[i] += count[i-1];
//将数据依次装入桶中 分配,时间复杂度为O(high-low+1),即O(n)
//这里要从右向左扫描,保证排序稳定性
for(i=high; i>=low; --i){
j = get_digit(A[i],d); //求出A[i]第k位的数字,如598的第2位是9
bucket[ count[j]-1 ] = A[i]; //放入对应的桶中,count[j]-1是第j个桶的右边界
count[j]--; //对应桶的装入数据索引减1
}
//将已经分配好的桶中数据倒出来,此时已是对应当前位数有序的表
//收集 此处虽然循环了high-low+1次,但实质上收集的时间复杂度为O(radix),这里的bucket经过之前的索引处理
for(i=low, j=0; i<=high; ++i,++j)
A[i] = bucket[j];
}
}
//时间复杂度O(d*(n+r)) 空间复杂度O(r) 稳定
void main(){
int A[]={110,119,7,911,114,120,122};
printf("before radix_sort_LSD:\n");
for(int i=0; i<7; ++i)
printf("%d ",A[i]);
radix_sort_LSD(A,0,6,3);
printf("\nafter radix_sort_LSD:\n");
for(i=0; i<7; ++i)
printf("%d ",A[i]);
getchar();
}
计数排序
- 假设数组A中小于元素a的个数为n,则直接把a放到第n+1个位置上。当存在几个相同的元素时要做适当的调整,因为不能把所有的元素放到同一个位置上
#include<stdio.h>
//A中元素个数为N 假设数组A的元素大小范围为0~range
void count_sort(int *A, int *B, int *Order,int N, int range){
//int C[range+1]={0};
int *C=new int[range+1];
int i;
for(i=0; i<range; ++i)
C[i]=0;
//pos = A[i]
//C[pos] = A中等于A[i]的元素个数
for(i=0; i<N; ++i){
C[A[i]]++;
}
//C[pos] = A中小于等于A[i]的元素个数
for(i=1; i<range; ++i){
C[i]+=C[i-1];
}
//count = C[pos] -1 A中小于A[i]的元素个数
//B[count] = A排序后的序列
int count=0;
for(i=N-1; i>=0; --i){
count=C[A[i]]-1;
B[count]=A[i];
Order[count]=i; //记录B中元素的原始位置
C[A[i]]--; //A存在相同元素时的处理方法
}
delete []C;
}
//时间复杂度为O(N+range) 空间复杂度为O(N+range)
void main(){
int A[9] = {5, 9, 8, 1, 2, 9, 8, 0, 4}, B[9],Order[9];
count_sort(A,B,Order,9,10);
printf("before count_sort:\n");
for(int i=0; i<9; ++i)
printf("%d ",A[i]);
printf("\nafter count_sort:\n");
for(i=0; i<9; ++i)
printf("%d ",B[i]);
printf("\nprimary order:\n");
for(i=0; i<9; ++i)
printf("%d ",Order[i]);
getchar();
}