当前位置:首页 > 算法 > 正文内容

常见算法的C语言实现(带题目、分析和答案) 穷举 递归 迭代 递推 分治 回溯 动态规划 贪心

chanra1n1年前 (2023-11-28)算法2850

1.1   基本思想

1.1.1  穷举

穷举(又称枚举)算法的基本思想是从所有可能的情况中搜索正确的答案。

a)      题一

查找数组中的两个元素,它们的和等于给定的目标值。给定一个包含 n 个整数的数组和一个目标值,找出数组中是否存在两个元素,它们的和等于目标值。如果存在,返回这两个元素的索引,如果不存在,返回一个特殊值(如-1)。

思路:

穷举所有可能的组合: 使用两层循环遍历数组的所有元素组合。

判断是否满足条件: 对于每一对元素,判断它们的和是否等于目标值。

返回结果: 如果找到满足条件的组合,返回它们的索引;否则,返回特殊值。

解:

#include <stdio.h>

 

// 穷举法查找数组中两个元素的和等于目标值的索引

void findTwoElements(int arr[], int size, int target) {

    // 遍历数组的所有元素组合

    for (int i = 0; i < size - 1; i++) {

        for (int j = i + 1; j < size; j++) {

            // 判断是否满足条件

            if (arr[i] + arr[j] == target) {

                // 找到满足条件的组合,输出索引

                printf("找到元素:%d + %d = %d,索引:%d 和 %d\n", arr[i], arr[j], target, i, j);

                return;

            }

        }

    }

 

    // 没有找到满足条件的组合

    printf("没有找到满足条件的组合\n");

}

 

int main() {

    // 示例数组

    int arr[] = {2, 7, 11, 15};

    // 目标值

    int target = 9;

 

    // 数组的大小

    int size = sizeof(arr) / sizeof(arr[0]);

 

    // 使用穷举法查找数组中两个元素的和等于目标值的索引

    findTwoElements(arr, size, target);

 

    return 0;

}

1.1.2  递归

递归调用是一个方法在其方法体内调用其自身方法。

b)      题一

image.png

解1(推导法):

在f函数中,当输入的x大于0时,返回x*f(x-1),当x等于0时,返回2。

       可将f看做An,且A0=2。An=n*A(n-1);则An/A(n-1)=n;则An=2*!n,(n>=1)

       则f(f(1)=4。

解2(带值法):

在f函数中,当输入的x大于0时,返回x*f(x-1),当x等于0时,返回2。

例如输入0,则f返回2。输入1,f返回1*f(1-1)=1*2=2。输入2,则返回2*f(2-1)=2*2=4。

       对于i=f(f(1)),从内开始看,f(1)=2,则结果等价于f(2),即等于4。

c)       题二

编写计算斐波那契(Fibonacci)数列的函数,数列大小为n。无穷数列1,1,2,3,5,8,13,21,35,…,称为斐波那契数列。这种数列有一个规律,数列第1个与第2个元素的值均为1,从第3个值开始,每个数据是前两个数据之和。

解:
数列可以表示为:

1,1,(1+1),(1+(1+1)),((1+1)+(1+(1+1)))…

在此,我们可以把这种规律转成递推式:

image.png

有了递推式,再来写程序,也就很容易了,直接转化即可,该问题程序实现如下所示:

image.png

1.1.3  迭代

很多时候无法使用公式一次求解,当使用迭代法时,通常是通过反复应用某种规则或算法来逐步逼近问题的解。

a)      题一

给定一个非负整数,请使用加减乘除和循环语句实现算法,计算其平方根并返回,只保留整数部分即可。

思路:

选择初始解: 使用给定的数作为初始解。

迭代更新: 反复应用迭代公式,逐步逼近平方根。

判断停止条件: 当迭代结果满足停止条件时,返回结果。

解:

#include <stdio.h>

 

// 迭代法计算平方根

int sqrtIteration(int x) {

    if (x == 0 || x == 1) {

        return x;

    }

 

    // 初始化解为 x/2

    int result = x / 2;

 

    // 迭代更新

    while (result > x / result) {

        result = (result + x / result) / 2;

    }

 

    return result;

}

 

int main() {

    // 示例输入

    int x = 25;

 

    // 使用迭代法计算平方根

    int result = sqrtIteration(x);

 

    // 输出结果

    printf("数字 %d 的平方根为 %d\n", x, result);

 

    return 0;

}

1.1.4  递推

递推算法是一种理性思维模式的代表,其根据已有的数据和关系,逐步推导而得到结果。

a)      题一

递推和递归的区别?

递推:

计算方式: 递推是一种迭代的计算方式,通过定义初始项和递推关系,从初始项开始逐步计算出目标项。

实现方式: 通常使用循环结构,从初始项开始,通过迭代计算得到目标项。

