【夢馬】程序員必備的九種算法(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);

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