数值算法: 解方程、微积分、有限元分析、信号处理,等等
非数值算法: 排序、查找
一、冒泡排序:
1、算法:
9,7,5,3,1
扫描1: 7,5,3,1,9
扫描2: 5,3,1,7,9
…
扫描N-1: 1,3,5,7,9
-
(1) 比较相邻的元素, 如果第一个比第二个大, 就交换它们两个
-
(2) 对每一对相邻元素做同样的工作, 从开始的第一对到结尾的最后一对. 经过这一步, 最后的元素将是最大值
-
(3) 针对所有的元素重复以上步骤, 除了最后一个
-
(4) 持续每次对越来越少的元素重复以上步骤, 直至没有元素需要交换
范例:
#include <stdio.h>
void bubble_sort(int data[], size_t size)
{
size_t i; // 第几趟
for (i = 0; i < size - 1; ++i) {
int ordered = 1;
size_t j; // 第几次
for (j = 0; j < size - 1 - i; ++j) {
if (data[j+1] < data[j]) {
data[j] = data[j] ^ data[j+1];
data[j+1] = data[j] ^ data[j+1];
data[j] = data[j] ^ data[j+1];
ordered = 0;
}
}
if (ordered) {
break;
}
}
}
int main(void)
{
int data[] = {9,0,7,2,5,4,3,6,1,8};
size_t size = sizeof(data) / sizeof(data[0]);
bubble_sort(data, size);
size_t i;
for (i = 0; i < size; ++i) {
printf("%d ", data[i]);
}
printf("\n");
return 0;
}
/*
Output:
0 1 2 3 4 5 6 7 8 9
*/
2、评价:
平均时间复杂度,O(N^2),稳定,对数据有序性非常敏感。
稳定是指 数据相同的元素,顺序不变
对数据敏感是指 数据越接近有序,消耗时间越短, 越接近逆序,消耗时间越长
二、插入排序
1、算法
-
(1) 从第一个元素开始, 该元素可以认为已经被排序
-
(2) 取出下一个元素, 在已经排序的元素序列中从后向前扫描
-
(3) 若该元素大于新元素, 则将该元素移到下一位置
-
(4) 若该元素小于或等于新元素, 则将新元素插入到该元素后
-
(5) 重复步骤2, 直至处理完所有元素
范例:
void insert_sort(int data[], size_t size)
{
size_t i;
for (i = 1; i < size; ++i) {
int temp = data[i];
size_t j;
for (j = i; j - 1 >= 0; --j) {
if (data[j-1] > temp) {
data[j] = data[j-1];
}
else {
data[j] = temp;
break;
}
}
}
}
2、评价:
平均时间复杂度, O(N^2),稳定,对数据有序性非常敏感。
冒泡排序是值交换, 而插入排序是值移动, 因此插入排序要优于冒泡排序。
三、选择排序
1、算法:
首先在未排序序列中找到最小元素, 存放到排序序列的起始位置, 然后, 再从剩余未排序元素中继续寻找最小元素, 然后放到排序序列末尾. 以此类推, 直到所有元素均排序完毕.
范例:
void select_sort(int data[], size_t size)
{
size_t i;
for (i = 0; i < size - 1; ++i) {
int k = i;
for (int j = i + 1; j < size; ++j) {
if (data[j] < data[k]) {
k = j;
}
}
if (k != i) {
data[i] = data[i] ^ data [k];
data[k] = data[i] ^ data [k];
data[i] = data[i] ^ data [k];
}
}
}
2、评价:
平均时间复杂度, O(N^2),不稳定。
对数据有序性不敏感。虽然比较次数多, 但数据交换少, 所以一般情况下快于冒泡排序。
四、快速排序
1、算法:
-
(1) 从数列中挑出一个元素, 成为基准;
-
(2) 重新排序数列, 所有比基准小的元素摆放在基准前面, 所有比基准大的元素摆在基准的后面(相同的元素可以放在任一边), 这个过程称为分区
-
(3) 以递归方式对小于基准的元素分区和大于基准的元素分区分别进行排序
范例:
void quick_sort(int data[], size_t left, size_t right)
{
size_t p = (left + right) / 2;
int pivot = data[p];
size_t i = left, j = right;
// i 向右走, j 向左走 当两者重合终止
while (i < j) {
for (; !(i >= p || pivot < data[i]); ++i) {} // 小括号里是离开for循环的条件
if (i < p) {
data[p] = data[i];
p = i;
}
for (; !(j <= p || data[j] < pivot); --j) {}
if (j > p) {
data[p] = data[j];
p = j;
}
}
data[p] = pivot;
if (p - left > 1) {
quick_sort(data, left, p-1);
}
if (right - p > 1) {
quick_sort(data, p + 1, right);
}
}
2、评价:
平均时间复杂度, O(NlogN),N*log2底N的对数。不稳定, 如果每次能均分划分序列, 它是最快的排序算法。
每次大于基准值的个数和小于基准值的个数相差很小, 消耗时间越少。
范例2: 实现C标准库中的快速排序方法
void quick_sort2(void* base, size_t left, size_t right, size_t size,
int(*compar)(const void*, const void*))
{
// 把pb指向的size个字节,copy到pa
// memcpy(pa, pb, size);
// compar函数指针, -1代表参数1小于参数2, 0代表相等, 1代表参数1大于参数2
size_t p = (left + right) / 2;
void* pivot = malloc(size);
memcpy(pivot, base + p * size, size);
size_t i = left, j = right;
while (i < j) {
for (; !(i >= p || compar(base+i*size, pivot) > 0); ++i) {}
if (i < p) {
memcpy(base+p*size, base+i*size, size);
p = i;
}
for (; !(j <= p || compar(base+j*size, pivot) < 0); --j) {}
if (j > p) {
memcpy(base+p*size, base+j*size, size);
p = j;
}
}
memcpy(base + p * size, pivot, size);
free(pivot);
if (p - left > 1) {
quick_sort2(base, left, p - 1, size, compar);
}
if (right - p > 1) {
quick_sort2(base, p + 1, right, size, compar);
}
}
void myqsort(void* base, size_t nmemb, size_t size,
int(*compar)(const void*, const void*))
{
quick_sort2(base, 0, nmemb - 1, size, compar);
}
int main(void)
{
int na[] = {55,23,65,2,4,75};
size_t size = sizeof(na[0]);
size_t numb = sizeof(na) / size;
// c标准库的函数qsort
//qsort(na, numb, size, cmpint);
myqsort(na, numb, size, cmpint);
size_t i;
for (i = 0; i < numb; ++i) {
printf("%d ", na[i]);
}
printf("\n");
const char* sa[] = {"daf", "ddeee", "rre", "awq", "bbfu"};
size = sizeof(sa[0]);
numb = sizeof(sa) / size;
//qsort(sa, numb, size, cmpstr);
myqsort(sa, numb, size, cmpstr);
for (i = 0; i < numb; ++i) {
printf("%s ", sa[i]);
}
printf("\n");
return 0;
}
/*
Output:
2 4 23 55 65 75
awq bbfu daf ddeee rre
*/
五、归并排序
1、算法:
-
(1) 申请空间, 使其大小为两个已经排序序列之和, 该空间用来存放合并后的序列
-
(2) 设定两个指针, 最初位置分别为两个已经排序序列的起始位置
-
(3) 比较两个指针所指向的元素,选择相对小的元素放入到合并空间, 并移动指针到下一个位置
-
(4) 重复步骤3直到某一指针达到序列尾
-
(5) 将另一序列剩下的所有元素直接复制到合并序列尾
2、评价:
平均时间复杂度, O(2NlogN)。稳定, 对数据有序性不敏感. 非就地排序, 需要与待排序序列一样多的辅助空间,
不适用于大数据量的排序。
范例:
// 实现一个外部合并功能的函数, data1和data2都是有序的
void outer_merge(int data1[], size_t size1, int data2[], size_t size2, int data3[]) {
size_t i = 0, j = 0, k = 0;
for (;;) {
if (i < size1 && j < size2) {
if (data1[i] < data2[j]) {
data3[k++] = data1[i++];
}
else {
data3[k++] = data2[j++];
}
}
else if (i < size1) {
data3[k++] = data1[i++];
}
else if (j < size2) {
data3[k++] = data2[j++];
}
else {
break;
}
}
}
// 实现一个内部合并功能的函数, left到mid有序, mid+1到right有序, 实现left到right有序
void inner_merge(int data[], size_t left, size_t mid, size_t right) {
size_t size = (right - left + 1) * sizeof(int);
int* merge = malloc(size);
outer_merge(data + left,
mid - left + 1,
data + mid + 1,
right - mid,
merge);
memcpy(data + left, merge, size);
free(merge);
}
// 归并排序
void merge_sort(int data[], size_t left, size_t right) {
if (left < right) {
int mid = (left + right) / 2;
merge_sort(data, left, mid);
merge_sort(data, mid + 1, right);
inner_merge(data, left, mid, right);
}
}