特点: 递推是一种自底向上的计算方式,从已知的初始项逐步推导出后续项,不涉及函数调用栈的压栈和弹栈过程。

 

递归:

计算方式: 递归是一种通过将问题分解为更小规模的相同问题来解决的方式,通过递归调用自身来计算目标项。

实现方式: 通常通过函数的递归调用来实现,每次调用解决一个更小规模的问题,直到达到基本情况。

特点: 递归是一种自顶向下的计算方式,通过将大问题分解为小问题,通过递归调用逐步解决小问题,并将结果合并。

 

区别总结:

递推是迭代的,递归是通过函数调用实现的。

递推从初始项开始迭代计算,自底向上;递归通过不断将问题分解,自顶向下。

递推通常使用循环结构,递归使用函数调用。

b)      题二

计算斐波那契数列。

思路:

定义初始项: 斐波那契数列的前两项为 0 和 1。

递推关系: 第 n 项等于前两项的和,即 F(n) = F(n-1) + F(n-2)。

递推计算: 使用循环或递归,依次计算出第 n 项的值。

解:

#include <stdio.h>

 

// 递推法计算斐波那契数列的第 n 项

int fibonacci(int n) {

    if (n == 0) {

        return 0;

    } else if (n == 1) {

        return 1;

    }

 

    // 定义初始项

    int fib0 = 0;

    int fib1 = 1;

 

    // 递推计算

    for (int i = 2; i <= n; i++) {

        int temp = fib1;

        fib1 = fib0 + fib1;

        fib0 = temp;

    }

 

    return fib1;

}

 

int main() {

    // 示例输入

    int n = 7;

 

    // 使用递推法计算斐波那契数列的第 n 项

    int result = fibonacci(n);

 

    // 输出结果

    printf("斐波那契数列的第 %d 项为 %d\n", n, result);

 

    return 0;

}

1.1.5  分治

分治算法的基本思想是将一个计算复杂的问题分成规模较小、计算简单的小问题求解,然后综合各个小问题,得到最终答案。

归并排序体现的便是分治的思想,将大问题分成小问题,并进行解决。

a)      题一

给定一个包含 n 个元素的数组,设计一个分治算法,找到数组中的最大元素及其索引。

思路:

基本情况: 如果数组中只有一个元素,那么该元素即为最大元素。

分解问题: 将数组分为两半,分别在左半部分和右半部分递归查找最大元素。

合并结果: 比较左右两半部分找到的最大元素,返回较大者。

解:

#include <stdio.h>

 

// 定义结构体表示包含最大元素及其索引的结果

struct MaxResult {

    int maxElement;

    int maxIndex;

};

 

// 分治算法函数,查找数组中的最大元素及其索引

struct MaxResult findMax(int arr[], int start, int end) {

    struct MaxResult result;

 

    // 基本情况:只有一个元素

    if (start == end) {

        result.maxElement = arr[start];

        result.maxIndex = start;

        return result;

    }

 

    // 分解问题:将数组分为两半

    int mid = (start + end) / 2;

 

    // 递归查找左半部分的最大元素

    struct MaxResult leftResult = findMax(arr, start, mid);

 

    // 递归查找右半部分的最大元素

    struct MaxResult rightResult = findMax(arr, mid + 1, end);

 

    // 合并结果:比较左右两半部分找到的最大元素

    if (leftResult.maxElement > rightResult.maxElement) {

        return leftResult;

    } else {

        return rightResult;

    }

}

 

int main() {

    // 示例数组

    int arr[] = {3, 1, 8, 6, 2, 7, 4, 5};

 

    // 数组的大小

    int size = sizeof(arr) / sizeof(arr[0]);

 

    // 使用分治算法查找最大元素及其索引

    struct MaxResult result = findMax(arr, 0, size - 1);

 

    // 输出结果

    printf("最大元素:%d,索引:%d\n", result.maxElement, result.maxIndex);

 

    return 0;

}

1.1.6  回溯

回溯法也是枚举法的一种,它的特点就是在搜索过程中寻找问题的解,当发现不满足求解条件时就回溯(返回),尝试别的路径,避免无效搜索。

a)      题一

老鼠走迷宫,采用尝试错误的方法找到出口,在走错路时就退回来并把走过的路记下来,避免下次走重复的路,就这样找到出口为止。

老鼠需要遵守以下三个原则:

1、一次只能走一格。

2、遇到墙无法往前走则退回一步,寻找其它路。

3、走过的路不再走第二次。

使用二维数组代表地图,值为1则代表不能通行,值为0则代表可以通行。

假设老鼠从左上角[1][1]进入,从右下角[8][10]出来。

可以使用链表来记录走过的位置,并且将走过的位置所对应的数组元素内容标记为2,然后将这个位置放入堆栈,再进行下一个方向

