一.选择排序
最简单的排序算法,即遍历整个数组,依次比较相邻两位置的元素大小,并进行交换操作
void sellection_sort(int *arr, int l, int r){
for(int i = l; i < r - 1; i++){
int ind = i;
for(int j = i + 1; j < r; j++){
if(arr[j] < arr[ind]) ind = j;
}
swap(&arr[i], &arr[ind]);
}
return ;
}
二.插入排序
将数组分为已排序区和待排序区,前半部分为已排序区,后半部分为待排序区,遍历待排序区,每次从待排序中取一个最小的值,与待排序区的第一个元素交换,并将其并入已排序区。
void insert_sort(int *arr, int l, int r){
for(int i = l + 1; i < r; i++){
int j = i;
while(j > l && arr[j] < arr[j - 1]){
swap(&arr[j], &arr[j - 1]);
j--;
}
}
return ;
}
为了降低其时间复杂度,可以尝试将while循环中的条件判断语句减少。
void unguard_insert_sort(int *arr, int l, int r){
int ind = l;
for(int i = l + 1; i < r; i++){
if(arr[i] < arr[ind]) ind = i;
}
while(ind > l){
swap(&arr[ind], &arr[ind - 1]);
ind--;
}
for(int i = l + 1; i < r; i++){
int j = i;
while(arr[j] < arr[j - 1]){
swap(&arr[j], &arr[j - 1]);
j--;
}
}
return ;
}
先找到数组中的最小元素并将其放在数组的第一位,之后再进行插入排序,这样arr[j] < arr[j - 1]
成立时,j > l就成立。
三.希尔排序
希尔排序是将待排序数组分为若干组,每组的元素的数组下标相差一个步长step,共分为step组,对每组元素进行插入排序,每进行完一次step后,就减小step的值,直到step为1停止。
第一种步长的设计方式可以是每次取原步长的一半,初始步长为待排序区间长度的一半。
void shell_sort(int *arr, int l, int r){
int k = 2, n = r - l, step;
do{
step = n / k == 0 ? 1 : n / k;
for(int i = 1; i < step + 1; i++){
unguard_insert_sort(arr, i - 1, r, step);
}
k *= 2;
}while(step != 1);
return ;
}
第二种步长设计可以是2^k - 1。
void shell_sort_hibbard(int *arr, int l, int r){
int step = 1, n = r - l;
while(step <= n / 2) step = step * 2 + 1;
do{
step /= 2;
for(int i = 1; i < step + 1; i++){
unguard_insert_sort(arr, i - 1, r, step);
}
}while(step != 1);
return ;
}
以上两种步长设计方法下的插入排序的实现:
void unguard_insert_sort(int *arr, int l, int r, int step){
int ind = l;
for(int i = l + step; i < r; i += step){
if(arr[i] < arr[ind]) ind = i;
}
while(ind > l){
swap(&arr[ind], &arr[ind - step]);
ind -= step;
}
for(int i = l + 2 * step; i < r; i += step){
int j = i;
while(arr[j] < arr[j - step]){
swap(&arr[j], &arr[j - step]);
j -= step;
}
}
return ;
}
四.冒泡排序
冒泡排序分为已排序区和待排序区,已排序区在后, 待排序区在前,每次在待排序中找到最大的元素放在待排序的最后一位,并将其并入已排序区。
void bubble_sort(int *arr, int l, int r){
for(int i = r - 1; i > l; i--){
int cnt = 0;
for(int j = l; j < i; j++){
if(arr[j] <= arr[j + 1]) continue;
swap(&arr[j], &arr[j + 1]);
cnt++;
}
if(cnt == 0) break;
}
return ;
}
其中记录待排序中的元素交换次数,来判断待排序是否已经有序。
五.快速排序
快速排序是对原数组设置基准值将数组中所有小于该基准值的元素全部放在其左边,大于其的元素全部放在其右边,然后对左右两片区域重复上述操作。
具体实现是设置头尾指针,交替交换头尾指针所指向的值
void quick_sort(int *arr, int l, int r){
if(r - l <= 2){
if(r - l <= 1) return ;
if(arr[l] > arr[l + 1]) swap(&arr[l], &arr[l + 1]);
return ;
}
int head = l, tail = r - 1, z = arr[l];
while(head < tail){
while(head < tail && z <= arr[tail]) --tail;//尾指针前移
if(head < tail) arr[head++] = arr[tail];
while(head < tail && z >= arr[head]) ++head;//头指针后移
if(head < tail) arr[tail--] = arr[head];
}
arr[head] = z;
quick_sort(arr, l, head);
quick_sort(arr, head + 1, r);
}
对于其的优化可以减少while中的判断条件,
void quick_sort2(int *arr, int l, int r){//除去while循环中head<tail的判断条件
if(r - l <= 2){
if(r - l <= 1) return ;
if(arr[l] > arr[l + 1]) swap(&arr[l], &arr[l + 1]);
return ;
}
int head = l, tail = r - 1, z = arr[l];
do{
while(arr[head] < z) ++head;
while(arr[tail] > z) --tail;
if(head <= tail){
swap(&arr[head], &arr[tail]);
head++, tail--;
}
}while(head <= tail);
quick_sort(arr, l, head);
quick_sort(arr, head, r);
return ;
}
基准值的选取,会影响递归树的高度,即程序运行的时间,所以我们可以使用三点去中法选取一个更加合理的基准值进行排序。
int three_point_sellect(int *a, int *b, int *c){
if(*a > *b) swap(a, b);
if(*a > *c) swap(a, c);
if(*b > *c) swap(b, c);
return *b;
}
void quick_sort3(int *arr, int l, int r){//用三点取中求基准值z
if(r - l <= 2){
if(r - l <= 1) return ;
if(arr[l] > arr[l + 1]) swap(&arr[l], &arr[l + 1]);
return ;
}
int head = l, tail = r - 1, z = three_point_sellect(&arr[l], &arr[r - 1], &arr[(l + r) / 2]);
do{
while(arr[head] < z) ++head;
while(arr[tail] > z) --tail;
if(head <= tail){
swap(&arr[head], &arr[tail]);
head++, tail--;
}
}while(head <= tail);
quick_sort(arr, l, head);
quick_sort(arr, head, r);
return ;
}
该排序函数每一次调用都要进行两次递归操作,十分浪费时间,我们可以将其改为单边递归的形式。
void quick_sort4(int *arr, int l, int r){//单边递归
while(l < r){
if(r - l <= 2){
if(r - l <= 1) return ;
if(arr[l] > arr[l + 1]) swap(&arr[l], &arr[l + 1]);
return ;
}
int head = l, tail = r - 1, z = three_point_sellect(&arr[l], &arr[r - 1], &arr[(l + r) / 2]);
do{
while(arr[head] < z) ++head;
while(arr[tail] > z) --tail;
if(head <= tail){
swap(&arr[head], &arr[tail]);
head++, tail--;
}
}while(head <= tail);
quick_sort(arr, l, head);
l = head;
}
return ;
}
六.归并排序
归并排序是将待排序的数组均分为两部分,并对每一部分进行同样操作直到每一部分元素数量小于2,将每一部分排好序,在依次合并为有序数组,直到原数组有序。
void merge_sort(int *arr, int l, int r){
if(r - l <= 1) return ;
int mid = (l + r) / 2;
merge_sort(arr, l, mid);
merge_sort(arr, mid, r);
int p1 = l, p2 = mid, k = 0;
int *temp = (int *)malloc(sizeof(int) * (r - l));
for(int i = 0; i < r - l; i++){
if(p2 == r || (p1 < mid && arr[p1] < arr[p2])) temp[k++] = arr[p1++];
else temp[k++] = arr[p2++];
}
for(int i = l; i < r; i++) arr[i] = temp[i - l];
free(temp);
return ;
}
七.基数排序
首先确定基数,对在该基数进制下先后个位和十位进行排序。
对于三十二位整型来说,我们以2^16为基数,这样对于待排序的元素类型可以到三十二位整型。
#define K 65536
#define max_n 10
void radix_sort(int *arr, int l, int r){
int *cnt = (int *)malloc(sizeof(int) * K);
int *temp = (int *)malloc(sizeof(int) * (r - l));
//以个位进行排序
memset(cnt, 0, sizeof(int) * K);
for(int i = l; i < r; i++) cnt[arr[i] % K]++;
for(int i = 1; i < K; i++) cnt[i] += cnt[i - 1];
for(int i = r - 1; i >= l; i--) temp[--cnt[arr[i] % K]] = arr[i];
memcpy(arr + l, temp, sizeof(int) * (r - l));
//以十位进行排序
memset(cnt, 0, sizeof(int) * K);
for(int i = l; i < r; i++) cnt[arr[i] / K]++;
for(int i = 1; i < K; i++) cnt[i] += cnt[i - 1];
for(int i = r - 1; i >= l; i--) temp[--cnt[arr[i] / K]] = arr[i];
memcpy(arr + l, temp, sizeof(int) * (r - l));
return ;
}
八.总结
对于以上七种排序,其中插入,冒泡,归并,基数为稳定排序,其他为不稳定排序。
归并排序为外部排序, 其他为内部排序。
稳定性指排序后相同大小的元素的相对位置不变。
内外部性指待排序的数据是否能直接对到内存中进行排序,对于大数据而言,归并排序是可以对其进行处理的。