【梦马】程序员必备的九种算法(C语言实现)

(一) BFPRT算法

世界十大经典算法之一,由Blum 、 Floyd 、 Pratt 、 Rivest 、 Tarjan提出,故称为BFPRT算法。

该算法解决的事是如何在n个数中找出第二大的数,抽象下就是求n个数中第k大(小)的数。当时想到的算法是取前k个排序,保存在数组中,然后遍历后n-k,并将每次遍历的数与数组中的k个值比较并重新排序。时间复杂度o(kn),如果k小还好说,但是k大了就不好办了。而BFPRT则在线性时间解决了这个问题。

①将整个数组array[] 5个5个分组,共有n/5组

②在组内使用插入排序

③找到每个组的中位数,组成新的数组(长度为n/5) newArray[]

④递归调用bfprt(newArray, newArray.length/2) 求出其中位数 num

⑤然后利用num进行3方法中进行partition

<num =num >num
⑥若在=num区域时,则返回,否则,选择其中的一半进行后续的partition

代码:

#include <stdio.h>
#include <stdlib.h>
//另类快速排序算法
int compInc(const void *a, const void *b)
{
return *(int *)a - *(int *)b;
}

int main()
{
float k; 注意输出格式
int a[1001];
int len ;

while(scanf("%d “,&len)!=EOF){ //要注意输入的终止条件
for(i=0;i<len;i++) //输入
scanf(”%d",&a[i]);
// printf(“递增排序结果:\n”);
qsort(a, len, sizeof(a[0]), compInc);
if(len%2==0) { //奇偶取中位数的方法不一样
k=(a[(len/2)-1]+a[(len/2)]);
printf("%.2f\n",k/2.00);
}
else {
k=a[len/2];
printf("%.2f\n",k);
}
// break;

}
return 0;
}

(二)朴素贝叶斯分类算法

1.1 概述

贝叶斯分类算法时一大类分类算法的总称。贝叶斯分类算法以样本可能属于某类的概率来作为分类依据。朴素贝叶斯分类算法时贝叶斯分类算法中最简单的一种。
注:朴素的意思时条件概率独立性(建议大家对概率中的独立重复事件有所了解)

1.2 算法思想

朴素贝叶斯的思想是这样的:如果一个事物在一些属性条件发生的情况下,事物属于A的概率>属于B的概率,则判定事物属于A。
通俗来说比如,在某条大街上,有100人,其中有50个美国人,50个非洲人,看到一个讲英语的黑人,那么我们是怎么去判断他来自哪里?
提取特征:
肤色:黑,语言:英语

先验知识:
P(黑色|非洲人) = 0.8
P(讲英语|非洲人)=0.1
P(黑色|美国人)= 0.2
P(讲英语|美国人)=0.9
要判断的概率是:
P(非洲人|(讲英语,黑色) )
P(美国人|(讲英语,黑色) )

思考过程:

P(非洲人|(讲英语,黑色) ) 的 分子= 0.1 * 0.8 *0.5 =0.04
P(美国人|(讲英语,黑色) ) 的 分子= 0.9 *0.2 * 0.5 = 0.09
从而比较这两个概率的大小就等价于比较这两个分子的值,可以得出结论,此人应该是:美国人。
其蕴含的数学原理如下:
p(A|xy)=p(Axy)/p(xy)=p(Axy)/p(x)p(y)=p(A)/p(x)p(A)/p(y) p(xy)/p(xy)=p(A|x)p(A|y)
(案例借鉴于 CSDN博主「xujing123qwe」)
由于朴素贝叶斯分类算法代码用C语言实现十分复杂,故不在此处展示

(三) 堆排序

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
 我们知道,堆分为"最大堆"和"最小堆"。最大堆通常被用来进行"升序"排序,而最小堆通常被用来进行"降序"排序。
鉴于最大堆和最小堆是对称关系,理解其中一种即可。本文将对最大堆实现的升序排序进行详细说明。

最大堆进行升序排序的基本思想:
① 初始化堆:将数列a[1…n]构造成最大堆。
② 交换数据:将a[1]和a[n]交换,使a[n]是a[1…n]中的最大值;然后将a[1…n-1]重新调整为最大堆。 接着,将a[1]和a[n-1]交换,使a[n-1]是a[1…n-1]中的最大值;然后将a[1…n-2]重新调整为最大值。 依次类推,直到整个数列都是有序的。

实现中用到了"数组实现的二叉堆的性质"。
在第一个元素的索引为 0 的情形中:
性质一:索引为i的左孩子的索引是 (2i+1);
性质二:索引为i的左孩子的索引是 (2
i+2);
性质三:索引为i的父结点的索引是 floor((i-1)/2);

代码如下

#include <stdio.h>
#include <malloc.h>
void HeapAdjust(int a[],int s,int m)//一次筛选的过程
{
int rc,j;
rc=a[s];
for(j=2s;j<=m;j=j2)//通过循环沿较大的孩子结点向下筛选
{
if(j<m&&a[j]<a[j+1]) j++;//j为较大的记录的下标
if(rc>a[j]) break;
a[s]=a[j];s=j;
}
a[s]=rc;//插入
}
void HeapSort(int a[],int n)
{
int temp,i,j;
for(i=n/2;i>0;i–)//通过循环初始化顶堆
{
HeapAdjust(a,i,n);
}
for(i=n;i>0;i–)
{
temp=a[1];
a[1]=a[i];
a[i]=temp;//将堆顶记录与未排序的最后一个记录交换
HeapAdjust(a,1,i-1);//重新调整为顶堆
}
}
int main()
{
int n,i;
scanf("%d",&n);
int a[n+1];
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
HeapSort(a,n);
}

注:mac需要改变malloc.h头文件(血的教训!)

(四)归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之),将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序,若将两个有序表合并成一个有序表,称为二路归并

1、归并排序的基本思想

将待排序序列R[0…n-1]看成是n个长度为1的有序序列,将相邻的有序表成对归并,得到n/2个长度为2的有序表;将这些有序序列再次归并,得到n/4个长度为4的有序序列;如此反复进行下去,最后得到一个长度为n的有序序列

2、归并排序的算法描述

第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置

第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

重复步骤3直到某一指针超出序列尾,将另一序列剩下的所有元素直接复制到合并序列尾

归并排序其实要做两件事:

(1)“分解”——将序列每次折半划分(递归实现)

(2)“合并”——将划分后的序列段两两合并后排序

如何合并?

在每次合并过程中,都是对两个有序的序列段进行合并,然后排序。

这两个有序序列段分别为 R[low, mid] 和 R[mid+1, high]。

先将他们合并到一个局部的暂存数组R2中,带合并完成后再将R2复制回R中。

我们称 R[low, mid] 第一段,R[mid+1, high] 为第二段。

每次从两个段中取出一个记录进行关键字的比较,将较小者放入R2中,最后将各段中余下的部分直接复制到R2中。

经过这样的过程,R2已经是一个有序的序列,再将其复制回R中,一次合并排序就完成了。

3、代码实现

(借鉴于蓝海人作者)
/* 将序列对半拆分直到序列长度为1*/
void MergeSort_UptoDown(int *num, int start, int end)
{
int mid = start + (end - start) / 2;

if (start >= end)
{
return;
}

MergeSort_UptoDown(num, start, mid);
MergeSort_UptoDown(num, mid + 1, end);

Merge(num, start, mid, end);
}

void Merge(int *num, int start, int mid, int end)
{
int *temp = (int *)malloc((end-start+1) * sizeof(int)); //申请空间来存放两个有序区归并后的临时区域
int i = start;
int j = mid + 1;
int k = 0;

while (i <= mid && j <= end)
{
if (num[i] <= num[j])
{
temp[k++] = num[i++];
}
else
{
temp[k++] = num[j++];
}
}

while (i <= mid)
{
temp[k++] = num[i++];
}
while (j <= end)
{
temp[k++] = num[j++];
}

//将临时区域中排序后的元素,整合到原数组中
for (i = 0; i < k; i++)
{
num[start + i] = temp[i];
}

free(temp);
}

(五)快速排序

1.

快排是对冒泡排序的一种改进,在快速排序中,元素的比较和移动是从两端向中间进行的,关键码较大的元素一次就能从前面移动到后面,关键码较小的元素一次就能从后面移动到前面,元素移动距离的较远,从而减少了总的比较次数和移动次数

2

.快速排序是基于分治法设计的,其分治策略是:
①、划分:选定一个元素作为轴值,以轴值为基准将整个序列划分为两个子序列。轴值的位置在划分的过程中确定,并且前一个子序列的元素均小于或者等于轴值,后一个子序列的元素大于或者等于轴值

②、求解子问题:分别对划分后的每一个子序列递归处理
③、合并:由于对子序列的排序是就地进行的,所以合并并不需要执行任何操作

代码示范

#include <stdlib.h>

#include <stdio.h>

if (end > begin) {

int l = begin + size;

int r = end;

while(l < r) {

if (cmp(array+l,pivot) <= 0) {

l += size;

} else {

r -= size;

swap(array+l, array+r, size);

}

}

}

typedef int type;

int num_list[]={5,4,3,2,1};

int len=sizeof(num_list)/sizeof(type);

int i;

qsort(num_list,len,sizeof(type),type_cmp);

printf(“sorted_num_list={”);

for(i=0; i<len; i++){

printf("%s%d",sep,num_list[i]);

}

printf("};n");

}

(六)二分查找算法

简析:

二分法查找适用于数据量较大时,但是数据需要先排好顺序。
主要思想是:(设查找的数组区间为array[low, high])
确定该区间的中间位置K将查找的值T与array[k]比较。若相等,查找成功返回此位置;否则确定新的查找区域,继续二分查找。
区域确定如下:a.array[k]>T 由数组的有序性可知array[k,k+1,……,high]>T;故新的区间为array[low,……,K-1]b.array[k]<T 类似上面查找区间为array[k+1,……,high]。每一次查找与中间值比较,可以确定是否查找成功,不成功当前查找区间将缩小一半,递归查找即可。

代码如下:(强推php中文网的代码规范 超赞 如下)
#include <stdio.h>
int binary_search(int key,int a[],int n) //自定义函数binary_search()
{
int low,high,mid,count=0,count1=0;
low=0;
high=n-1;
while(low<high) //查找范围不为0时执行循环体语句
{
count++; //count记录查找次数
mid=(low+high)/2; //求中间位置
if(key<a[mid]) //key小于中间值时
high=mid-1; //确定左子表范围
else if(key>a[mid]) //key 大于中间值时
low=mid+1; //确定右子表范围
else if(key= =a[mid]) //当key等于中间值时,证明查找成功
{
printf(“查找成功!\n 查找 %d 次!a[%d]=%d”,count,mid,key); //输出查找次数及所查找元素在数组中的位置
count1++; //count1记录查找成功次数
break;
}
}
if(count1==0) //判断是否查找失败
printf(“查找失败!”); //查找失败输出no found
return 0;
}
int main()
{
int i,key,a[100],n;
printf(“请输入数组的长度:\n”);
scanf("%d",&n); //输入数组元素个数
printf(“请输入数组元素:\n”);
for(i=0;i<n;i++)
scanf("%d",&a[i]); //输入有序数列到数组a中
printf(“请输入你想查找的元素:\n”);
scanf("%d",&key); //输入要^找的关键字
binary_search(key,a,n); //调用自定义函数
printf("\n");
return 0;
}

(七)动态规划

简介:

动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。

首先,动态规划最重要的是掌握他的思想,动态规划的核心思想是把原问题分解成子问题进行求解,也就是分治的思想。

动态规划问题,大致可以通过以下四部进行解决。

我们通过一个现实中的例子,来理解这个问题。大家可能在公司里面都有一定的组织架构,可能有高级经理、经理、总监、组长然后才是小开发,今天我们通过这个例子,来讲讲什么问题适合使用动态规划。又到了一年一度的考核季,公司要挑选出三个最优秀的员工。一般高级经理会跟手下的经理说,你去把你们那边最优秀的3个人报给我,经理又跟总监说你把你们那边最优秀的人报给我,经理又跟组长说,你把你们组最优秀的三个人报给我,这个其实就动态规划的思想(素材来源于沙茶敏碎碎念)

1.划分状态,即划分子问题,例如上面的例子,我们可以认为每个组下面、每个部门、每个中心下面最优秀的3个人,都是全公司最优秀的3个人的子问题
2.状态表示,即如何让计算机理解子问题。上述例子,我们可以实用f[i][3表示第i个人,他手下最优秀的3个人是谁。
3.状态转移,即父问题是如何由子问题推导出来的。上述例子,每个人大Leader下面最优秀的人等于他下面的小Leader中最优秀的人中最优秀的几个。
4.确定边界,确定初始状态是什么?最小的子问题?最终状态又是什么。例如上述问题,最小的子问题就是每个小组长下面最优秀的人,最终状态是整个企业,初始状态为每个领导下面都没有最优名单,但是小组长下面拥有每个人的评分。
#include<stdio.h>
#define V 1500
int f[10][V];//全局变量,自动初始化为0
int weight[10];
int value[10];
#define max(x,y) (x)>(y)?(x):(y)
int main()
{

f[i+1][j]=max{f[i][j],f[i][j-weight[i+1]+value[i+1]}
int N,M;
freopen(“1.txt”,“r”,stdin);
scanf("%d%d",&N,&M);//N物品个数 M揹包容量
for (int i=1;i<=N; i++)
{
scanf("%d%d",&weight[i],&value[i]);
}
//动态规划
for (int i=1; i<=N; i++)
for (int j=1; j<=M; j++)
{
if (weight[i]<=j)
{
f[i][j]=max(f[i-1][j],f[i-1][j-weight[i]]+value[i]);
}
else
f[i][j]=f[i-1][j];
}
printf("%d\n",f[N][M]);//输出最优解
}

(八)深度优先搜索算法

简介:

事实上,深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次.
深度优先的基本原则:按照某种条件往前试探搜索,如果前进中遭到失败(正如老鼠遇到死胡同)则退回头另选通路继续搜索,直到找到满足条件的目标为止。

代码来源于百度知道
#include <stdlib.h>
#include <stdio.h>

struct node /* 图顶点结构定义 /
{
int vertex; /
顶点数据信息 */
struct node nextnode; / 指下一顶点的指标 */
};
typedef struct node graph; / 图形的结构新型态 /
struct node head[9]; /
图形顶点数组 /
int visited[9]; /
遍历标记数组 */

/根据已有的信息建立邻接表/
void creategraph(int node[20][2],int num)/num指的是图的边数/
{
graph newnode; /指向新节点的指针定义/
graph ptr;
int from; /* 边的起点 /
int to; /
边的终点 /
int i;
for ( i = 0; i < num; i++ ) /
读取边线信息,插入邻接表*/
{
from = node[i][0]; /* 边线的起点 /
to = node[i][1]; /
边线的终点 */

/* 建立新顶点 /
newnode = ( graph ) malloc(sizeof(struct node));
newnode->vertex = to; /
建立顶点内容 /
newnode->nextnode = NULL; /
设定指标初值 /
ptr = &(head[from]); /
顶点位置 /
while ( ptr->nextnode != NULL ) /
遍历至链表尾 /
ptr = ptr->nextnode; /
下一个顶点 /
ptr->nextnode = newnode; /
插入节点 */
}
}

/********************** 图的深度优先搜寻法********************/
void dfs(int current)
{
graph ptr;
visited[current] = 1; /* 记录已遍历过 /
printf(“vertex[%d]\n”,current); /
输出遍历顶点值 /
ptr = head[current].nextnode; /
顶点位置 /
while ( ptr != NULL ) /
遍历至链表尾 /
{
if ( visited[ptr->vertex] == 0 ) /
如过没遍历过 /
dfs(ptr->vertex); /
递回遍历呼叫 /
ptr = ptr->nextnode; /
下一个顶点 */
}
}

/****************************** 主程序******************************/
int main()
{
graph ptr;
int node[20][2] = { {1, 2}, {2, 1}, /* 边线数组 /
{1, 3}, {3, 1},
{1, 4}, {4, 1},
{2, 5}, {5, 2},
{2, 6}, {6, 2},
{3, 7}, {7, 3},
{4, 7}, {4, 4},
{5, 8}, {8, 5},
{6, 7}, {7, 6},
{7, 8}, {8, 7} };
int i;
//clrscr();
for ( i = 1; i <= 8; i++ ) /
顶点数组初始化 /
{
head[i].vertex = i; /
设定顶点值 /
head[i].nextnode = NULL; /
指针为空 /
visited[i] = 0; /
设定遍历初始标志 /
}
creategraph(node,20); /
建立邻接表 /
printf(“Content of the gragh’s ADlist is:\n”);
for ( i = 1; i <= 8; i++ )
{
printf(“vertex%d ->”,head[i].vertex); /
顶点值 /
ptr = head[i].nextnode; /
顶点位置 /
while ( ptr != NULL ) /
遍历至链表尾 /
{
printf(" %d ",ptr->vertex); /
印出顶点内容 /
ptr = ptr->nextnode; /
下一个顶点 /
}
printf("\n"); /
换行 /
}
printf("\nThe end of the dfs are:\n");
dfs(1); /
打印输出遍历过程 /
printf("\n"); /
换行 */
puts(" Press any key to quit…");
// getch();
}

(九)广度优先搜索算法

宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。
代码如下(借鉴CSDN博主「waectr」的原代码):
#include<stdio.h>

struct note{
int x;//横座标
int y;//纵座标
int f;//父在队列中的编号
int s;//步数
};

int main(){
struct note que[2501];//创建队列
int a[51][51]={0},book[51][51]={0};
//定义方向的数组
int next[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
int head,tail;
int i,j,m,n,startx,starty,p,q,tx,ty,flag;
scanf("%d %d",&n,&m);
for(i=1;i<=n;i++){
for(j=1;j<=m;j++){
scanf("%d",&a[i][j]);
}
}

//输入出发点和要到达的点
scanf("%d %d %d %d",&startx,&starty,&p,&q);

//初始化队列
head=1;tail=1;
que[tail].x=startx;
que[tail].y=starty;
que[tail].f=0;
que[tail].s=0;
tail++;
book[startx][starty]=1;

flag=0;//用来标记是否到达

//判断队列
while(head<tail){

//向着四个方向出发
for(i=0;i<4;i++){

tx=que[head].x+next[i][0];
ty=que[head].y+next[i][1];

if(tx<1||tx>n||ty<1||ty>m){
continue;
}

if(a[tx][ty]= =0&&book[tx][ty]= =0){
book[tx][ty]=1;
que[tail].x=tx;
que[tail].y=ty;
que[tail].f=head;
que[tail].s=que[head].s+1;
tail++;
}
if(tx= =p&&ty= =q){
flag=1;
break;
}
}
if(flag==1){
break;
}
head++;
}

printf("%d",que[tail-1].s);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章