或路的选择。如果走到死胡同并且还没有抵达终点,就退回到上一个位置,再选择其他的路。由于每次新加入的位置必定会在堆栈的

顶端,因此堆栈顶端指针所指向的方格编号就是当前老鼠的位置,如此重复直至走到迷宫出口为止。

思路:

       回溯法通常使用栈实现。例如求解括号组合,图的深度优先遍历等。

解:

#include <stdio.h>

 

// 定义迷宫的大小

#define ROWS 8

#define COLS 10

 

// 迷宫地图,1表示墙,0表示通路

int maze[ROWS][COLS] = {

    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},

    {1, 0, 0, 0, 1, 0, 0, 0, 0, 1},

    {1, 0, 1, 0, 1, 0, 1, 1, 0, 1},

    {1, 0, 1, 0, 1, 0, 0, 1, 0, 1},

    {1, 0, 1, 1, 1, 1, 1, 1, 0, 1},

    {1, 0, 0, 0, 0, 0, 0, 0, 0, 1},

    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},

    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}

};

 

// 定义堆栈

int stack[ROWS * COLS][2];

int top = -1; // 堆栈顶端指针

 

// 打印迷宫路径

void printPath() {

    for (int i = 0; i <= top; i++) {

        printf("(%d, %d) -> ", stack[i][0], stack[i][1]);

    }

    printf("Exit\n");

}

 

// 回溯法解决老鼠走迷宫问题

int solveMaze(int startRow, int startCol, int endRow, int endCol) {

    // 起点入栈

    push(startRow, startCol);

    maze[startRow][startCol] = 2; // 标记已经走过的位置

 

    // 定义四个方向的移动

    int directions[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

 

    while (!isEmpty()) {

        int currentRow = stack[top][0];

        int currentCol = stack[top][1];

 

        // 到达终点

        if (currentRow == endRow && currentCol == endCol) {

            printPath();

            return 1;

        }

 

        // 尝试向四个方向移动

        int moved = 0;

        for (int i = 0; i < 4; i++) {

            int newRow = currentRow + directions[i][0];

            int newCol = currentCol + directions[i][1];

 

            // 判断新位置是否合法

            if (maze[newRow][newCol] == 0) {

                push(newRow, newCol);

                maze[newRow][newCol] = 2; // 标记已经走过的位置

                moved = 1;

                break;

            }

        }

 

        // 如果无法移动,回溯

        if (!moved) {

            pop();

        }

    }

 

    // 无解

    printf("No solution\n");

    return 0;

}

 

int main() {

    // 设置起点和终点

    int startRow = 1, startCol = 1;

    int endRow = 6, endCol = 8;

 

    // 解决老鼠走迷宫问题

    solveMaze(startRow, startCol, endRow, endCol);

 

    return 0;

}

 

1.1.7  动态规划

动态规划法(Dynamic Programming Algorithm,DPA)类似于分治法,动态规划法的主要做法:

如果一个问题的答案与子问题相关,就能将大问题拆解成各个小问题,

其中与分治法最大的不同是可以让每一个子问题的答案被存储起来,以供下次求解时直接取用。

这样的做法不仅可以减少再次计算的时间,而且可以将这些解组合成大问题的解,可以解决重复计算的问题。

a)      题一

给定一个未排序的整数数组,找到最长递增子序列的长度。

思路:

定义状态: 定义一个状态数组 dp,其中 dp[i] 表示以第 i 个元素结尾的最长递增子序列的长度。

状态转移方程: 对于每个元素 nums[i],遍历其之前的元素 nums[j],如果 nums[i] > nums[j],则更新 dp[i] = max(dp[i], dp[j] + 1)。

最终结果: 返回 dp 数组中的最大值。

解:

#include <stdio.h>

 

// 动态规划法计算最长递增子序列的长度

int lengthOfLIS(int nums[], int size) {

    if (size == 0) {

        return 0;

    }

 

    // 初始化状态数组

    int dp[size];// 注意,数组长度必须为常量,这里为了方便编写。实际可以通过malloc实现类似功能。

    for (int i = 0; i < size; i++) {

        dp[i] = 1; // 初始长度为1

    }

 

    // 动态规划状态转移

    for (int i = 1; i < size; i++) {

        for (int j = 0; j < i; j++) {

            if (nums[i] > nums[j]) {

                dp[i] = (dp[i] > dp[j] + 1) ? dp[i] : dp[j] + 1;

            }

        }

    }

 

    // 返回dp数组中的最大值

    int maxLength = 1;

    for (int i = 0; i < size; i++) {

        maxLength = (maxLength > dp[i]) ? maxLength : dp[i];

    }

 

    return maxLength;

}

 

