讲的很简单,因为懒得打字,之后会更新,看不懂可以在评论区问。
源码:https://github.com/lilydedbb/algorithm/blob/master/main.c
这里就从选择排序和插入排序说起,为什么是这两种排序算法,因为效率更高的排序算法一般都是从他们演变改进而来,比如希尔排序,归并排序和应用最多的快排。
选择排序:从第一个元素开始遍历,遍历到某个元素时,从该元素的下一个元素开始,一直到最后一个元素,找到比该元素小且是最小元素的位置,然后交换该元素和这个最小的元素。
插入排序:从第二个元素开始遍历,检查该元素是否比前一个元素小,如果小,则交换位置并且继续比较前前一个元素,知道找到该元素应该在的位置。
选择排序和插入排序的C代码实现:
#include <stdio.h>
void exchange(int *arr, int index1, int index2);
void selectSort(int *arr, int len);
void insertSort(int *arr, int len);
int main() {
int N;
int i;
printf("Input the length of the array: ");
scanf("%d", &N);
int arr[N];
printf("Input the original array: ");
for(i = 0; i < N; i++){
scanf("%d", &arr[i]);
}
selectSort(arr, N);
// insertSort(arr, N);
for(i = 0; i < N; i++){
printf("%d ", arr[i]);
}
return 0;
}
void selectSort(int *arr, int len){
for(int i = 0; i < len; i++){
int index = i, min = arr[i];
for(int j = i + 1; j < len; j++){
if(arr[j] < min){
index = j;
min = arr[j];
}
}
if(index != i)
exchange(arr, i, index);
}
}
void insertSort(int *arr, int len){
for(int i = 1; i < len; i++){
for(int j = i; j > 0 && arr[j] < arr[j - 1]; j--){
exchange(arr, j, j - 1);
}
}
}
void exchange(int *arr, int index1, int index2){
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
对于长度为N的数组,选择排序需要大约N^2 / 2
次比较和N
次交换。插入排序最坏情况下需要N^2 / 2
次比较和N^2 / 2
次交换,最好情况下需要N - 1
次比较和0
次交换,平均情况下需要N^2 / 4
次比较和N^2 / 4
次交换。
插入排序在排序
部分有序
的数组时,表现很优异,而选择排序的效率却不会提高。原因就是插入排序在排序部分有序
的数组的时候,会立刻发现其中有序的一部分,不再进行多余的比较和交换,而选择排序则不然,对于任何形式的数组,去论其混乱程度怎样,都需要N^2级的运行时间
希尔排序,就是在插入排序的基础上改进得来的,比插入排序效率更高,但其具体的时间复杂度和空间复杂度的尚不明确,大致为N^(3/2)
级时间复杂度。
希尔排序的代码实现:
void shellSort(int *arr, int len){
int k = 1;
while(k < len / 3)
k = k * 3 + 1;
while(k >= 1){
for(int i = k; i < len; i++){
for(int j = i; j - k >= 0 && arr[j] < arr[j - k]; j--){
exchange(arr, j, j - k);
}
}
k /= 3;
}
}
归并排序【从基础到高级】
归并排序的基本思想是:把数组分为左右两个小数组,然后分别对左右两个数组排序,然后再通过merge()
函数将排序后的左右数组合并起来。
自顶向下和自底向上的归并排序都需要1/2NlgN
至NlgN
次比较,最多访问数组6NlgN
次。
其具体实现又分为自顶向下和自底向上两种,代码实现:
// 自顶向下的归并排序
void mergeSort(int *arr, int low, int high){
if(high <= low)
return;
int mid = low + (high - low) / 2;
mergeSort(arr, low, mid);
mergeSort(arr, mid + 1, high);
merge(arr, low, mid, high);
}
// 自底向上的归并排序
void BUMergeSort(int *arr, int len){
for(int i = 1; i < len; i *= 2){
for(int j = 0; j < len; j += 2 * i){
merge(arr, j, j + i - 1, fmin(j + 2 * i - 1, len - 1));
}
}
}
// 归并左右数组
void merge(int *arr, int low, int mid, int high){
int i = low, j = mid + 1, k;
// 用一个临时数组暂存
int temp[high + 1];
for(k = low; k <= high; k++){
temp[k] = arr[k];
}
// 归并
for(k = low; k <= high; k++){
if(i > mid)
arr[k] = temp[j++];
else if(j > high)
arr[k] = temp[i++];
else if(temp[j] < temp[i])
arr[k] = temp[j++];
else
arr[k] = temp[i++];
}
}
快排是在归并排序的基础上得来的,是一种分治的排序算法,主要思想是,选取数组中的一个数作为基准,然后使基准左边的的数都小于基准,基准右边的数都大于基准,然后再分别对左右数组递归排序【这是简单的二分法快排实现】
// 快速排序
void quickSort(int *arr, int low, int high){
if(high <= low)
return;
int pivot = partition(arr, low, high);
quickSort(arr, low, pivot - 1);
quickSort(arr, pivot + 1, high);
}
int partition(int *arr, int low, int high){
int pivot = arr[low];
int i = low, j = high + 1;
while(1){
while (arr[++i] < pivot)
if(i == high) break;
while (arr[--j] > pivot)
if(j == low) break;
if(i >= j) break;
exchange(arr, i, j);
}
exchange(arr, j, low);
return j;
}
二分法实现方式忽略了数组中可能有很多和基准数相同大小的数,没有对这些数作出处理,虽不影响结果的正确性,但是使下一步递归排序的子数组规模不是最小的,即性能上不是最优的。在二分法的基础上,三分法实现方式优化了这一问题。
三分法快排代码实现如下:
// 三向切分的快速排序
void quickSort3way(int *arr, int low, int high){
if(high <= low)
return;
int pleft = low, i = low + 1, pright = high;
int pivot = arr[low];
while(i <= pright){
if(arr[i] < pivot){
exchange(arr, i++, pleft++);
}else if (arr[i] > pivot){
exchange(arr, i, pright--);
}else{
i++;
}
quickSort3way(arr, low, pleft - 1);
quickSort3way(arr, pright + 1, high);
}
}
改进快速排序除了用三向切分实现,还可以综合插入排序优化。
因为对于小数组,快速排序比插入排序慢,所以可以递归到一定程度之后,改用插入排序实现,如:
if(high <= low)
return;
// 这两个语句可以改为
if(high <= low + M)
InsertSort(arr, low, high);