1.常用的算法:4个排序算法和2个查找算法
1.1.冒泡排序算法
例如:
初始状态:9 7 5 3 1
第一趟:7 5 3 1 9(4)
第二趟:5 3 1 7 9(4)
第三趟:3 1 5 7 9(4)
第四趟:1 3 5 7 9(4)
目前来看:N个数据需要N-1趟排序,每趟需要N-1次比较
优化:
初始状态:9 7 5 3 1
第一趟:7 5 3 1 9(4)
第二趟:5 3 1 7 9(3)
第三趟:3 1 5 7 9(2)
第四趟:1 3 5 7 9(1)
目前来看:N个数据需要N-1趟排序,每趟需要N-i次比较
优化:
初始状态:0 7 5 3 1
第一趟:0 5 3 1 7
第二趟:0 3 1 5 7
第三趟:0 1 3 5 7
第四趟:0 1 3 5 7
结论:如果某趟排序中,发现没有发生数据的交换,则认为排序完成
参考代码:sort目录
2.插入排序算法
例如:
初始状态:10 30 20 15 5 //无序的
原始插入排序思想:5 10 15 20 30 //有序的
思路:从无序的数列中取出数据单独放到一个有序的数列中,存放时找到自己对应的位置即可
弊端:需要额外开辟内存来存储有序数列
原地实现插入排序流程:
初始状态:10 30 20 15 5 //无序的
5 10 15 20 30
· 5//data变量暂存被插入的数据
思路:定义一个变量先暂存被插入的数据,然后拿着被插入的数据在前面的有序数列中从后往前扫描 ,找到对应的位置插入即可
3.选择排序算法
例如:
初始值:9 7 5 3 1 //无序的
原始思想:先额外开辟一块内存存储有序数列,从无序的数列中取出最小的数放到有序数列中,然后从剩余无序的数列
中再选择一个最小的放到有序的第二个,以此类推直到剩余一个
弊端:需要额外开辟内存来存储有序数列
解决办法原地完成选择排序:
初始值:9 7 5 3 1
-----------
1 7 5 3 9
-----------
1 3 5 7 9
-----------
1 3 5 7 9
-----------
1 3 5 7 9
-
思路:在无序数列中选择一个最小的放到无序的第一个,依次重复直到剩余最后一个
4.快速排序算法
例如:0 10 80 30 60 50 40 70 20 90 100
思路:不管站在哪个数上去看,它左边的数小于他,它右边的数大于它
原始实现:比如站在50来看,进行一次分组,把比50小的放左边,大的放右边,得到:
0 10 30 40 20 50 60 70 90 80 100
然后对50左边的数列进行再次分组,站在30来看,比它小的放左边,大的放右边
然后对50右边的数列进行再次分组,站在90来看,比它小的放左边,大的放右边,得到:
0 20 10 30 40 50 60 80 70 90 100
以此类推,对30左边和右边再次分组,直到剩余最后一个或者没有无需分组
对90左边和右边再次分组,直到剩余最后一个或者没有无需分组
等到数列实在无法再分组,数列必然有序
0 10 80 30 60 50 40 70 20 90 100
50基准: 0 10 30 40 20 50 60 70 90 80 100
30,90基准: 0 20 10 30 40 50 60 80 70 90 100
20,80基准: 10 0 20 30 40 50 70 60 80 90 100
10,70基准: 0 10 20 30 40 50 60 70 80 90 100
弊端:需要额外开辟内存来存储有序数列
原地实现快排:定义三个游标(三个变量,数据的下标)来实现原地分组(p=pivot=基准),流程:
以50为基准进行一次分组//pivot变量暂存基准值
0 10 80 30 60 50 40 70 20 90 100
i
p
j
这里,i 指向左边界,j 指向右边界,p 指向基准值
而后,i与基准值比较,0小于50,i++,当i到 80,比基准值大,故而将p指向的值变为80,而后将 p 指向 i 的位置,如下
0 10 80 30 60 80 40 70 20 90 100
i
p
j
而后 j 从100 向左与基准值左比较,当指向20时,如下
0 10 80 30 60 80 40 70 20 90 100
i
p
j
此时,将 j 的20挪到 p 的位置,将p挪到 j 的位置,而后右对 i 进行操作,20 <50, i++,直到指向60
0 10 20 30 60 80 40 70 20 90 100
i
p
j
60比基准值大,顾把60挪到p的位置, 然后把p挪到 i 的位置,然后 对j进行操作,j--.直到指向40
0 10 20 30 60 80 40 70 60 90 100
i
p
j
此时将 j 的 40 挪到 p 的位置,p 挪到 j ,而后对 i 进行操作,i++ 到80
0 10 20 30 40 80 40 70 60 90 100
i
p
j
而后将80挪到p的位置, p挪到 i ,对 j操作 j--, 发现 i j位置重合,结束分组,此时将基准值50放到p位置
0 10 20 30 40 50 80 70 60 90 100
i
p
j
vim sort.h
//sort.h:声明
#ifndef __SORT_H
#define __SORT_H
#include <stdio.h>
extern void bubble_sort(int data[], int size); //冒泡
extern void insert_sort(int data[], int size); //插入
extern void select_sort(int data[], int size); //选择
extern void quick_sort(int data[], int left, int right);//快排
#endif
vim sort.c
//sort.c:定义
#include "sort.h"
//定义冒泡排序算法函数
void bubble_sort(int data[], int size) {
int i, j;
for(i = 0; i < size - 1; i++) { //排序的趟次
int ordered = 1;//记录数据是否交换了,如果交换ordered=0,没有交换ordered=1
for(j = 0; j < size-i-1; j++) {//每趟比较的次数
if(data[j+1] < data[j]) {//交换位置
int swap = data[j];
data[j] = data[j+1];
data[j+1] = swap;
ordered = 0;//如果交换位置,ordered置0
}
}
//每趟比较完成之后,判断是否发生了数据交换
//如果交换了则继续后续趟次的比较
//如果没有交换,则退出后续趟次比较,认为排序完成
if(ordered)
break;
}
}
//定义插入排序算法
void insert_sort(int data[], int size) {
int i;
for(i = 1; i < size; i++) { //第一个数据本身有序,无需研究
int inserted = data[i]; //暂存被插入的数据
int j;
//从后往前扫描找到对应的位置
for(j = i; j > 0 && inserted < data[j-1]; --j)
data[j] = data[j-1];//交换位置
//如果被插入的数据比前面的数据都大,位置不动,无需额外将数据往自己的位置重新插入
//10 20 30 40
//40=inserted
if(j != i)
data[j] = inserted; //插入数据
}
}
//定义选择排序算法
void select_sort(int data[] /*int *data*/, int size) {
int i;
for(i = 0; i < size - 1; i++) { //找最小数的趟次,最后一个数据本身有序
int min = i; //假设无序数列中的第1个数据最小,min记录最小数的下标
int j;
for(j = i + 1; j < size; j++) //找最小数
if(data[j] < data[min])
min = j; //更新最小数的下标
if(min != i) { //交换
int swap = data[i];
data[i] = data[min];
data[min] = swap;
}
}
}
//快速排序
void quick_sort(int data[], int left, int right) {
int p = (left+right)/2; //定义基准值,随意
int i = left; //left=0;左边界
int j = right; //right=size-1;右边界
int pivot = data[p]; //暂存基准值
while(i < j) { //进行一次分组,直到i=j停止分组
//i不做i++运算的两种情况:i对应的值大于基准值或者i和p重合
for(; !(i >= p || pivot < data[i]); i++);
//此种情况是因为i对应的值大于基准值而退出上面的for循环,此时交换位置,
//而不是i>=p这种情况
if(i < p) {
data[p] = data[i]; //将i对应的值挪到p的位置
p = i;//调整p到i的位置
}
//j不做j--运算的两种情况:j对应的值小于基准值或者j和p重合
for(;!(j <= p || data[j] < pivot); j--);
//此种情况是因为j对应的值小于基准值而退出上面的for循环,此时交换位置
if(j > p) {
data[p] = data[j]; //将j的值挪到p的位置
p = j;//调整p到j的位置
}
}
data[p] = pivot; //分组结束将基准值放到p的位置
if(p - left > 1)//如果基准值左侧的数据个数大于等于2个继续分组直到剩余1个或者没有
quick_sort(data, left, p-1);//左边界还是0,右边界不包含基准值p
if(right - p > 1)//如果基准值右侧的数据个数大于等于2个继续分组直到剩余1个或者没有
quick_sort(data, p+1, right);//左边界不包含基准值p,右边界还是right
}
vim main.c
//main.c:测试
#include "sort.h"
int main(void) {
int data[] = {9, 0, 7, 2, 5, 4, 1, 6, 3, 8};
int size = sizeof(data)/sizeof(data[0]);
//bubble_sort(data, size); //冒泡
//insert_sort(data, size); //插入
//select_sort(data, size); //选择
quick_sort(data, 0, size-1); //快排
for(int i = 0; i < size; i++)
printf("%d ", data[i]);
printf("\n");
return 0;
}
vim Makefile
#Makefile
BIN=sort
OBJ=main.o sort.o
CC=gcc
$(BIN):$(OBJ)
$(CC) -o $(BIN) $(OBJ)
%.o:%.c
$(CC) -c -o $@ $<
clean:
rm $(BIN) $(OBJ)
2.查找算法
2.1.线性查找算法:
在数列中挨个匹配查找想要的数据,对数据有序性无要求
2.2.二分查找(折半查找)
注意:此方法使用的前提是数据必须有序,无序则先排序
原理:10 20 30 40 50 60 70 //目标找60
对数列对半分,拿着目标值和中间值进行比较,要不找到了要不继续左侧二分找要不继续右侧二分找
vim find.h
//find.h:声明
#ifndef __FIND_H
#define __FIND_H
#include <stdio.h>
extern int line_find(int data[], int size, int key);//线性
extern int half_find(int data[], int size, int key);//二分
#endif
vim find.c
//find.c:定义
#include "find.h"
//定义线性查找函数
int line_find(int data[], int size, int key) {
for(int i = 0; i < size; i++)
if(data[i] == key)
return i; //找到并且返回下标
return -1; //没有找到
}
//定义递归二分查找函数
static int recu_find(int data[], int left, int right, int key) {
if(left <= right) {
int mid = (left+right)/2;
if(key < data[mid])
return recu_find(data, left, mid-1, key); //左边找
else if(key > data[mid])
return recu_find(data, mid+1, right, key); //右边找
else
return mid; //找到了
}
return -1; //没有找到
}
//定义二分查找
int half_find(int data[], int size, int key) {
//调用递归函数来实现
return recu_find(data, 0, size-1, key);
#if 0
//不用递归
int left = 0;
int right = size-1;
while(left <= right) {
int mid = (left+right)/2; //二分
if(key < data[mid]) //左侧继续找
right = mid - 1;//重新定义右边界,中间值不要
else if(key > data[mid]) //右侧继续找
left = mid + 1; //重新定义左边界,中间值不要
else
return mid; //找到了
}
return -1; //没有找到
#endif
}
vim main.c
//main.c:测试
#include "find.h"
#include "sort.h"
int main(void) {
int data[] = {9, 0, 7, 2, 5, 4, 1, 6, 3, 8};
int size = sizeof(data)/sizeof(data[0]);
int key = 7;
int index = -1;
//线性
index = line_find(data, size, key);
if(index == -1) {
printf("没有找到.\n");
return -1;
}
printf("找到:data[%d] = %d\n", index, data[index]);
//先快排
quick_sort(data, 0, size-1);
//后二分
index = half_find(data, size, key);
if(index == -1) {
printf("没有找到.\n");
return -1;
}
printf("找到:data[%d] = %d\n", index, data[index]);
return 0;
}
vim Makefile
#Makefile
BIN=find
OBJ=main.o find.o sort.o
CC=gcc
$(BIN):$(OBJ)
$(CC) -o $(BIN) $(OBJ)
%.o:%.c
$(CC) -c -o $@ $<
clean:
rm $(BIN) $(OBJ)
算法执行的时间跟很多因素相关:
1.跟代码的实现方式相关:函数/宏函数
2.跟编程语言相关:C,python
3.跟编译器相关:inline
4.跟硬件相关:单核,多核
5.操作系统相关:单任务/多任务
6.平均时间复杂度:O
空间复杂度:S
冒泡/选择/插入:O(N^2)
快排:O(NlogN)