int main() {

    // 示例数组

    int nums[] = {10, 9, 2, 5, 3, 7, 101, 18};

 

    // 数组的大小

    int size = sizeof(nums) / sizeof(nums[0]);

 

    // 使用动态规划法计算最长递增子序列的长度

    int result = lengthOfLIS(nums, size);

 

    // 输出结果

    printf("最长递增子序列的长度为 %d\n", result);

 

    return 0;

}

 

1.1.8  贪心

贪心法(Greed Method)又称贪婪算法,从某一起点开始,在每一个解决问题的步骤使用贪心原则,

即采用在当前状态下最有利或最优化的选择,不断地改进该解答,持续在每一个步骤中选择最佳的方法,

并且逐步逼近给定的目标,当达到某一个步骤不能再继续前进时,算法就停止,以尽可能快的方法求得更好的解。

a)      题一

有6种物品,它们的体积分别为:60、45、35、20、20和20单位体积,箱子的容积为100个单位体积。现在求需要几只箱子才能把这些物品都装起来。

解:

使用贪婪法求解该问题的基本思想是:先将物品的体积按从大到小的顺序排列。然后依次将物品放到它第一个能放进去的箱子中,若当前箱子装不下当前物品,则启用一个新的箱子装该物品,直到所有的物品都装入了箱子。如采用这种方式,我们发现,得到解决方案为:1、3号物品放一个箱子,2、4、5放第二个箱子,6放在第3个箱子,一共需要3个箱子。但由于此问题很简单,我们可以很容易用人工计算的方式得知最优解只要2个箱子。即:1、4、5和2、3、6。所以从此可以看出贪婪法求的是可行解,而非最优解。

#include <stdio.h>

 

#define NUM_ITEMS 6

#define BIN_CAPACITY 100

 

// 定义物品结构体

struct Item {

    int index;

    int volume;

};

 

// 比较函数,用于qsort排序

int compareItems(const void *a, const void *b) {

    return ((struct Item *)b)->volume - ((struct Item *)a)->volume;

}

 

// 贪婪法求解函数

int greedyBinPacking(struct Item items[], int numItems) {

    qsort(items, numItems, sizeof(struct Item), compareItems);

 

    int bins = 0;

    int binRemainingCapacity = BIN_CAPACITY;

 

    for (int i = 0; i < numItems; ++i) {

        if (binRemainingCapacity >= items[i].volume) {

            printf("Item %d in Bin %d\n", items[i].index, bins + 1);

            binRemainingCapacity -= items[i].volume;

        } else {

            bins++;

            binRemainingCapacity = BIN_CAPACITY - items[i].volume;

            printf("Item %d in Bin %d\n", items[i].index, bins + 1);

        }

    }

 

    return bins + 1; // 返回箱子数量

}

 

int main() {

    struct Item items[NUM_ITEMS] = {

        {1, 60},

        {2, 45},

        {3, 35},

        {4, 20},

        {5, 20},

        {6, 20}

    };

 

    int numBins = greedyBinPacking(items, NUM_ITEMS);

 

    printf("Total bins needed: %d\n", numBins);

 

    return 0;

}


扫描二维码推送至手机访问。

版权声明:本文由我的FPGA发布,如需转载请注明出处。

本文链接:http://myfpga.cn/index.php/post/342.html

分享给朋友:

“常见算法的C语言实现(带题目、分析和答案) 穷举 递归 迭代 递推 分治 回溯 动态规划 贪心” 的相关文章

Hanoi Tower问题的简单实现

Hanoi Tower问题的简单实现

设A,B,C是3个塔座。开始时,在塔座A上有一叠共n个圆盘,这些圆盘自上而下,由小到大地叠在一起。各圆盘从小到大编号为1,2,…,n,现要求将塔座A上的这一叠圆盘移到塔座C上,并仍按同样顺序叠置。在移动圆盘时应遵守以下移动规则:(1)每次只能移动1个圆盘;(2)任何时刻都不允许将较大的圆盘压在较小的...

图的应用

图的应用

(1)创建无向图的邻接矩阵存储结构(2)输出无向图的邻接矩阵表示(3)输入任意两个结点,判断是否有边存在(4)输入任意一个结点,求顶点的度(5)根据邻接矩阵求该无向图中边的数目(6)实现图的深度优先遍历(7)实现图的广度优先遍历#include<stdio.h> #include<...

(LeetCode刷题)1. 两数之和

(LeetCode刷题)1. 两数之和

题目解答一:/**  * Note: The returned array must be malloced, assume caller calls free(). &nbs...

(LeetCode刷题)3. 无重复字符的最长子串

(LeetCode刷题)3. 无重复字符的最长子串

题目:解法一:class Solution(object):     def lengthOfLongestSubstring(self,s):        &nb...