归并排序
归并排序:不断递归的将待排序序列分裂为左右两个待排序序列,直到左右子序列为独立的值,视单个独立的值为有序,合并这两个有序的数,得到一个新的有序的子序列,依次得到对应位置的新的有序子序列,然后再次合并这两个新的有序数列,得到更大的新的有序子序列。
核心:合并两个已经有序的数列得到新的有序数列。
#include<stdio.h>
#include<stdlib.h>
/* 归并排序 */
void print_array(int* a, int start, int end){ /* 打印数组 */
int k;
for(k=start;k<=end;k++)
printf("%d ",a[k]);
printf("\n");
}
void merge(int *a, int left, int mid, int right){ /* 将数组 a 分割出的两部分已经有序的序列合并为一个整体有序的序列*/
int i;
int j;
int k;
int llen=mid-left+1; /* 计算 a 左部分有序数组的长度 */
int rlen=right-mid; /* 计算 a 右部分有序数组的长度 */
int* L=(int*)malloc(llen*sizeof(a[0])); /* 动态分配数组 L 用于拷贝数组 a 左部分有序数组的数值,数组 L 的长度为 llen */
int* R=(int*)malloc(rlen*sizeof(a[0])); /* 动态分配数组 R 用于拷贝数组 a 右部分有序数组的数值,数组 R 的长度为 rlen */
for(i=0,k=left;i<llen;i++,k++) /* L 拷贝数组 a 的左部分有序数组的值*/
L[i]=a[k];
for(j=0,k=mid+1;j<rlen;j++,k++) /* R 拷贝数组 a 的右部分有序数组的值*/
R[j]=a[k];
i=0,j=0,k=left; /* 由此开始合并有序数组 L 和有序数组 R,并将其合并到数组 a 中覆盖原来的值,使数组 a 里的合并序列全部有序*/
while(i<llen&&j<rlen){
if(L[i]<R[j])
a[k++]=L[i++];
else
a[k++]=R[j++];
}
while(i<llen)
a[k++]=L[i++];
while(j<rlen)
a[k++]=R[j++];
free(L); /* 释放 L 所占内存 */
free(R); /* 释放 R 所占内存 */
}
void merge_sort(int *a, int left, int right){
if(right>left){ /* 首先设置好递归的条件,如果数组右端标识不大于左端标识时则停止递归 */
int mid=(left+right)/2; /* 满足递归条件的话就计算中间标识位 mid */
int k; /* k 只是用于后面循环输出已排序数组 */
merge_sort(a, left, mid); /* 将数组 a 的左部分数组分出去排序 */
merge_sort(a, mid+1, right); /* 将数组 a 的右部分数组分出去排序 */
merge(a, left, mid, right); /* 将已经有序左部分和右部分数组合并起来 */
printf("left:%d; right:%d \n",left, right); /* 打印此趟排好序的数组部分的左右端标识*/
/* 打印此趟排好序的部分 */
print_array(a,left,right);
}
}
void main(){
int a[]={5,4,7,6,9,8,1,0,3,2};
int len=sizeof(a)/sizeof(a[0]);
merge_sort(a,0,len-1);
}
/*
left:0; right:1
4 5
left:0; right:2
4 5 7
left:3; right:4
6 9
left:0; right:4
4 5 6 7 9
left:5; right:6
1 8
left:5; right:7
0 1 8
left:8; right:9
2 3
left:5; right:9
0 1 2 3 8
left:0; right:9
0 1 2 3 4 5 6 7 8 9
*/
快速排序
快速排序:每趟排序选择一个值做为衡量值,然后将待排序序列分为左右两个待排序的子序列,其中左序列的数全部小于该衡量值,右序列的数全部大于该衡量值,然后继续按照此法分别为左右两个待排序的子序列排序。
核心:将待排序序列分割为左序列全部小于某个数右序列全部大于某个数。
#include<stdio.h>
void print_array(int* a, int start, int end){ /* 打印数组 */
int k;
for(k=start;k<=end;k++)
printf("%d ",a[k]);
printf("\n");
}
void quick_sort(int *a, int left, int right){ /* left 为数组 a 的左端标识, right为数组 a 的右端标识 */
if(right>left){ /* 当right 小于 left 时跳出递归 */
int flag=a[right]; /* flag 为选出的衡量值,默认数组 a 的最后一个为衡量值,将比flag大的放到flag右端,比flag小的放到数组右端 */
int i=left; /* i 从前向后找比 flag 大的数用于换到 flag 后面 */
int j=right; /* j 从前向后找比 flag 小的数用于换到 flag 前面 */
while(i<j){
while(a[i]<=flag&&i<j) /* 从 i 到 j 部分的数组中找比flag 大的数,如果小于flag 则 i 继续加 1,即向后继续找 */
i++;
if(i<j) /* 当退出循环时 i 依然小于 j 说明找到了比 flag 大的数,而不是找到 flag 本身才退出循环 */
a[j--]=a[i]; /* 将该大于flag的数 a[i] 换到 flag 后部分 j 所标识的位置,并令 j 减1, 此时 a[i] 为重复数组,需要之后找到比flag小的数来替换a[i] */
while(a[j]>flag&&i<j) /* 从 j 到 i 部分的数组中找比flag 小的数,如果大于flag 则 j 继续减 1,即向前继续找 */
j--;
if(i<j) /* 当退出循环时 i 依然小于 j 说明找到了比 flag 小的数,而不是找到 flag 本身才退出循环 */
a[i++]=a[j]; /* 将该小于flag的数 a[j] 换到 flag 前部分 i 所标识的位置,并令 i 加 1, 此时 a[j] 为重复数组,需要之后找到比flag大的数来替换a[j] */
}
a[i]=flag; /* 一趟排序循环退出时,i=j,且a[i]处为重复数组,a[i] 之前的都比flag小,之后的都比flag 大,因此将flag 值拷贝给a[i] */
printf("flag:%d ",flag); /* 打印此趟排序部分的结果 */
print_array(a,left,right);
quick_sort(a,left,i-1); /* 对flag 之前的部分,即从 left 到 i-1 部分继续排序 */
quick_sort(a,i+1,right); /* 对flag 之后的部分,即从 i+1 到 right 部分继续排序 */
}
}
void main(){
int a[]={5,4,7,6,9,8,1,0,3,2};
int len=sizeof(a)/sizeof(a[0]);
quick_sort(a,0,len-1);
printf("result: ");
print_array(a,0,len-1)
}
/*
flag:2 0 1 2 6 9 8 7 4 3 5
flag:1 0 1
flag:5 3 4 5 7 8 9 6
flag:4 3 4
flag:6 6 8 9 7
flag:7 7 9 8
flag:8 8 9
result: 0 1 2 3 4 5 6 7 8 9
*/
希尔排序
希尔排序:通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
核心:步长由大到小的插入排序。
#include<stdio.h>
void print_array(int* a, int start, int end){ /* 打印数组 */
int k;
for(k=start;k<=end;k++)
printf("%d ",a[k]);
printf("\n");
}
void shell_sort(int *a, int n){
int h; /* 用于标识步长 */
int i;
int j;
int tmp; /* 用于存储当前需要插入的值 */
for(h=n/2;h>=1;h=h/2){ /* 控制步长 h */
printf("h:%d \n",h);
for(i=h;i<n;i++){ /* i初始值为h,对于间隔为 h 步长的序列,序列的第一个数标号为0,默认为已经有序,则插入排序从该序列的第二个数,即标号 0+h 的数开始 */
tmp=a[i]; /* 默认待排序序列中的首个数为待插入值 */
printf("after sort %d :",a[i]); /* 打印当前待插入的值 */
for(j=i-h;j>=0;j=j-h){ /* 在间隔为 h 的序列,j=i-h 即为 j 标识该间隔序列中 i 的前一个数,从 i 的前一个数开始查找待插入值的插入位置*/
if(a[j]>tmp )
a[j+h]=a[j];
else
break;
}
a[j+h]=tmp; /* 将待插入值拷贝到合适的插入位置 */
print_array(a,0,n-1); /* 打印此趟插入 a[i] 的结果 */
}
}
}
void main(){
int a[]={5,4,7,6,9,8,1,0,3,2};
int len=sizeof(a)/sizeof(a[0]);
shell_sort(a,len);
}
/*
h:5
after sort 8: 5 4 7 6 9 8 1 0 3 2
after sort 1: 5 1 7 6 9 8 4 0 3 2
after sort 0: 5 1 0 6 9 8 4 7 3 2
after sort 3: 5 1 0 3 9 8 4 7 6 2
after sort 2: 5 1 0 3 2 8 4 7 6 9
h:2
after sort 0: 0 1 5 3 2 8 4 7 6 9
after sort 3: 0 1 5 3 2 8 4 7 6 9
after sort 2: 0 1 2 3 5 8 4 7 6 9
after sort 8: 0 1 2 3 5 8 4 7 6 9
after sort 4: 0 1 2 3 4 8 5 7 6 9
after sort 7: 0 1 2 3 4 7 5 8 6 9
after sort 6: 0 1 2 3 4 7 5 8 6 9
after sort 9: 0 1 2 3 4 7 5 8 6 9
h:1
after sort 1: 0 1 2 3 4 7 5 8 6 9
after sort 2: 0 1 2 3 4 7 5 8 6 9
after sort 3: 0 1 2 3 4 7 5 8 6 9
after sort 4: 0 1 2 3 4 7 5 8 6 9
after sort 7: 0 1 2 3 4 7 5 8 6 9
after sort 5: 0 1 2 3 4 5 7 8 6 9
after sort 8: 0 1 2 3 4 5 7 8 6 9
after sort 6: 0 1 2 3 4 5 6 7 8 9
after sort 9: 0 1 2 3 4 5 6 7 8 9
*/
堆排序
堆排序:首先将待排序序列看成一个顺序存储的二叉树,然后将其调整为一个大顶堆,将堆顶元素与堆的最后一个元素交换,则最后一个值即为最大值,接着将前n-1个序列再次调整为一个大顶堆,将当前堆顶元素与当前堆最后一个元素交换得到次大值,重复该过程,直到只剩一个元素为最小值为止。
核心:构建堆;交换当前堆顶元素与当前堆的最后一个值。
#include<stdio.h>
void swap(int* a, int* b){ /* 交换两个数的值 */
int tmp;
tmp=*a;
*a=*b;
*b=tmp;
}
void print_array(int* a, int start, int end){ /* 打印数组 */
int k;
for(k=left;k<=right;k++)
printf("%d ",a[k]);
printf("\n");
}
void adjust_max_heap(int* a, int pos, int len){ /* 把数组a看作完全二叉树,根节点标号为0,将其调整成大顶堆,pos 为开始调整的位置,len 为数组a的长度*/
int l_child=2*pos+1; /* 计算pos 左孩子的标号,因为根节点标号从0 开始,所以左孩子标号为 2*pos+1 */
if(l_child<len){ /* 如果pos 存在左孩子,则可以开始调整 */
int father=pos; /* 默认pos 为该堆的父节点 father*/
int max=pos; /* 默认pos 为值最大的节点*/
int r_child=l_child+1; /* 计算右孩子的标号 */
if(r_child<len&&a[r_child]>a[l_child]) /* 如果存在右孩子,且右孩子的值大于左孩子 */
max=r_child; /* 则max 标识右孩子为最大值 */
else
max=l_child; /* 若没有右孩子,或者右孩子小于左孩子,则用max 标识左孩子为最大值 */
max=a[father]>a[max]?father:max; /* 将当前父节点的值与当前的最大值比较,找到最大值的标号 */
if(max!=father){ /* 如果最大值不是当前父节点,说明需要进行调整 */
swap(&a[father],&a[max]);
print_array(a,0,len-1); /* 打印调整后的数组 */
if(max==l_child) /* 如果max为左孩子,说明父节点与左孩子发生过交换,因此需要继续调整左子树,使之依旧为大顶堆 */
adjust_max_heap(a,l_child,len);
if(max==r_child) /* 如果max为右孩子,说明父节点与右孩子发生过交换,因此需要继续调整右子树,使之依旧为大顶堆 */
adjust_max_heap(a,r_child,len);
}
}
}
void max_heap_sort(int* a, int len){
int i;
int pos;
for(i=len-1;i>0;i--){ /* 控制排序次数也即构建大顶堆的次数*/
printf("\n%dth sort:\n",len-i);
for(pos=i/2;pos>=0;pos--) /* 内层循环负责控制构建堆的起使标号和构建堆的数组的长度,每排好一个数,下次需要构建堆的数组长度就减1,也即不用再考虑后面已排序的数了 */
adjust_max_heap(a,pos,i+1);
swap(&a[0],&a[i]); /* 每构建一个大顶堆就得到了一个最大值,也即根节点a[0]处的值,将该最大值换到数组末尾,就排好了一个数 */
printf("result: ");
print_array(a,0,len-1);
}
}
void main(){
int a[]={5,4,9,8,7,6,0,1,3,2};
int len=sizeof(a)/sizeof(a[0]);
max_heap_sort(a,len);
}
/*
1th sort:
5 8 9 4 7 6 0 1 3 2
9 8 5 4 7 6 0 1 3 2
9 8 6 4 7 5 0 1 3 2
result: 2 8 6 4 7 5 0 1 3 9
2th sort:
8 2 6 4 7 5 0 1 3
8 7 6 4 2 5 0 1 3
result: 3 7 6 4 2 5 0 1 8 9
3th sort:
7 3 6 4 2 5 0 1
7 4 6 3 2 5 0 1
result: 1 4 6 3 2 5 0 7 8 9
4th sort:
6 4 1 3 2 5 0
6 4 5 3 2 1 0
result: 0 4 5 3 2 1 6 7 8 9
5th sort:
5 4 0 3 2 1
5 4 1 3 2 0
result: 0 4 1 3 2 5 6 7 8 9
6th sort:
4 0 1 3 2
4 3 1 0 2
result: 2 3 1 0 4 5 6 7 8 9
7th sort:
3 2 1 0
result: 0 2 1 3 4 5 6 7 8 9
8th sort:
2 0 1
result: 1 0 2 3 4 5 6 7 8 9
9th sort:
result: 0 1 2 3 4 5 6 7 8 9
*/