本章将讲以下几种排序算法:插入排序,合并排序,堆排序,快速排序,计数排序。
所有排序算法最后结果皆为增序
①插入排序,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中。
我们都知道一个数字,总是为有序数列,我们所要做的事情,就是从未排序部分,比较插入之前的有序数列中。整个过程就如同你打斗地主,慢慢抓牌并排序,什么掼蛋还是算了。
如果以数列做例,则如下所示:
代码如下:
#include <stdio.h>
#include <stdlib.h>
void
insertion_sort(int *,int len);
void
arr_print(int *arr,int len);
int*
arr_convertor(char **argv,int *len);
int main(int argc,char **argv) {
int *arr;
int len;
arr = arr_convertor(argv, &len);
arr_print(arr,len);
insertion_sort(arr,len);
arr_print(arr,len);
free(arr);
return 0;
}
void insertion_sort(int *arr,int len){
int i,j;
for(i = 1;i < len;i++){
int tmp = arr[i];
for(j = i-1;j >= 0 && arr[j] > tmp; j--)
arr[j+1] = arr[j];
arr[j+1] = tmp;
}
}
int* arr_convertor(char **argv,int *len){
int i;
char **ptr = argv;
int tmp[100];
while (*++ptr != NULL) {
tmp[i++] = atoi(*ptr);
}
*len = i;
int *parr = malloc(sizeof(int)*i);
for (i = 0; i < *len; i++) {
parr[i] = tmp[i];
}
return parr;
}
void arr_print(int *arr,int len){
int i;
for(i = 0;i < len;i++) printf("%d ",arr[i]);
printf("\n");
}
②合并排序,合并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
合并排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。合并排序也叫归并排序。
#include <stdio.h>
#include <stdlib.h>
void
arr_print(int *arr,int len);
int*
arr_convertor(char **argv,int *len);
void
merge(int *arr,int beg,int mid,int end);
void
merge_sort(int *arr,int beg,int end);
int main(int argc,char **argv) {
int len;
int *arr = arr_convertor(argv,&len);
arr_print(arr,len);
merge_sort(arr,0,len-1);
arr_print(arr,len);
free(arr);
return 0;
}
void merge(int *arr,int beg,int mid,int end) {
int fore_len = mid - beg + 1;
int back_len = end - mid;
int *fore_arr = malloc(sizeof(int) * fore_len);
int *back_arr = malloc(sizeof(int) * back_len);
int i,j;
for(i = 0;i < fore_len;i++) {
fore_arr[i] = arr[beg + i];
}
for(i = 0;i < back_len;i++) {
back_arr[i] = arr[mid + i + 1];
}
i = 0,j = 0;
while ((i+j+beg <= end)) {
if (i >= fore_len) {
arr[i+j+beg] = back_arr[j];
j++;
} else if (j >= back_len) {
arr[i+j+beg] = fore_arr[i];
i++;
}else {
if (fore_arr[i] < back_arr[j]) {
arr[i+j+beg] = fore_arr[i];
i++;
} else {
arr[i+j+beg] = back_arr[j];
j++;
}
}
}
free(fore_arr);
free(back_arr);
}
void merge_sort(int *arr,int beg,int end){
if(beg < end){
int mid = (beg + end)/2;
merge_sort(arr,beg,mid);
merge_sort(arr,mid+1,end);
merge(arr,beg,mid,end);
}
}
int* arr_convertor(char **argv,int *len){
int i;
char **ptr = argv;
int tmp[100];
while (*++ptr != NULL) {
tmp[i++] = atoi(*ptr);
}
*len = i;
int *parr = malloc(sizeof(int)*i);
for (i = 0; i < *len; i++) {
parr[i] = tmp[i];
}
return parr;
}
void arr_print(int *arr,int len){
int i;
for(i = 0;i < len;i++) printf("%d ",arr[i]);
printf("\n");
}
③堆排序:堆排序是利用堆的性质进行的一种选择排序。
什么是堆?堆数据结构是一种数组对象,它可以被视为一科完全二叉树结构。它的特点是父节点的值大于(小于)两个子节点的值(分别称为最大堆和最小堆)。它常用于管理算法执行过程中的信息,应用场景包括堆排序,优先队列等。
图(a)为堆的完全二叉树形势,图(b)为堆的数组形式,可以看出这是一个最大堆。
以下为求当前节点的父节点和左右节点的公式。
parent = (index - 1) / 2
left = 2 * index + 1
right = 2 * index + 2
那么如何利用堆来排序呢?这里用最大堆来进行升序排序
构建好最大堆后,然后将根,即最大值,与堆尾最后一个元素交换位置,然后排除堆尾,再次对堆定进行最大堆化。得到剩余元素中最大值,插入当前堆尾,排除堆尾。重复该操作知道只剩下最后一个元素的时候,这个时候可以看到堆是有序的了。
#include <stdio.h>
#include <stdlib.h>
#define LEFT(X) (2 * (X) + 1)
#define RIGHT(X) (2 * (X) + 2)
#define PARENT(X) (X-1) / 2
void
arr_print(int *arr,int len);
int*
arr_convertor(char **argv,int *len);
void
max_heapify(int *arr,int len,int index);
void
build_max_heap(int *arr,int len);
void
heap_sort(int *arr,int len);
/*
0
1 2
3 4 5 6
7 8 9 10 11 12 13 14
parent = (index - 1) / 2
left = 2 * index + 1
right = 2 * index + 2
*/
int main(int argc,char **argv) {
int len;
int *arr = arr_convertor(argv,&len);
arr_print(arr,len);
heap_sort(arr,len);
arr_print(arr,len);
free(arr);
return 0;
}
void heap_sort(int *arr,int len){
build_max_heap(arr,len); //cnlgn
int i;
// cnlgn
for(i = len-1;i > 0;i--){
int tmp = arr[0];
arr[0] = arr[i];
arr[i] = tmp;
max_heapify(arr,i,0);
}
}
void build_max_heap(int *arr,int len){
int i;
for(i = PARENT(len-1);i >= 0;i--) {
max_heapify(arr,len,i);
}
}
void max_heapify(int *arr,int len,int index){
int largest;
int left = LEFT(index);
int right = RIGHT(index);
int tmp;
if(left < len && arr[left] > arr[index]) {
largest = left;
}else{
largest = index;
}
if(right < len && arr[right] > arr[largest]){
largest = right;
}
if(largest != index) {
tmp = arr[index];
arr[index] = arr[largest];
arr[largest] = tmp;
arr_print(arr,len);
max_heapify(arr,len,largest);
}
}
int* arr_convertor(char **argv,int *len){
int i;
char **ptr = argv;
int tmp[100];
while (*++ptr != NULL) {
tmp[i++] = atoi(*ptr);
}
*len = i;
int *parr = malloc(sizeof(int)*i);
for (i = 0; i < *len; i++) {
parr[i] = tmp[i];
}
return parr;
}
void arr_print(int *arr,int len){
int i;
for(i = 0;i < len;i++) printf("%4d",arr[i]);
printf("\n");
}
④快速排序:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
#include <stdio.h>
#include <stdlib.h>
void
arr_print(int *arr,int len);
int*
arr_convertor(char **argv,int *len);
int
partition(int *arr,int beg,int end);
void
quick_sort(int *arr,int beg,int end);
int main(int argc,char **argv) {
int len;
int *arr = arr_convertor(argv,&len);
arr_print(arr,len);
quick_sort(arr,0,len-1);
arr_print(arr,len);
free(arr);
return 0;
}
void quick_sort(int *arr,int beg,int end){
if(beg < end){
int seg = partition(arr,beg,end);
quick_sort(arr,beg,seg-1);
quick_sort(arr,seg+1,end);
}
}
int partition(int *arr,int beg,int end){
int pivot = arr[beg];
int i = beg,j = end;
while(i < j) {
while( i < j && arr[j] >= pivot) j--;
arr[i] = arr[j];
while( i < j && arr[i] <= pivot) i++;
arr[j] = arr[i];
}
arr[i] = pivot;
return i;
}
int* arr_convertor(char **argv,int *len){
int i;
char **ptr = argv;
int tmp[100];
while (*++ptr != NULL) {
tmp[i++] = atoi(*ptr);
}
*len = i;
int *parr = malloc(sizeof(int)*i);
for (i = 0; i < *len; i++) {
parr[i] = tmp[i];
}
return parr;
}
void arr_print(int *arr,int len){
int i;
for(i = 0;i < len;i++) printf("%4d",arr[i]);
printf("\n");
}
⑤计数排序:数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。
首先我们有待排序数列 arr 7 6 1 2 3 4 9 5 8 1 1 13
然后已排序数列 sorted_arr
然后计数序列 num_of_couting
然后扫描待排序数列,统计待排序数列中 各个数字出现次数 计数序列的空间为max(arr)-min(arr)+1大小 此处为(13 - 1 + 1 ) 13大小
所以最后得到的计数序列为: 1 2 3 4 5 6 7 8 9 10 11 12 13
num_of_counting 3 1 1 1 1 1 1 1 1 0 0 0 1
然后 num_of_counting[i] = num_of_counting[i] + num_of_counting[i-1]得到各个数字应该在数组中的位置
num_of_counting 3 4 5 6 7 8 9 10 11 11 11 11 12
然后重新扫描序列,比如扫描到 7 ,查看计数序列,7的位置在第9个(由于数组以0开始计数,所以应该减1),即插入sorted_arr[8]的位置,然后,将num_of_counting[7]减少1。
直至,扫描到最后一个数,完成排序。
下图中,A为待排序数列,B为排序后数列,C为计数数列。
#include <stdio.h>
#include <stdlib.h>
void
arr_print(int *arr,int len);
int*
arr_convertor(char **argv,int *len);
int
find_range(int *arr,int len,int *min,int *max);
void
counting(int *arr,int len,int *num_of_counting,int base);
void
re_counting(int *num_of_counting,int range);
void
sort_arr(int *arr,int len,int *sorted_arr,int *num_of_counting,int range,int base);
void
counting_sort(int *arr,int len);
/*
arr 9 8 7 8 2 1 0 8 2
sorted_arr 0 1 2 2 7 8 8 8 9
init:
min max
index 0 1 2 3 4 5 6 7 8 9
counting_num1 1 2 0 0 0 0 1 3 1
after counting:
counting_num1 2 4 4 4 4 4 5 8 9
*/
int main(int argc,char **argv) {
int len;
int *arr = arr_convertor(argv,&len);
arr_print(arr,len);
counting_sort(arr,len);
free(arr);
return 0;
}
void counting_sort(int *arr,int len){
int min,max;
int range = find_range(arr,len,&min,&max);
int *sorted_arr = malloc(sizeof(int)*len);
int *num_of_counting = calloc(range,sizeof(int));
counting(arr,len,num_of_counting,min);
re_counting(num_of_counting,range);
sort_arr(arr,len,sorted_arr,num_of_counting,range,min);
arr_print(sorted_arr,len);
free(num_of_counting);
free(sorted_arr);
}
void sort_arr(int *arr,int len,int *sorted_arr,int *num_of_counting,int range,int base){
int i;
for(i = 0;i < len;i++) {
sorted_arr[num_of_counting[arr[i]-base]-1] = arr[i];
num_of_counting[arr[i]-base]--;
}
}
void re_counting(int *num_of_counting,int range){
int i;
for(i = 1;i < range; i++) {
num_of_counting[i] += num_of_counting[i-1];
}
}
void counting(int *arr,int len,int *num_of_counting,int base){
int i = 0;
for(i = 0;i < len;i++) {
num_of_counting[arr[i]-base]++;
}
}
int find_range(int *arr,int len,int *min,int *max){
int i;
*min = arr[0],*max = arr[0];
for(i = 1; i < len; i++) {
if(arr[i] > *max) *max = arr[i];
else if(arr[i] < *min) *min = arr[i];
}
return (*max - *min + 1);
}
int* arr_convertor(char **argv,int *len){
int i;
char **ptr = argv;
int tmp[100];
while (*++ptr != NULL) {
tmp[i++] = atoi(*ptr);
}
*len = i;
int *parr = malloc(sizeof(int)*i);
for (i = 0; i < *len; i++) {
parr[i] = tmp[i];
}
return parr;
}
void arr_print(int *arr,int len){
int i;
for(i = 0;i < len;i++) printf("%4d",arr[i]);
printf("\n");
}
综合来说,以上各种排序插入排序简单快捷,容易实现。合并排序,堆排序,以及快速排序都有较好的性能,快速排序在数组随机分布情况下的效率是最好的,但是在某些情况下,效率会大幅下降。合并和堆排序都有不错的稳定性,但是由于,堆排序需要实现构建堆这样的数据结构,所以在小数据的时候不宜使用。最后计数排序是典型的,以空间换时间的排序方式。
当然既然说到排序就不得提到一个有意思的排序,bogo sort,俗称猴子排序,简单的说就是 你抓了一手牌,然后往天上一抛,然后祈祷牌落地后是自然有序的。最坏时间复杂度O(无穷),最好时间复杂度O(1)。当然如果未来量子理论能有突破性发展,这完全是一个非常棒的排序方式。
附上排序时间复杂度图